AEM開発者ブログ by YAMATO

アドビ社のデリバリーパートナー大和株式会社のAEM開発者ブログです。

Sling Modelsに出てくるAnnotationって何!? Part.02

大和株式会社の狩野です。

前回に引き続いて、Sling Modelsにおける重要な概念であるAnnotationについて解説いたします。
今回の記事では、 @Self , @ChildResource , @RequestAttribute , @OSGiService について説明します。

前回の記事:「Sling Modelsに出てくるAnnotationって何!? Part.01」

Annotationという言葉の意味、概要

前回の記事 でも同じことを書きましたが、この記事から読んでいる場合もあるので、こちらでも書いておきます。

辞書的にいうと、「Annotation」という単語には「注釈」という意味があります。
転じて、プログラミング的には、変数やメソッドに注釈を付けて意味をもたせるという役割があります。

コードで説明すると、以下のようになります。

@Component(
    service = Servlet.class,
    property = {
            "sling.servlet.methods=post",
            "sling.servlet.paths=" + "/bin/any/path",
    }
)
public class TutorialClass extends SlingAllMethodsServlet {
    // any code
}

TutorialClass@Component というAnnotationを付与することによって、このクラスはComponentとして扱われます。
丸かっこ内に書かれた記述によって、この TutorialClass はServletであり、Postメソッドで /bin/any/path というパスに対してリクエストを受けたときに動作しますという意味です。

更に、以下のようなこともできます。

@Model(
    adaptables = {SlingHttpServletRequest.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TutorialClass2 {
    @ValueMapValue
    private String rootPath;
}

2つ目の例では、 @ValueMapValue Annotationにより、ダイアログに入力した値をこれだけの記述で取得することができます。
一旦 properties などで取ってきて、そこから properties.get() とかする必要すら無く、記述量も少なく済むので是非覚えましょう。

Sling Modelsで使われる各Annotationについての説明

@Self

org.apache.sling.models.annotations.injectorspecific.Self

@Self Annotationは @SlingObject@ScriptVariable などと同じく、付与したフィールドにオブジェクトを挿入するAnnotationです。
Selfという名前が示すように、当該Sling Models自身のadaptableであるか、 adaptTo() メソッドで適合可能なオブジェクトを指定可能です。 例に挙げた@SlingObject@ScriptVariableと異なり、あらかじめ規定されたクラスだけでなく、自作のJavaクラスも使用可能です。

Javaコード

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;

@Model(adaptables = SlingHttpServletRequest.class)
public class MyRequestAdapter {
    @SlingObject
    private SlingHttpServletRequest request;

    public String getResolver() {
        return request.getResourceResolver();
    }
}
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.Self;

@Model(adaptables = SlingHttpServletRequest.class)
public class TutorialClassForSelf {
    @Self
    private MyRequestAdapter requestAdapter;

    public String fetchResourceFromPath(String path) {
        return requestAdapter.getResolver().getResource(path);
    }
}

上記のコードでは、自作の MyRequestAdapter というクラスを他のSling Modelsで @Self Annotationを用いて呼び出しています。
この例では、 MyRequestAdapter クラスと TutorialClasForSelf クラス共に adaptables = SlingHttpServletRequest.class であり@Selfの利用が可能になっています。

また下記の例では、コアコンポーネントを継承したカスタムモデルにおいて@Self@Viaを利用して継承元のモデルを参照し、コアモデルのメソッドを利用しています。

...
import com.adobe.cq.wcm.core.components.models.Title;
import org.apache.sling.models.annotations.Via;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.via.ResourceSuperType;
...

@Model(adaptables = SlingHttpServletRequest.class,
               adaptors = Title.class
               resourceType = MyTitle.RESOURCE_TYPE)
public class MyTitle implements Title {
    protected static final String RESOURCE_TYPE = "yamato/components/mytitle";

    @Self @Via(type = ResourceSuperType.class)
    private Title coreTitle;

    @Override
    public String getText() {
        return coreTitle.getText();
    }
    ....
}

どんな時に使うか

前述したように、当該Sling Models自身のadaptableであるか adaptTo() メソッドで適合可能なオブジェクトを指定可能ですので、それら適合するクラスのメソッドやフィールドを利用したい場合に使います。

@ChildResource

org.apache.sling.models.annotations.injectorspecific.ChildResource

@ChildResource Annotationは、自分自身の子リソースを取得するためのAnnotationです。
以下、コード及び実際のノード構造を用いた例をあげます。

Javaコード

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;

import org.apache.sling.models.annotations.injectorspecific.ChildResource;
import org.apache.sling.models.annotations.Model;

@Model(adaptables = SlingHttpServletRequest.class)
public class TutorialClassForChildResource {
    @ChildResource(name = "childresources")
    Resource items;
}

ノード構造

componentnode
    +-- childresources
        +-- item0
        +-- item1
        +-- item2

結果

この時、変数 items には

+-- childresources
    +-- item0
    +-- item1
    +-- item2

ノード情報を格納した Resource クラスのオブジェクトが格納されます。
このitemsは配列やListではありませんが、 getChildren() などで子のResourceを取得できます。

どんな時に使うか

ダイアログのmultifieldで保存したノード構造を扱う時に便利です。

@RequestAttribute

org.apache.sling.models.annotations.injectorspecific.RequestAttribute

@RequestAttribute Annotationは、HTMLに記述した文字列をSling Modelsクラス内で扱うためのAnnotationです。以下、コードを用いた例を挙げます。

Javaコード

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;

import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
import org.apache.sling.models.annotations.Model;

@Model(adaptables = SlingHttpServletRequest.class)
public class TutorialClassForRequestAttribute {
    @RequestAttribute(name = "fromHTL1")
    private String param;

    @RequestAttribute
    private String fromHTL2;

    public String getConcatenatedValue() {
        return param + fromHTL2;
    }
}

htmlコード

<sly data-sly-use.comp="${ 'path.to.TutorialClassForRequestAttribute' @ fromHTL1='Adobe', fromHTL2='ExperienceManager' }"></sly>
<div>${comp.concatenatedValue}</div>

このように記述すると、divタグの中には AdobeExperienceManagerという文字列が描画されます。

こんな時に使う

Javaコード上でHTL上に出力された文字列を扱いたい時に使えます。 例えば、同じSling Modelsを複数のコンポーネントから呼び出した時に、どのコンポーネントから呼び出されたかをJava上で区別したい時などがあります。

@OSGiService

org.apache.sling.models.annotations.injectorspecific.OSGiService

@OSGiService は、OSGiServiceを付与したフィールドに挿入するAnnotationです。以下にコードを用いた例を挙げます。
以下のコードは、ImageコンポーネントやTitleコンポーネントで呼ばれる可能性があるSling Modelsで、異なるコンポーネントから「リンク」というプロパティを取得することができるコードです。

この例では、 @OSGiService Annotationは、Titleコンポーネントのモデルを取得するための ModelFactory サービスを取得するために使います。

Javaコード

import org.apache.commons.lang3.StringUtils;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;

import org.apache.sling.models.annotations.injectorspecific.RequestAttribute;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.factory.ModelFactory;

import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.wcm.core.components.models.Image;
import com.adobe.cq.wcm.core.components.models.Title;

@Model(
    adaptables = SlingHttpServletRequest.class,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class TutorialClassForOSGiService {
    @OSGiService
    private ModelFactory modelFactory;

    @RequestAttribute
    private String componentName;

    @Self
    private SlingHttpServletRequest request;

    public String getLink() {
        Title title = modelFactory.getModelFromWrappedRequest(request, request.getResource(), Title.class);
        if (StringUtils.equals(componentName, "title")) {
            return title.getLink().getURL();
        }
        Image image = modelFactory.getModelFromWrappedRequest(request, request.getResource(), Image.class);
        if (StringUtils.equals(componentName, "image")) {
            return image.getLink();
        }
    }
}

こんな時に使う

AEMに登録されている他のサービスを参照する時に使います。 今回の例では、 ModelFactory というサービスを参照して呼び出しています。

今回の @OSGiService Annotation の例ではSling Modelsからサービスを参照していますが、 Sling Modelsからではなく、サービスから他のサービスを参照する場合は @Reference Annotationを使います。

最後に

以上となります。

これらのAnnotationの具体的な使い方を網羅的に書いてある記事があまり見つからなかったので、実務で用いたコードなどを参考に執筆いたしました。