AEM開発者ブログ by YAMATO

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

AEM標準APIのNPE対策

AEM Developerの皆様お疲れさまです。大和株式会社の狩野です。

Qiitaのアドベントカレンダーに便乗して、始めるのちょっと遅いですが大和開発者ブログでも同じような趣旨で、本日から記事投稿イベントを開催します!

1日目の内容はタイトルにもある通り、AEM標準APIのNPE対策です。 NPE = NullPointerExceptionです。以下の記事内ではNPEと書いたらNullPointerExceptionだと思ってください。

AEM標準APIが返却するnullに困ったということは、一度はあると思います。それをどう対策するかについて書きたいと思います。

静的解析ツールの導入

こちらはAEMとは関係ない内容にはなるのですが、静的解析ツールの導入を行います。 静的解析ツールが無いと、どの部分にNull対策をすればよいかすらわかりません。

Visual Studio Codeを使ったAEMの開発環境を構築するでVisual Studio Codeの設定を終えているのを想定して、こちらのVS codeでの設定について書きます。 他のエディタやIDEを使う場合についても検索すればすぐに出てくると思います。

  1. SonarLintをインストール
  2. settings.jsonを開く(Ctrl + Shift + P → Open Settings と入力)
  3. 以下の設定を挿入
"sonarlint.ls.javaHome":"C:\\Program Files\\Java\\jdk1.8.0_201"

そうすると、以下のようにNPEが出そうな箇所に警告が出ます。

f:id:yamato_tech:20201209131111p:plain
Sonarlintによる警告

実際にAEMのAPIがNPEを返す場面

@ScriptVariable
private Resource resource;

private String fetchPropertiesValue() {
    return resource.getValueMap().get("propName", "");
}

上記のコードは以下の2つの部分がnullになる可能性があります

  • resource
  • resource.getValueMap()の返り値

誰でも思いつくNPE回避の方法

これを簡単にnullチェックするとしたら、以下のようなコードになります。

@ScriptVariable
private Resource resource;

private String fetchPropertiesValue() {
    if (resource == null) return "nullだよ";
    if (resource.getValueMap() == null) return "nullだよ";
    return resource.getValueMap().get("propName", "空だよ");
}

一見、ギリギリ悪くないようにも見えますが、以下のようなケースに変更するとなるとどうなるでしょう。

@ScriptVariable
private Resource resource;

private String fetchChildPropertiesValue() {
    return resource.getChild(childNodeName).getValueMap().get("propName", "");
}

こうなってくると、さっきのコードに加えて、 getChild() の部分にもif文によるnullチェックを挟まないといけなくなり、コードが煩雑になります。

Optionalという解決策

これを解決するために、Optionalというクラスを使います。

java.util.Optional

@ScriptVariable
private Resource resource;

private String fetchChildPropertiesValue() {
    return Optional.ofNullable(resource)
        .map(res -> res.getChild("nodeName"))
        .map(Resource::getValueMap)
        .map(prop -> prop.get("propName", "空だよ"))
        .orElse("nullだよ");
}

Optional.ofNullable() には、nullになるかもしれない値を引数として渡します。 nullだった場合、空のOptionalを返します。 nullではない場合、引数の値が格納されたOptionalを返します。

Optional.map() には、Optionalの中身に対して行いたい処理をラムダ式等で渡します。 処理の結果がnullだった場合、空のOptionalを返します。 処理の結果がnullではない場合、処理の結果が格納されたOptionalを返します。

Optional.orElse() には、返したいデフォルト値を渡します。 Optionalの中身が空だった場合、デフォルト値を返します。 Optionalの中身が空ではない場合、Optionalの中身を返します。

よって、 resource.getChild()resource.getValueMap() メソッドでNullPointerExceptionが起きることは無くなりました。

NPE回避に使えるAEM標準API

上記の内容だけなら、あんまりAEM関係ない内容になってしまうので、ここからはAEM特有のオブジェクトのNullPointerException回避に使えるAPIをいくつか紹介します。

NonExistingResource

org.apache.sling.api.resource.NonExistingResource

以下のように子のResouceを取得したいが、子のResourceが存在しない場合 Resource.getChild() はnullを返すので困るという場合は以下のようにすることができる。

Resource childRes = Optional.ofNullable(resource.getChild(nodeName))
    .orElse(new NonExistingResource(resourceResolver, resource.getPath() + "/nodeName"));

こうすると、 resource.getChild() の返り値がnullだった場合、NonExistingResourceのインスタンスが返されるので、続く処理でNullPointerExceptionを防ぐことができる。

ValueMap.EMPTY

org.apache.sling.api.resource.ValueMap#EMPTY

任意の場所にあるResourceからValueMapを生成したいが、Resourceがnullだった場合に空のValueMapを返したいが、やっぱり Resource.getValueMap() はnullを返すので困るという場合は以下のようにすることができる。

ValueMap properties = Optional.ofNullable(resource.getValueMap())
    .orElse(ValueMap.EMPTY);

最後に

以上となります。明日の内容も私が執筆します。内容は「Unit Test内でJSONからResourceを作り出す」です。