Dynamics CRM Online 2016 Web API を Java から使ってみた。

Apache Olingo, Azure Active Directory, Java, Microsoft Azure, Microsoft Dynamics CRM Online, OAuth, Open Data Protocol, プログラミング

本記事はアプレッソ Advent Calendar 第1日目の記事です。

こんにちは、ソリューション開発部の柴崎です。アプレッソ Advent Calendar に協力会社も参加しても良いとのことで、書かせていただきます!

Microsoft Dynamics CRM の Web API を使う前に

Dynamics CRM 2016 は、RESTful API として OData 4.0 仕様に則りアクセスすることができます。Microsoft 社製の製品だけあってサンプルも .NET のものが多いのですが、OData 4.0 をサポートしているライブラリであれば Dynamics CRM に接続できます。今回は Java を使い、ライブラリとして Apache Olingo を使います。

認証

Dynamics CRM Online の場合は、Azure Active Directory にアプリケーションを事前に登録する必要があります。実際のアクセストークンの取得については別の記事に譲ることとします。参考までに、認可コード取得時に resource=https%3a%2f%2fFOO.crmBAR.dynamics.com のようなパラメータが必要となるので注意してください。

エンティティを取得する

さっそく一番有名と思われる「取引先企業」(accounts) を取得してみましょう。/accounts?$select=name,openrevenue_base の URI にアクセスするコードは以下のようになります。

final ODataClient client = ODataClientBuilder.createClient();
final RetrieveRequestFactory factory = client.getRetrieveRequestFactory();
final URI uri = client.newURIBuilder(SERVICE_ROOT)
        .appendEntitySetSegment("accounts")
        .addQueryOption(QueryOption.SELECT, "name")
        .addQueryOption(QueryOption.SELECT, "openrevenue_base")
        .build();
final ODataEntitySetRequest<ClientEntitySet> request = factory.getEntitySetRequest(uri);
request.addCustomHeader("Authorization", "Bearer " + ACCESS_TOKEN);
final ODataRetrieveResponse<ClientEntitySet> response = request.execute();
final ClientEntitySet body = response.getBody();
final List<ClientEntity> entities = body.getEntities();
entities.stream()
        .map(entity -> String.format(
                "%s\t%s",
                entity.getProperty("name").getValue().asPrimitive().toValue(),
                entity.getProperty("openrevenue_base").getValue().asPrimitive().toValue()
        ))
        .forEach(System.out::println);

※ ハイライトしている行は環境に応じて書き換えてください。

結果

……
フォース コーヒー (サンプル)	0.0
リビングウェア (サンプル)	0.0
アドベンチャー ワークス (サンプル)	0.0
ファブリカム (サンプル)	10000.0
ブルー ヤンダー航空 (サンプル)	25000.0
シティ パワー アンド ライト (サンプル)	0.0
コントソ製薬 (サンプル)	26000.0
アルパイン スキー ハウス (サンプル)	30000.0
エー データム コーポレーション (サンプル)	0.0
コーホー ワイナリー (サンプル)	25000.0
……

試しに直接 API を呼び出し、JSON で返ってきた内容と比較してみましょう。

{
  "@odata.context": "https://********.crm7.dynamics.com/api/data/v8.0/$metadata#accounts(name,openrevenue_base)",
  "value": [
    {
      "@odata.etag": "W/\"******\"",
      "name": "フォース コーヒー (サンプル)",
      "openrevenue_base": 0,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "リビングウェア (サンプル)",
      "openrevenue_base": 0,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "アドベンチャー ワークス (サンプル)",
      "openrevenue_base": 0,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "ファブリカム (サンプル)",
      "openrevenue_base": 10000,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "ブルー ヤンダー航空 (サンプル)",
      "openrevenue_base": 25000,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "シティ パワー アンド ライト (サンプル)",
      "openrevenue_base": 0,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "コントソ製薬 (サンプル)",
      "openrevenue_base": 26000,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "アルパイン スキー ハウス (サンプル)",
      "openrevenue_base": 30000,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "エー データム コーポレーション (サンプル)",
      "openrevenue_base": 0,
      "accountid": "********-****-****-****-************"
    },
    {
      "@odata.etag": "W/\"******\"",
      "name": "コーホー ワイナリー (サンプル)",
      "openrevenue_base": 25000,
      "accountid": "********-****-****-****-************"
    }
  ]
}

問題なく取得できていますね!

エンティティの作成

今度は新しいエンティティを作成してみましょう。

final ODataClient client = ODataClientBuilder.createClient();
final CUDRequestFactory factory = client.getCUDRequestFactory();
final URI uri = client.newURIBuilder(SERVICE_ROOT)
    .appendEntitySetSegment("accounts")
    .build();
final ClientObjectFactory objectFactory = client.getObjectFactory();
final ClientEntity entity = objectFactory.newEntity(null);
{
    final List properties = entity.getProperties();
    final ClientPrimitiveValue val = objectFactory.newPrimitiveValueBuilder().buildString("foo bar");
    final ClientProperty prop = objectFactory.newPrimitiveProperty("name", val);
    properties.add(prop);
}
final ODataEntityCreateRequest request = factory.getEntityCreateRequest(uri, entity);
request.addCustomHeader("Authorization", "Bearer " + ACCESS_TOKEN);
final ODataEntityCreateResponse response = request.execute();
final Collection entityId = response.getHeader("OData-EntityId");
System.out.println(entityId);

※ ハイライトしている行は環境に応じて書き換えてください。

結果

[https://********.crm7.dynamics.com/api/data/v8.0/accounts(********-****-****-****-************)]

問題なく作成できました! Dynamics CRM の画面で確認してみてください。応答 OData-EntityId ヘッダーには、作成したエンティティの URI が含まれています。この URI はエンティティ更新や削除で使います。

エンティティの更新

続いて先ほどのエンティティを更新してみましょう。

final ODataClient client = ODataClientBuilder.createClient();
final CUDRequestFactory factory = client.getCUDRequestFactory();
final URI uri = client.newURIBuilder(SERVICE_ROOT)
        .appendEntitySetSegment("accounts")
        .appendKeySegment(UUID.fromString("********-****-****-****-************"))
        .build();
client.getConfiguration().setUseChuncked(false);
final ClientObjectFactory objectFactory = client.getObjectFactory();
final ClientEntity entity = objectFactory.newEntity(null);
{
    final List properties = entity.getProperties();
    final ClientPrimitiveValue val = objectFactory.newPrimitiveValueBuilder().buildString("(株)ふーばー");
    final ClientProperty prop = objectFactory.newPrimitiveProperty("name", val);
    properties.add(prop);
}
final ODataEntityUpdateRequest request = factory.getEntityUpdateRequest(uri, UpdateType.PATCH, entity);
request.addCustomHeader("Authorization", "Bearer " + ACCESS_TOKEN);
final ODataEntityUpdateResponse response = request.execute();
System.out.println(response.getStatusCode());

※ ハイライトしている行は環境に応じて書き換えてください。

結果

204

見事に更新されました! Dynamics CRM の画面で確認してみてください。更新が成功した場合、状態 204 の応答が返されます。ここでのポイントは、7行目で Chunked Transfer を無効にしていることです。

client.getConfiguration().setUseChuncked(false);

Chunked Transfer で要求すると以下のようなエラーとなってしまうので注意してください。

{
  "error":{
    "code":"","message":"Object reference not set to an instance of an object.","innererror":{
      "message":"Object reference not set to an instance of an object.","type":"System.NullReferenceException","stacktrace":"   at Microsoft.Crm.Extensibility.OData.EdmTypeConverter.ConvertToCrmEntity(EdmEntityObject edmEntity, EntityReference entityReference)\r\n   at Microsoft.Crm.Extensibility.OData.CrmODataServiceDataProvider.UpdateEdmEntity(CrmODataExecutionContext context, String edmEntityName, String entityKeyValue, EdmEntityObject entityObject)\r\n   at Microsoft.Crm.Extensibility.OData.EntityController.PatchEntity(String entityName, String key, EdmEntityObject entityDelta)\r\n   at lambda_method(Closure , Object , Object[] )\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments)\r\n   at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext()"
    }
  }
}

エンティティの削除

最後に先ほどのエンティティを削除してみましょう。

final ODataClient client = ODataClientBuilder.createClient();
final CUDRequestFactory factory = client.getCUDRequestFactory();
final URI uri = client.newURIBuilder(SERVICE_ROOT)
        .appendEntitySetSegment("accounts")
        .appendKeySegment(UUID.fromString("********-****-****-****-************"))
        .build();
final ODataDeleteRequest request = factory.getDeleteRequest(uri);
request.addCustomHeader("Authorization", "Bearer " + ACCESS_TOKEN);
final ODataDeleteResponse response = request.execute();
System.out.println(response.getStatusCode());

※ ハイライトしている行は環境に応じて書き換えてください。

結果

204

綺麗さっぱり削除できました! Dynamics CRM の画面で確認してみてください。削除も同様に、成功した場合、状態 204 の応答が返されます。ちなみに、再度実行すると 404 Not Found となります。

まとめ

今回は Dynamics CRM のエンティティの簡単な CRUD 操作を試みました。冒頭に書いたとおり、世の中には .NET のサンプルが多いですが、OData 4.0 仕様で接続することができますので、クライアントの言語を問いません。Olingo を使うことで簡単に Java から操作することができそうです。

  • 株式会社アークシステムの来訪管理・会議室予約システム BRoomHubs
  • 低コスト・短納期で提供するまるごとおまかせZabbix