Wicket で、ある特定の状況で ページ表示時に Exception が発生する事象に遭遇しました。
エラーメッセージを確認したところ、以下のメッセージが出力されていました。

java.lang.IllegalArgumentException: A child with id '@@@@@@' already exists 

調べたところ、Wicket で 同一 Id の Component を 2度、add すると発生するエラーのようでした。


エラーが発生した Wicket の Version

Wicket 1.5.2


MarkupContainer#add(final Component… children) の動作について

wicket/MarkupContainer.java at master · apache/wicket
add メソッドは以下の実装になっています。

  • MarkupContainer#add(final Component… children) 1

    /**
     * Adds the child component(s) to this container.
     * 
     * @param children
     *            The child(ren) to add.
     * @throws IllegalArgumentException
     *             Thrown if a child with the same id is replaced by the add operation.
     * @return This
     */
    public MarkupContainer add(final Component... children)
    {
        for (Component child : children)
        {
            Args.notNull(child, "child");

            if (this == child)
            {
                throw new IllegalArgumentException(
                    exceptionMessage("Trying to add this component to itself."));
            }

            MarkupContainer parent = getParent();
            while (parent != null)
            {
                if (child == parent)
                {
                    String msg = "You can not add a component's parent as child to the component (loop): Component: " +
                        this.toString(false) + "; parent == child: " + parent.toString(false);

                    if (child instanceof Border.BorderBodyContainer)
                    {
                        msg += ". Please consider using Border.addToBorder(new " +
                            Classes.simpleName(this.getClass()) + "(\"" + this.getId() +
                            "\", ...) instead of add(...)";
                    }

                    throw new WicketRuntimeException(msg);
                }

                parent = parent.getParent();
            }

            checkHierarchyChange(child);

            if (log.isDebugEnabled())
            {
                log.debug("Add " + child.getId() + " to " + this);
            }

            // Add the child to my children
            Component previousChild = children_put(child);
            if (previousChild != null && previousChild != child)
            {
                throw new IllegalArgumentException(
                    exceptionMessage("A child '" + previousChild.getClass().getSimpleName() +
                        "' with id '" + child.getId() + "' already exists"));
            }

            addedComponent(child);

        }
        return this;
    }

対象の Exception は以下の箇所で発生していて、既に Component 追加済の Id に再度追加すると、Exception が発生することがわかります。

        // Add the child to my children
        Component previousChild = children_put(child);
        if (previousChild != null && previousChild != child)
        {
            throw new IllegalArgumentException(
                exceptionMessage("A child '" + previousChild.getClass().getSimpleName() +
                    "' with id '" + child.getId() + "' already exists"));
        }

RuntimeConfigurationType.DEVELOPMENT、であっても、RuntimeConfigurationType.DEPLOYMENT でも Exception は発生します。
Page 継承関係、Panel 等が複雑に絡みあうと、どこで add しているのがわからなくなり、継承先クラスや、使用している Panel クラス等でも重複していないことを確認しないといけないという状況になるのは、なかなか大変なので、add したとしても上手く動くようにしたいです。
MarkupContainer の実装を確認すると幾つか使えそうなAPIがあったので、以下に記載します。1


MarkupContainer#addOrReplace(final Component… children) を使う

MarkupContainer#addOrReplace使用すると、指定した Id に対応するコンポーネントが存在しない場合は、追加、存在する場合は、置き換えます。
MarkupContainer#replaceいう API もあって、こちらは、指定した Id に対応するコンポーネントが存在する場合は、置き換え、存在しない場合は、例外をスローする動作となります。


MarkupContainer#get(String path) と、 MarkupContainer#add(final Component… children) の組み合わせ

MarkupContainer#get Id を指定すると、対応するコンポーネントが存在するかどうか(追加されているかどうか)を確認することができます。
この API と、MarkupContainer#add組み合わせで、指定した Id に対応するコンポーネントが存在しない場合は、追加、存在する場合は、何もしない、が実現できます。


その他 queue と、dequeue メソッド

Wicket 7 から、MarkupContainer に、queue メソッド、dequeue メソッドが追加されました。
これは、階層構造を辿って Component を追加できる機能になります。
以下、Qiita の記事に詳しい使用方法が記載されています。
Wicket7の Component queueing を試してみた - Qiita

2度 add したいユースケースは実はほとんどない気がします。実装的に if 分岐をなくしたい、または苦肉の索で、直しにくい複雑なロジックを回避して、後処理で Component を書き換えたりする場合に登場するのかもしれません。

以上です。


  1. Exception が発生した時は、2度 addしない ようにして対処はしました。 

コメント