public void retargetMethodExpressions()

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);
            }
        }
    }