Java™ API のベストプラクティス

Adobe Experience Manager(AEM)は、開発時に使用できる多数の Java™ API を公開していいる機能豊富なオープンソースソフトウェアスタックに基づいて構築されています。この記事では、主要な API とそれらを使用するタイミングおよび理由について説明します。

AEM は、4 つの主要な Java™ API セットに基づいて構築されています。

  • Adobe Experience Manager(AEM)

    • 製品の抽象概念(ページ、アセット、ワークフローなど)。
  • Apache Sling Web フレームワーク

    • REST およびリソースベースの抽象概念(リソース、値マップ、HTTP リクエストなど)。
  • JCR(Apache Jackrabbit Oak)

    • データとコンテンツの抽象概念(ノード、プロパティ、セッションなど)。
  • OSGi(Apache Felix)

    • OSGi アプリケーションコンテナの抽象概念(サービスや(OSGi)コンポーネントなど)。

Java™ API 環境設定の「経験則」

一般的なルールとして、次の順序で API/抽象概念を優先します。

  1. AEM
  2. Sling
  3. JCR
  4. OSGi

API が AEM から提供される場合は、API を Sling、JCR および OSGi よりも優先します。AEM が API を提供しない場合は、Sling を JCR や OSGi よりも優先します。

この順序は一般的なルールで、例外が存在します。 このルールから逸脱する理由として容認できるものは、次のとおりです。

  • よく知られた例外(以下で説明します)。

  • 必要な機能が上位レベルの API で使用できない。

  • あまり優先されない API をそれ自体で使用している既存コード(カスタムコードまたは AEM 製品コード )のコンテキストで動作しており、新しい API に移行するためのコストが正当と認められない。

    • 混在させるよりも、低レベルの API を一貫して使用する方が適切です。

AEM API

AEM API では、製品化されたユースケースに固有の抽象概念と機能を提供します。

例:AEM の PageManager API と Page API は、web ページを表す AEM の cq:Page ノードの抽象概念を提供します。

これらのノードは Sling API を介してリソースとして使用でき、JCR API を介してノードとして使用できますが、AEM の API は一般的なユースケースの抽象概念を提供します。AEM API を使用することで、AEM 製品と AEM のカスタマイズおよび拡張機能の間で一貫した動作が保証されます。

com.adobe.* API と com.day.* API の比較

AEM API には、次の Java™ パッケージ(優先度順)で示すように、パッケージ内の優先順位があります。

  1. com.adobe.cq
  2. com.adobe.granite
  3. com.day.cq

com.adobe.cq パッケージでは製品のユースケースをサポートしているのに対して、 com.adobe.granite では、(AEM Assets、Sites などの複数の製品を横断して使用される)ワークフローやタスクなど、複数の製品にまたがるプラットフォームのユースケースをサポートしています。

com.day.cq パッケージには「オリジナル」の API が含まれています。 これらの API は、アドビが Day CQ を買収する前や買収前後に存在していた中核概念および機能に対応しています。これらの API はサポートされており、 com.adobe.cq または com.adobe.granite パッケージに(新しい)代替手段が用意されていない場合を除いて、使用を避けてください。

Content Fragments や Experience Fragments などの新しい抽象概念は、下記の com.day.cq ではなく com.adobe.cq 空間に構築されています。

クエリ API

AEM では複数のクエリ言語をサポートしています。 主な言語は、JCR-SQL2、XPath、AEM Query Builder の 3 つです。

最も重要な関心事は、コードベース全体で一貫性のあるクエリ言語を維持して、理解するうえでの複雑さとコストを軽減することです。

すべてのクエリ言語は事実上同じパフォーマンスプロファイルを持っています。クエリの最終的な実行のために Apache Oak によってクエリ言語が JCR-SQL2 にトランスパイルされ、JCR-SQL2 への変換時間がクエリ時間そのものと比較して無視できるほど短いからです。

推奨される API は AEM Query Builder です。これは最高レベルの抽象概念であり、クエリを作成、実行およびクエリ結果を取得するための堅牢な API を提供しています。次のものが用意されています。

CAUTION
AEM QueryBuilder API は ResourceResolver オブジェクトをリークします。 このリークを軽減するには、次のコード例に従います。

Sling API

Apache Sling は、AEM を支える RESTful web フレームワークです。 Sling は、HTTP リクエストルーティングを提供したり、JCR ノードをリソースとしてモデル化したり、セキュリティコンテキストを提供したりするなど、多くの機能を提供します。

Sling API には、拡張に対応するように構築されているというメリットが付加されています。つまり、多くの場合、Sling API を使用して構築されたアプリケーションの動作は、拡張性のより低い JCR API よりも容易かつ安全に拡張できます。

Sling API の一般的な用途

JCR API

JCR(Java™ Content Repository)2.0 API は、JCR 実装(AEM の場合、Apache Jackrabbit Oak)の仕様の一部です。すべての JCR 実装は、これらの API に準拠し API を実装する必要があります。したがって、これは AEM のコンテンツを操作するための最も低レベルの API になります。

JCR 自体は階層型/ツリーベースの NoSQL データストアで、AEM ではこれをコンテンツリポジトリとして使用します。 JCR には、コンテンツの CRUD からコンテンツのクエリまで、サポートされている様々な API があります。 この堅牢な API にもかかわらず、AEM や Sling の高レベルの抽象概念より優先されることはまれです。

Apache Jackrabbit Oak API よりも JCR API を常に優先します。 JCR API が JCR リポジトリと​ やり取り ​するためのものであるのに対して、Oak API は JCR リポジトリを​ 実装 ​するためのものです。

JCR API に関するよくある誤解

JCR は AEM のコンテンツリポジトリですが、JCR API はコンテンツを操作するための望ましい方法ではありません。 より優れた抽象概念を提供するので、代わりに AEM API(Page、Assets、Tag など)や Sling Resource API を優先します。

CAUTION
AEM アプリケーションで JCR API の Session インターフェイスや Node ノードインターフェイスを幅広く使用することは、コードスメルになります。 代わりに Sling API を使用してください。

JCR API の一般的な用途

OSGi API

OSGi API と上位レベルの API(AEM、Sling および JCR)はほとんど重複せず、OSGi API を使用する必要はめったにありません。使用する場合は、AEM 開発の高度な専門知識が必要です。

OSGi API と Apache Felix API の比較

OSGi では、すべての OSGi コンテナが実装し準拠する必要がある仕様を定義しています。 AEM の OSGi 実装である Apache Felix では、独自の API もいくつか提供しています。

  • OSGi API(org.osgi)を Apache Felix API(org.apache.felix)より優先します。

OSGi API の一般的な用途

ルールの例外

上記の規則に対する一般的な例外を以下に示します。

OSGi API

OSGi コンポーネントプロパティでの定義や読み取りなど、低レベルの OSGi 抽象概念を扱う場合、org.osgi で提供される新しい抽象概念は、上位レベルの Sling の抽象概念よりも優先されます。 競合する Sling の抽象概念は @Deprecated としてマークされておらず、org.osgi の代替案が推奨されています。

また、OSGi 設定ノードの定義では sling:OsgiConfig 形式より cfg.json が優先される点にも注意してください。

AEM Asset API

  • com.adobe.granite.asset.api より com.day.cq.dam.api を優先します。

    • com.day.cq Assets API には、AEM のアセット管理ユースケースに対するより相補的なツールが用意されているのに対して、。
    • Granite Assets API では、低レベルのアセット管理ユースケース(バージョン、関係)をサポートしています。

クエリ API

  • AEM QueryBuilder では、候補、スペルチェック、インデックスヒントといったそれほど一般的でない機能など、特定のクエリ関数をサポートしていません。これらの機能を使用してクエリを実行する場合は、JCR-SQL2 をお勧めします。

Sling サーブレットの登録 sling-servlet-registration

Sling フィルター登録 sling-filter-registration

有用なコードスニペット

ここで紹介した API を使用する一般的なユースケースのベストプラクティスの例を示す有用な Java™ コードスニペットを以下に列挙します。また、これらのスニペットでは、優先度の低い API からより優先度の高い API に移行する方法も示しています。

JCR Session から Sling ResourceResolver へ

Sling ResourceResolver の自動終了

AEM 6.2 以降、SlingResourceResolverAutoClosabletry-with-resources 文にあります。この構文を使用すると、resourceResolver .close() への明示的な呼び出しは不要です。

@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);

try (ResourceResolver resourceResolver = rrf.getResourceResolver(authInfo)) {
    // Do work with the resourceResolver
} catch (LoginException e) { .. }

Sling ResourceResolver の手動終了

上記の方法で自動終了できない場合、ResourceResolvers は finally ブロックで手動終了する必要があります。

@Reference
ResourceResolverFactory rrf;
...
Map<String, Object> authInfo = new HashMap<String, Object>();
authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, jcrSession);

ResourceResolver resourceResolver = null;

try {
    resourceResolver = rrf.getResourceResolver(authInfo);
    // Do work with the resourceResolver
} catch (LoginException e) {
   ...
} finally {
    if (resourceResolver != null) { resourceResolver.close(); }
}

JCR Path から Sling Resource へ

Resource resource = ResourceResolver.getResource("/path/to/the/resource");

JCR Node から Sling Resource へ

Resource resource = resourceResolver.getResource(node.getPath());

Sling Resource から AEM Asset へ

推奨アプローチ

この DamUtil.resolveToAsset(..) 関数は、必要に応じてツリーを上に移動して、dam:Asset 下のあらゆるリソースを Asset オブジェクトに解決します。

Asset asset = DamUtil.resolveToAsset(resource);

代替アプローチ

リソースを Asset に適合させるには、リソース自体が dam:Asset ノードである必要があります。

Asset asset = resource.adaptTo(Asset.class);

Sling Resource から AEM Page へ

推奨アプローチ

pageManager.getContainingPage(..) は、必要に応じてツリーを上に移動して、cq:Page 下のあらゆるリソースを Page オブジェクトに解決します。

PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page page = pageManager.getContainingPage(resource);
Page page2 = pageManager.getContainingPage("/content/path/to/page/jcr:content/or/component");

代替アプローチ alternative-approach-1

リソースを Page に適合させるには、リソース自体が cq:Page ノードである必要があります。

Page page = resource.adaptTo(Page.class);

AEM Page のプロパティの読み取り

Page オブジェクトのゲッター(getTitle()getDescription() など)を使用すると、よく知られているプロパティを取得でき、page.getProperties() を使用すると、その他のプロパティを取得するための [cq:Page]/jcr:content ValueMap を取得できます。

Page page = resource.adaptTo(Page.class);
String title = page.getTitle();
Calendar value = page.getProperties().get("cq:lastModified", Calendar.getInstance());

AEM Asset のメタデータプロパティの読み取り

Asset API には、[dam:Asset]/jcr:content/metadata ノードからプロパティを読み取るための便利なメソッドが用意されています。これは ValueMap ではなく、2 番目のパラメーター(デフォルト値、自動タイプキャスト)はサポートされていません。

Asset asset = resource.adaptTo(Asset.class);
String title = asset.getMetadataValue("dc:title");
Calendar lastModified = (Calendar) asset.getMetadata("cq:lastModified");

Sling Resource のプロパティの読み取り read-sling-resource-properties

AEM API(Page、Asset)から直接アクセスできない場所(プロパティまたは相対リソース)にプロパティが格納されている場合は、Sling Resource と ValueMap を使用してデータを取得できます。

ValueMap properties = resource.getValueMap();
String value = properties.get("jcr:title", "Default title");
String relativeResourceValue = properties.get("relative/propertyName", "Default value");

この場合、目的のプロパティまたはサブリソースを効率的に見つけるために、AEM オブジェクトを Sling Resource に変換する必要がある可能性があります。

AEM Page から Sling Resource へ

Resource resource = page.adaptTo(Resource.class);

AEM Asset から Sling Resource へ

Resource resource = asset.adaptTo(Resource.class);

Sling の ModifiableValueMap を使用したプロパティの書き込み

Sling の ModifiableValueMap を使用すると、ノードにプロパティを書き込むことができます。これは、直接ノードにのみ書き込むことができます(相対的なプロパティパスはサポートされていません)。

なお、.adaptTo(ModifiableValueMap.class) への呼び出しにはリソースへの書き込み権限が必要であり、権限がない場合は null が返されます。

ModifiableValueMap properties = resource.adaptTo(ModifiableValueMap.class);

properties.put("newPropertyName", "new value");
properties.put("propertyNameToUpdate", "updated value");
properties.remove("propertyToRemove");

resource.getResourceResolver().commit();

AEM Page オブジェクトの作成

AEM でページを適切に定義および初期化するために必要なページテンプレートを使用するので、必ず PageManager を使用してページを作成します。

String templatePath = "/conf/my-app/settings/wcm/templates/content-page";
boolean autoSave = true;

PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
pageManager.create("/content/parent/path", "my-new-page", templatePath, "My New Page Title", autoSave);

if (!autoSave) { resourceResolver.commit(); }

Sling Resource オブジェクトの作成

ResourceResolver では、リソースを作成するための基本的な操作をサポートしています。上位レベルの抽象概念(AEM Page、Asset、Tag など)を作成する場合は、それぞれのマネージャーに用意されているメソッドを使用します。

resourceResolver.create(parentResource, "my-node-name", new ImmutableMap.Builder<String, Object>()
           .put("jcr:primaryType", "nt:unstructured")
           .put("jcr:title", "Hello world")
           .put("propertyName", "Other initial properties")
           .build());

resourceResolver.commit();

Sling Resource オブジェクトの削除

ResourceResolver では、リソースの削除をサポートしています。上位レベルの抽象概念(AEM Page、Asset、Tag など)を作成する場合は、それぞれのマネージャーに用意されているメソッドを使用します。

resourceResolver.delete(resource);

resourceResolver.commit();
recommendation-more-help
c92bdb17-1e49-4e76-bcdd-89e4f85f45e6