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に定数一覧が定義されており、その中から探して見つけました。
マニュアルに載ってないんですよねコレ…。