in impl/src/main/java/org/apache/myfaces/view/facelets/FaceletViewDeclarationLanguage.java [917:1208]
public void retargetMethodExpressions(FacesContext context, UIComponent topLevelComponent)
{
Assert.notNull(context, "context");
BeanInfo compositeComponentMetadata
= (BeanInfo) topLevelComponent.getAttributes().get(UIComponent.BEANINFO_KEY);
if (compositeComponentMetadata == null)
{
log.severe("Composite component metadata not found for: " + topLevelComponent.getClientId(context));
return;
}
// "...For each attribute that is a MethodExpression..." This means we have to scan
// all attributes with "method-signature" attribute and no "type" attribute
// jakarta.faces.component._ComponentAttributesMap uses BeanInfo.getPropertyDescriptors to
// traverse over it, but here the metadata returned by UIComponent.BEANINFO_KEY is available
// only for composite components.
// That means somewhere we need to create a custom BeanInfo object for composite components
// that will be filled somewhere (theoretically in ViewDeclarationLanguage.getComponentMetadata())
PropertyDescriptor[] propertyDescriptors = compositeComponentMetadata.getPropertyDescriptors();
ELContext elContext = (ELContext) context.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
for (PropertyDescriptor propertyDescriptor : propertyDescriptors)
{
if (propertyDescriptor.getValue("type") != null)
{
// This check is necessary if we have both "type" and "method-signature" set.
// In that case, "method-signature" is ignored
continue;
}
String attributeName = propertyDescriptor.getName();
// <composite:attribute> method-signature attribute is
// ValueExpression that must evaluate to String
ValueExpression methodSignatureExpression
= (ValueExpression) propertyDescriptor.getValue("method-signature");
String methodSignature = null;
if (methodSignatureExpression != null)
{
// Check if the value expression holds a method signature
// Note that it could be null, so in that case we don't have to do anything
methodSignature = methodSignatureExpression.getValue(elContext);
}
String targetAttributeName = null;
ValueExpression targetAttributeNameVE
= (ValueExpression) propertyDescriptor.getValue("targetAttributeName");
if (targetAttributeNameVE != null)
{
targetAttributeName = targetAttributeNameVE.getValue(context.getELContext());
if (targetAttributeName == null)
{
targetAttributeName = attributeName;
}
}
else
{
targetAttributeName = attributeName;
}
boolean isKnownTargetAttributeMethod
= "action".equals(targetAttributeName) || "actionListener".equals(targetAttributeName)
|| "validator".equals(targetAttributeName) || "valueChangeListener".equals(targetAttributeName);
// either the attributeName has to be a knownMethod or there has to be a method-signature
if (isKnownTargetAttributeMethod || methodSignature != null)
{
ValueExpression targetsExpression =
(ValueExpression) propertyDescriptor.getValue("targets");
String targets = null;
// <composite:attribute> targets attribute is
// ValueExpression that must evaluate to String
if (targetsExpression != null)
{
targets = targetsExpression.getValue(elContext);
}
if (targets == null)
{
// "...let the name of the metadata element be the
// evaluated value of the targets attribute..."
targets = attributeName;
}
FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance();
// If the MethodExpression attribute has been already applied, there is no need to
// handle it and it is probably a MethodExpression instance is on attribute map, so the
// inner code will cause a ClassCastException.
if (!mctx.isMethodExpressionAttributeApplied(topLevelComponent, attributeName))
{
ValueExpression attributeNameValueExpression =
(ValueExpression) topLevelComponent.getAttributes().get(attributeName);
if (attributeNameValueExpression == null)
{
// composite:attribute has a default property, so if we can't found on the
// component attribute map, we should get the default as CompositeComponentELResolver
// does.
attributeNameValueExpression = (ValueExpression) propertyDescriptor.getValue("default");
if (attributeNameValueExpression == null)
{
// It is only valid to log an error if the attribute is required
ValueExpression ve = (ValueExpression) propertyDescriptor.getValue("required");
if (ve != null)
{
Object requiredValue = ve.getValue(elContext);
Boolean required;
if (requiredValue instanceof Boolean boolean1)
{
required = boolean1;
}
else
{
required = Boolean.valueOf(requiredValue.toString());
}
if (required != null && required)
{
if (log.isLoggable(Level.SEVERE))
{
log.severe("attributeValueExpression not found under the key \""
+ attributeName
+ "\". Looking for the next attribute");
}
}
}
continue;
}
}
String[] targetsArray = StringUtils.splitShortString(targets, ' ');
String attributeExpressionString = attributeNameValueExpression.getExpressionString();
//Check if the stored valueExpression is a ccRedirection, to handle it properly later.
boolean ccAttrMeRedirection =
attributeNameValueExpression instanceof LocationValueExpression &&
CompositeComponentELUtils.isCompositeComponentAttrsMethodExpression(
attributeNameValueExpression.getExpressionString());
if (isKnownTargetAttributeMethod)
{
// To add support to #{cc.attrs.action}, #{cc.attrs.actionListener}, #{cc.attrs.validator} or
// #{cc.attrs.valueChangeListener} it is necessary to put a MethodExpression or a
// ValueExpression pointing to the associated java method in the component attribute map.
// org.apache.myfaces.view.facelets.tag.composite.RetargetMethodExpressionRule already put
// a ValueExpression, so we only need to put a MethodExpression when a non redirecting
// expression is used (for example when a nested #{cc.attrs.xxx} is used).
if ("action".equals(targetAttributeName))
{
applyActionMethodExpressionEL(context, elContext,
topLevelComponent, attributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
}
else if ("actionListener".equals(targetAttributeName))
{
applyActionListenerMethodExpressionEL(context, elContext,
topLevelComponent, attributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
}
else if ("validator".equals(targetAttributeName))
{
applyValidatorMethodExpressionEL(context, elContext,
topLevelComponent, attributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
}
else if ("valueChangeListener".equals(targetAttributeName))
{
applyValueChangeListenerMethodExpressionEL(context, elContext,
topLevelComponent, attributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
}
UIComponent topLevelComponentBase =
topLevelComponent.getFacet(UIComponent.COMPOSITE_FACET_NAME);
for (String target : targetsArray)
{
UIComponent innerComponent
= ComponentSupport.findComponentChildOrFacetFrom(context, topLevelComponentBase,
target);
if (innerComponent == null)
{
continue;
}
if (isCompositeComponentRetarget(context, innerComponent, targetAttributeName))
{
innerComponent.getAttributes().put(targetAttributeName, attributeNameValueExpression);
mctx.clearMethodExpressionAttribute(innerComponent, targetAttributeName);
retargetMethodExpressions(context, innerComponent);
if (mctx.isUsingPSSOnThisView() && mctx.isMarkInitialState())
{
innerComponent.markInitialState();
}
}
else
{
if ("action".equals(targetAttributeName))
{
applyActionMethodExpressionTarget(context, mctx, elContext,
topLevelComponentBase, innerComponent,
attributeName, targetAttributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
if (mctx.isUsingPSSOnThisView() && mctx.isMarkInitialState())
{
innerComponent.markInitialState();
}
}
else if ("actionListener".equals(targetAttributeName))
{
applyActionListenerMethodExpressionTarget(context, mctx, elContext,
topLevelComponentBase, innerComponent,
attributeName, targetAttributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
if (mctx.isUsingPSSOnThisView() && mctx.isMarkInitialState())
{
innerComponent.markInitialState();
}
}
else if ("validator".equals(targetAttributeName))
{
applyValidatorMethodExpressionTarget(context, mctx, elContext,
topLevelComponentBase, innerComponent,
attributeName, targetAttributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
if (mctx.isUsingPSSOnThisView() && mctx.isMarkInitialState())
{
innerComponent.markInitialState();
}
}
else if ("valueChangeListener".equals(targetAttributeName))
{
applyValueChangeListenerMethodExpressionTarget(context, mctx, elContext,
topLevelComponentBase, innerComponent,
attributeName, targetAttributeName,
attributeExpressionString, attributeNameValueExpression,
ccAttrMeRedirection);
if (mctx.isUsingPSSOnThisView() && mctx.isMarkInitialState())
{
innerComponent.markInitialState();
}
}
}
}
}
else
{
MethodExpression methodExpression = null;
// composite:attribute targets property only has sense for action, actionListener,
// validator or valueChangeListener. This means we have to retarget the method expression
// to the topLevelComponent.
// Since a MethodExpression has no state, we can use it multiple times without problem, so
// first create it here.
methodSignature = methodSignature.trim();
methodExpression = context.getApplication().getExpressionFactory().
createMethodExpression(elContext,
attributeExpressionString,
FaceletsViewDeclarationLanguageUtils.getReturnType(methodSignature),
FaceletsViewDeclarationLanguageUtils.getParameters(methodSignature));
methodExpression = reWrapMethodExpression(methodExpression, attributeNameValueExpression);
applyMethodExpression(context, mctx, topLevelComponent, attributeName,
targetAttributeName, attributeNameValueExpression, methodExpression,
ccAttrMeRedirection, targetsArray);
}
mctx.markMethodExpressionAttribute(topLevelComponent, attributeName);
}
// We need to remove the previous ValueExpression, to prevent some possible
// confusion when the same value is retrieved from the attribute map.
topLevelComponent.setValueExpression(attributeName, null);
}
}
}