Search code examples
jsfactionlistenercomposite-componentactionevent

JSF - Getting bean and action name from ActionEvent from composite component


I've got a composite component to dynamically call a bean action:

<composite:interface>
    <composite:attribute name="actionMethod" method-signature="java.lang.String action()"/>
</composite:interface>

<composite:implementation>

    <p:menubar autoDisplay="false" styleClass="subMenu">
        <p:menuitem>
             <h:commandButton action="#{cc.attrs.actionMethod}" value="#{bundle.CreateSaveLink}" styleClass="smallButton button buttonSave"/>
        </p:menuitem>
    </p:menubar>

</composite:implementation>

And I've also defined an ActionListener class to implement security:

public class SecurityActionListener extends ActionListenerImpl implements ActionListener {


    private static final Logger log = Logger.getLogger(SecurityActionListener.class);
    private String isCasEnabled;

    public SecurityActionListener() {
        isCasEnabled = PropertyUtility.isCasEnabled();
    }


    @SuppressWarnings("unused")
    @Override
    public void processAction(final ActionEvent event) {

        if(!isCasEnabled.equals("true")) {
            super.processAction(event);
            return;
        }


        final FacesContext context = FacesContext.getCurrentInstance();
        final Application application = context.getApplication();
        final ConfigurableNavigationHandler navHandler = (ConfigurableNavigationHandler) application.getNavigationHandler();

        // Action stuff
        final UIComponent source = event.getComponent();
        final ActionSource actionSource = (ActionSource) source;
        MethodBinding binding;

        binding = actionSource.getAction();
        final String expr = binding.getExpressionString();
        if (!expr.startsWith("#")) {
            super.processAction(event);
            return;
        }

        final int idx = expr.indexOf('.');
        if (idx <0) {
            log.error("Errore nella formattazione della chiamata al metodo: " + expr + ". No '.' found");
            return;
        } 

        final String target = expr.substring(0, idx).substring(2);
        final String t = expr.substring(idx + 1);
        String method = t.substring(0, (t.length() - 1));

        final int idxParams = method.indexOf('(');
        if (idxParams >=0) {
            method = method.substring(0,idxParams);
        }

        final MethodExpression expression = new MethodExpressionMethodBindingAdapter(binding);
        final ELContext elContext = context.getELContext();
        final ExpressionFactory factory = context.getApplication().getExpressionFactory();

        final ValueExpression ve = factory.createValueExpression(elContext, "#{" + target + '}', Object.class);
        final Object result = ve.getValue(elContext);

        // Check if the target method is a secured method
        // and check security accordingly
        final Method[] methods = result.getClass().getMethods();
        for (final Method meth : methods) {
            if (meth.getName().equals(method)) {
                if (meth.isAnnotationPresent(CustomSecurity.class)) {
                    final CustomSecurity securityAnnotation = meth.getAnnotation(CustomSecurity.class);
                    log.debug("Function to check security on: " + securityAnnotation.value()); 
                    SecurityUtility.checkSecurity(securityAnnotation.value());
                } else {
                    super.processAction(event);
                }
                break;
            }
        }

        log.warn("No method: " + method + " found in: " + methods + ", for object: " + result);

    }

}

If the action on commandButton is defined in standard way:

<h:commandButton action="#{bean.action}" value="test" />

everything is OK and I'm able to detect bean and action in the listener by its ActionEvent but with composite component and this code I have no information on real action params

I have the same problem if I use brace notation: #{beanName['action']}. In this case, in debug mode I can see the object TagMethodExpression in binding with a MethodExpressionImpl and a VariableMapperImpl where there is the mapping beanName -> "real_bean_name"

It's there a way to get bean and action by ActionEvent if it is generated by a composite component or brace notation?

Thanks!


Solution

  • Not very clean as solution but I solved using ActionSource2, MethodExpression and Reflection to access TagMethodExpression private field...

    final ActionSource2 actionSource = (ActionSource2) source;
    MethodExpression expression;
    expression = actionSource.getActionExpression();
    String exp = expression.getExpressionString();
    
    String trimmed = exp.trim();
    
    // remove ending bracket
    trimmed = trimmed.substring(0, trimmed.lastIndexOf("']"));
    
    int openBracket = trimmed.lastIndexOf("['");
    
    String beanName = trimmed.substring(0, openBracket);
    StringBuilder builder = new StringBuilder(beanName);
    builder.append('.');
    builder.append(trimmed.substring(openBracket + 2, trimmed.length()));
    
    String ret = builder.toString();
    
    if (expression instanceof TagMethodExpression) {
        try {
    
            TagMethodExpression tme = (TagMethodExpression) expression;
            Field f = tme.getClass().getDeclaredField("orig");
            f.setAccessible(true);
            MethodExpression me = (MethodExpression) f.get(tme);
            Field ff = me.getClass().getDeclaredField("varMapper");
            ff.setAccessible(true);
            VariableMapperImpl vmi = (VariableMapperImpl) ff.get(me);
            ValueExpression beanResolveVariable = vmi.resolveVariable(beanName);
            String toString = beanResolveVariable.toString();
            Pattern pattern = Pattern.compile("value=\"#\\{(.*?)\\}");
            Matcher matcher = pattern.matcher(toString);
    
            if(matcher.find()){
                String realBeanName = matcher.group(1);
                ret = ret.replace(beanName, realBeanName);
            }
        } catch (NoSuchFieldException ex) {
            log.error(LogUtility.getStrExc(ex));
        } catch (SecurityException ex) {
            log.error(LogUtility.getStrExc(ex));
        } catch (IllegalAccessException ex) {
            log.error(LogUtility.getStrExc(ex));
        }
    }
    ...