読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり、所属会社の見解ではありません。 GitHub: takahashikzn

Struts2のデフォルトのBeanを置き換える

Struts2


OGNL評価中に発生した例外を握りつぶさずに上位へスローするかどうかの制御は、struts.xmlにて

<constant name="struts.el.throwExceptionOnFailure" value="true" />

を指定することで行います。
で、当然ながらすべてのOGNL例外を握りつぶさずスローするにようになります。


しかし、とある事情により、いくつかの例外は無視して欲しいと言うニーズがあったため、
特定の例外に関しては、今まで通り握りつぶすようにする方法を調べてみました。

例外を握りつぶしているのはどこ?

例外を握りつぶしているのはcom.opensymphony.xwork2.ognl.OgnlValueStackです。


以下に一部抜粋。(170行目付近)

/**
 * @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean)
 */
public void setValue(String expr, Object value, boolean throwExceptionOnFailure) {
    setValue(expr, value, throwExceptionOnFailure, true);
}

private void setValue(String expr, Object value, boolean throwExceptionOnFailure, boolean evalExpression) {
    Map<String, Object> context = getContext();
    try {
        trySetValue(expr, value, throwExceptionOnFailure, context, evalExpression);
    } catch (OgnlException e) {
        handleOgnlException(expr, value, throwExceptionOnFailure, e);
    } catch (RuntimeException re) { //XW-281
        handleRuntimeException(expr, value, throwExceptionOnFailure, re);
    } finally {
        cleanUpContext(context);
    }
}

...


private void handleOgnlException(String expr, Object value, boolean throwExceptionOnFailure, OgnlException e) {
    String msg = "Error setting expression '" + expr + "' with value '" + value + "'";
    if (LOG.isWarnEnabled()) {
        LOG.warn(msg, e);
    }
    if (throwExceptionOnFailure) {
        throw new XWorkException(msg, e);
    }
}


OgnlExceptionを握りつぶしている処理の実態はhandleOgnlExceptionで、このメソッドを呼んでいる箇所はOgnlValueStack内に3つあります。

方針

OgnlValueStackのカスタム実装を作り、そこでhandleOgnlExceptionを使っているpublicメソッドを全部オーバーライドすることにします。
以下のような感じ。

public class Struts2ValueStack
    extends ValueStackWrapper {

    private static final long serialVersionUID = -6799915730021608347L;

    /** 握りつぶすOGNL例外のパターンを指定 */
    private final Pattern ignorePattern;

    public Struts2ValueStack(final ValueStack stack, final Pattern ignorePattern) {
        super(stack);
        this.ignorePattern = ignorePattern;
    }

    @Override
    public void setParameter(final String expr, final Object value) {
        try {
            super.setParameter(expr, value);
        } catch (final RuntimeException e) {
            this.handleRuntimeException(e);
        }
    }

    @Override
    public void setValue(final String expr, final Object value, final boolean throwExceptionOnFailure) {
        try {
            super.setValue(expr, value, throwExceptionOnFailure);
        } catch (final RuntimeException e) {
            this.handleRuntimeException(e);
        }
    }

    @Override
    public Object findValue(final String expr, final Class asType, final boolean throwExceptionOnFailure) {
        try {
            return super.findValue(expr, asType, throwExceptionOnFailure);
        } catch (final RuntimeException e) {
            this.handleRuntimeException(e);
            return null;
        }
    }

    @Override
    public Object findValue(final String expr, final boolean throwExceptionOnFailure) {
        try {
            return super.findValue(expr, throwExceptionOnFailure);
        } catch (final RuntimeException e) {
            this.handleRuntimeException(e);
            return null;
        }
    }

    /** 例外がOGNL例外をラップしていないか調べる */
    private OgnlException findOgnlException(final Exception e) {

        if (e == null) {
            return null;
        } else if (e.getCause() == e) {
            return null;
        } else if (e instanceof OgnlException) {
            return (OgnlException) e;
        } else {
            return this.findOgnlException((Exception) e.getCause());
        }
    }

    private void handleRuntimeException(final RuntimeException e) {

        final OgnlException ognlException = this.findOgnlException(e);

        // OGNL例外をラップしてなければ、無視してはダメなのでスロー
        if (ognlException == null) {
            throw e;
        }

        // 無視しないOGNL例外ならスローする
        if (!this.ignorePattern.matcher(ognlException.getMessage()).matches()) {
            throw e;
        }
    }
}

OgnlValueStackのインスタンス生成をすり替える

さて、上で作成したクラスに差し替えるためには、OgnlValueStackをnewしている処理をすり替えなければなりません。
どうせファクトリーがあるんだろ?と思って調べたら案の定ありました。名前はそのまんまOgnlValueStackFactory。


struts2でOgnlValueStackFactoryのFQCNを指定している設定があるはずなので、それを調べたところ、
struts2.jarの中に入っているstruts-default.xmlで、以下の通り指定されていることが判明。(71行目)

<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />


コレを置き換えれば良いということです。という訳でstruts.xml内で以下のように指定してみました。

<constant name="struts.el.throwExceptionOnFailure" value="true" />
<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="foo.bar.Struts2ValueStackFactory" />

しかし…この方法だとエラーで起動せず。
どうやら既にstruts-default.xmlでBeanを定義済みの場合、それを後からオーバーライドすることが出来ないようです。

ここからが本題


で、やっと表題の内容にたどり着くわけです。前置きが長くてスイマセン。
結論から言うと、以下の手順でOKっぽい。

<constant name="struts.el.throwExceptionOnFailure" value="true" />
<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="foobar-valueStack" class="foo.bar.Struts2ValueStackFactory" />
<constant name="struts.valueStackFactory" value="foobar-valueStack" />


要するに、

  • OgnlValueStackFactoryのサブクラスを別名で定義
  • Struts2の定数を指定して、インスタンスを差し替える

とやればOKのようです。


で、『struts.valueStackFactoryなんて定数はどこで見つけてきたのじゃ?』なんですが、
org.apache.struts2.StrutsConstantsに定数一覧が定義されており、その中から探して見つけました。
マニュアルに載ってないんですよねコレ…。