次のメソッドhttp://blog.kennardconsulting.com/2010/10/safely-manipulating-component-tree-with.htmlを使用して、PreRenderViewEvent にコンポーネントを動的に追加しています。
コンポーネントの追加部分では問題なく動作しますが、ValueExpression を動的にインスタンス化しようとすると問題が発生します。
具体的には、動的に渡されたパラメーターを使用して動的な ValueExpression を偽造しようとすると問題が発生します。
例を説明してみましょう...
最上位では、タグ コンポーネント (タグ ファイルに記述されたコンポーネントであり、コンポジットでもカスタム コンポーネントでもありません。
<my:topComponent param=#{toto}"/>
my:topComponent では、入れ子になったコンポーネントに param を渡します。
<my:nestedComponent param2=#{param}/>
このnestedComponentはカスタムコンポーネント(私の場合、primefaces Datatableから派生したコンポーネント)を使用しており、別のパラメータとしてparam2を渡しています...
<my:customComponent finalParam=#{param2}/>
customComponent では、PreRenderViewEvent にいくつかの子コンポーネントを動的に追加し、customComponent にいくつかの ValueExpression を設定します。
これらの式の一部は、finalParam を使用します。そこで、 finalParam 値をアンラップしてから、新しい ValueExpression を作成します。
String varName = getValueExpression("finalParam").getExpressionString().replace("#{", "").replace("}", "");
次に、このヘルパー関数を使用して動的な値式をインスタンス化します。
public static ValueExpression createValueExpression(String expression, Class clazz) {
FacesContext fc = FacesContext.getCurrentInstance();
ELContext elContext = fc.getELContext();
ExpressionFactory expFactory = fc.getApplication().getExpressionFactory();
ValueExpression ret = expFactory.createValueExpression(elContext, expression, clazz);
return ret;
}
例 :
ValueExpression dynExpression = JSFUtils.createValueExpression("#{" + varName + ".code" + "}"), Object.class);
この例では、値式は「#{param2.code}」です。
次に、この valueExpression をコンポーネントに設定できます。
this.setValueExpression("rowKey", dynExpression);
このコードはすべて、カスタム コンポーネント クラスにあります。基本クラスのレンダラーを使用します。
ただし、プログラムによってインスタンス化された ValueExpression は、レンダリング中に適切に評価されません。たとえば、primefaces データテーブル レンダラーが rowKey を計算しようとすると、param2 が不明であるように見えるため、#{param2.code} は「null」と評価されます。
これを修正するにはどうすればよいですか? デバッグ中に、getValueExpression("finalParam") には VariableMapper が設定されているのに、dynExpression には何も設定されていない (null 値) ことに気付きました。
うまくいけば、この VariableMapper を使用して param2 を param に変換します。
VariableMapper(s) チェーンが保持されるように動的式をインスタンス化するにはどうすればよいですか? 質問は FunctionMapper についても同じです。
前もって感謝します。
更新 Richard Kennard の返信に同意します。同じバグのようです。
修正を何年も待つことができないので、次のクラッジを使用して変数を再帰的に解決します。MyFaces 2.1.9 / CODI 1.0.5 / OWB 1.1.6 / Tomcat 7 スタックの単純なケースで機能します。
public static String getValueExpressionExpression(ValueExpression valueExpression) {
return valueExpression.getExpressionString().replace("#{", "").replace("}", "");
}
public static String getMappedValueExpression(ValueExpression valueExpression) {
ContextAwareTagValueExpression ctxAware = (ContextAwareTagValueExpression)valueExpression;
if(ctxAware != null) {
return getMappedValueExpression((WrappedValueExpression)ctxAware.getWrapped());
}
return getValueExpressionExpression(valueExpression);
}
public static String getMappedValueExpression(WrappedValueExpression wrappedExpression) {
String exprString = wrappedExpression.getExpressionString().replace("#{", "").replace("}", "");
String ret = exprString;
try {
Field valueExpression = WrappedValueExpression.class.getDeclaredField("valueExpression");
valueExpression.setAccessible(true);
ValueExpressionImpl vei = (ValueExpressionImpl) valueExpression.get(wrappedExpression);
Field varMapper = ValueExpressionImpl.class.getDeclaredField("varMapper");
varMapper.setAccessible(true);
VariableMapperImpl vmi = (VariableMapperImpl) varMapper.get(vei);
if(vmi != null) {
String[] components = exprString.split("\\.");
components[0] = getMappedValueExpression(vmi.resolveVariable(components[0]));
ret = "";
for(int i = 0 ; i < components.length ; i++) {
if(i != 0) {
ret += ".";
}
ret += components[i];
}
}
} catch (Exception ex) {
logger.error("Exception lors du mapping de l'expression EL " + exprString, ex);
} finally {
return ret;
}
}
MyFaces または Mojarra にこの回避策のよりクリーンなバージョンがあるとよいでしょう...