5 Replies Latest reply on Nov 6, 2009 9:34 AM by Thomas Goettlich

    Run stored MethodExpression with parameters

    Thomas Goettlich Novice

      Hi,


      I'm currently about to implement a breadcrumbs component for our application.
      To go back to a certain view, I store the view id and the action that is used to restore that view's state.


      When I click on the link, I'd like execute the stored method expression but with the parameters I stored as well.


      However, MethodExpression.invoke(context, parameters) doesn't work, since the parameters are ignored and instead extracted from the expression (in which case I get null values).


      So I'm currently doing it like this:


      public void executeAction()
        {
          if(methodExpression != null)
          {
            Node tNode = ExpressionBuilder.createNode( methodExpression.getExpressionString());
            
            if(tNode != null && tNode.jjtGetNumChildren() == 2)
            {
              ELContext tElContext = FacesContext.getCurrentInstance().getELContext();
              
              FunctionMapper tFunctionMapper = tElContext.getFunctionMapper();
              VariableMapper tVariableMapper = tElContext.getVariableMapper();
              
              EvaluationContext tEvaluationContext = new EvaluationContext( tElContext, tFunctionMapper, tVariableMapper );
              
              String tPrefix = tNode.jjtGetChild( 0 ).getImage();
              String tLocalName = tNode.jjtGetChild( 1 ).getImage();
              
              Object tBase = tNode.jjtGetChild( 0 ).getValue( tEvaluationContext );
      
      
              try
              {
                ReflectionUtil.invokeMethod( tBase, tLocalName, parameters.toArray() );
              }
              catch( Exception e )
              {
                FacesContext.getCurrentInstance().getExternalContext().log( "could not execute expression: " + methodExpression.getExpressionString(), e );          e.printStackTrace();
              }
            }
          }
        }



      However, I'd like to use a better, less hackish solution if one is available.


      Can someone help me with this?


      Thanks in advance.


      Thomas

        • 1. Re: Run stored MethodExpression with parameters
          Tim Evers Master

          I have something that might help. I'm not sure it's the best way to do this but, I have found it to be quite useful.


          I'm going to make some assumptions.



          • You can store (know/work it out) the return type of the method you are executing.

          • You know the types of all the params you are going to pass to the method

          • You know the values of all the params you are going to pass to the method



          So, lets assume your methodExpression is something like this:


          #{ASeamBean.AMethod(param1, param2)}
          



          When you go to execute the action. Create a new MethodExpression but only use the string #{ASeamBean.AMethod} (No params)


          So you'll do something like this


          MethodExpression expression = 
              expressionFactory.createMethodExpression(
                  elContext,
                  "#{ASeamBean.AMethod}",
                  returnType,
                  paramsTypes);
          




          • 'expressionFactory' comes from facesContext.getApplication().getExpressionFactory()

          • 'elContext' comes from facesContext.getELContext()

          • 'returnType' is the return type of your action method.

          • 'paramTypes' are the types of your params in the order they should be passed to the method.



          Now you should be able to use the invoke method and pass in the params you want.


          So:


          result = expression.invoke(elContext, paramValues);
          



          The 'result' variable should be of the same type as the returnType of the original MethodExpression.


          I'm pretty sure this should work. It's basically how I do something in my app. Not exactly what you are doing, but close enough :)

          • 2. Re: Run stored MethodExpression with parameters
            Thomas Goettlich Novice

            That sounds a lot better.


            As for the return and parameter types, I could use the MethodExpression#getMethodInfo(...) method, which returns a MethodInfo object containing those types.


            The parameter values currently have to be passed using <f:param> tags, so they are also known.


            And the expression string could bet taken from the MethodExpression I stored. However, I'd need to extract the bean and method names myself, so if there is a more standard way to extract those from #{SomeBean.someMethod(someParameter} I'd apreciate it.


            On the other hand, I could use this to pass an expression that is not evaluated yet but stored for later evaluation.
            What I mean is, that in the page the user could write <mytag expression="SomeBean.someMethod"> and I could create the MethodExpression object from that. (If it would be <mytag expression="#{SomeBean.someMethod}">the expression would be evaluated at immediately, wich I don't want in that case).


            However, the difficulty here is to get the parameter types right (the return type generally is void, so that's no problem). Sometimes I pass a derived object as a parameter, so if I have a method someMethod(SuperObject o) and pass a DerivedObject instance, I'd still have to pass SuperObject.class as the parameter type (not DerivedObject.class), wouldn't I?

            • 3. Re: Run stored MethodExpression with parameters
              Thomas Goettlich Novice

              I've now changed my code accoring to Tim's suggestions and it works quite well.
              Additionally, I added support for deferred actions that are not evaluated until the breadcrumb link is clicked.


              One issue remains though: the deferred action has to be provided as <mytag action="SomeBean.someMethod">, i.e. without being enclosed in #{...}. Otherwise the expression is evaluated immediately, which results in an error, since someMethod expects parameters. Is there a way to tell the resolver or who ever evaluates the expression not to do so until I invoke the method manually?


              Nevertheless, I'll post my code for those who are interested. Any review is welcome.


              ...
                        String tBreadCrumbAction = (String) tAttributes.get( "nav.breadCrumb.action" );
              
                        if( tComponent instanceof UICommand )
                        {
                          UICommand tCommand = (UICommand) tComponent;
              
                          FacesContext tFacesContext = FacesContext.getCurrentInstance();
                          ExpressionFactory tExpressionFactory = tFacesContext.getApplication().getExpressionFactory();
                          ELContext tElContext = tFacesContext.getELContext();
              
                          // get the passed parameters
                          for( UIComponent tParameterChild : tCommand.getChildren() )
                          {
                            if( tParameterChild instanceof UIParameter )
                            {
                              UIParameter tParam = (UIParameter) tParameterChild;
                              tBreadCrumbEntry.addParameter( tParam.getValue() );
                            }
                          }
                          
                          String tExpressionString;
                          Class tReturnType = null;
                          Class[] tParameterTypes;
                          if( tBreadCrumbAction == null )
                          {
                            //strip the parameters from the expression, i.e. #{SomeBean.someMethod(someParameter)} -> #{SomeBean.someMethod}
                            MethodExpression tActionExpression = tCommand.getActionExpression();
                            StringBuilder tExpressionBuilder = new StringBuilder();              
                            
                            if( tActionExpression != null )
                            {
                              Node tNode = ExpressionBuilder.createNode( tActionExpression.getExpressionString());
              
                              for(int i = 0; i < tNode.jjtGetNumChildren(); ++i)
                              {
                                Node tChild = tNode.jjtGetChild( i );
                                String tNodeImage = tChild.getImage();
                                
                                if(tExpressionBuilder.length() > 0 && tNodeImage != null)
                                {
                                  tExpressionBuilder.append( '.' );
                                }
                                else if(tExpressionBuilder.length() == 0)
                                {
                                  tExpressionBuilder.append( "#{" );
                                }
                                
                                if(tNodeImage != null)
                                {
                                  tExpressionBuilder.append( tNodeImage );
                                }
                              }
              
                            }
                            tExpressionBuilder.append( "}" );
                            tExpressionString = tExpressionBuilder.toString();
              
                            MethodInfo tMethodInfo = tActionExpression.getMethodInfo( tElContext );
                            tReturnType = tMethodInfo.getReturnType();
                            tParameterTypes = tMethodInfo.getParamTypes();
                          }
                          else
                          {
                            //build an expression and parse it into an AST -> I'd like to skip that part, if possible
                            tExpressionString = "#{" + tBreadCrumbAction + "}";
                            
                            Node tNode = ExpressionBuilder.createNode( tExpressionString );
                            
                            if(tNode.jjtGetNumChildren() != 2)
                            {
                              throw new RuntimeException( "currently only actions in the form 'SomeBean.someAction' are supported");
                            }
                            
                            FunctionMapper tFunctionMapper = tElContext.getFunctionMapper();
                            VariableMapper tVariableMapper = tElContext.getVariableMapper();
                            
                            EvaluationContext tEvaluationContext = new EvaluationContext( tElContext, tFunctionMapper, tVariableMapper );
                            
                            //get the bean instance and the method name
                            Object tBeanInstance = tNode.jjtGetChild( 0 ).getValue( tEvaluationContext );
                            String tMethodName = tNode.jjtGetChild( 1 ).getImage();
                            
                            List<Object> tParameters = tBreadCrumbEntry.getParameters();
                            
                            //retrieve the best fitting method for the passed parameters
                            MethodInfo tMethodInfo = ReflectionUtil.getMethodInfo( tBeanInstance, tMethodName, tParameters.toArray() );
              
                            tReturnType = tMethodInfo.getReturnType();
                            tParameterTypes = tMethodInfo.getParamTypes();
                          }
              
                          // build a fresh method expression
                          if( tExpressionString != null )
                          {
                            MethodExpression tMethodExpression = tExpressionFactory.createMethodExpression( tElContext, tExpressionString, tReturnType, tParameterTypes );
                            
                            
                            tBreadCrumbEntry.setMethodExpression( tMethodExpression );
                          }
                        }
              ...



              My executeAction() method then boils down to:


                public void executeAction()
                {
                  //some other admistrative work
                  
                  if(methodExpression != null)
                  {
                    methodExpression.invoke( FacesContext.getCurrentInstance().getELContext(), parameters.toArray() );
                  }
                }


              • 4. Re: Run stored MethodExpression with parameters
                Tim Evers Master

                Hey Thomas,


                Putting method expressions into a custom component is deffinitly possible and I have done just that in one of my components. That being said it took me a LONG time to find the right way of doing it because there are so many terrible tutorials that don't give all the necessary information. If done correctly they will not be evaluated at render time and you can treat them as propper methodExpressions :) However, I had not worked out how to do it properly when I wrote my component (and I haven't had time to go back and fix it... why fix what's not broken :P). Anyway, here's what I have. Hope it helps.


                My xhtml looks like this (resolveEntryMethod is my special attribute with a methodExpression in it. valueLabel also does funky stuff but ignore it for now :P)


                <gekko:textLookup
                    id="controlLevelLookup"
                    gekkoRequired="true"
                    var="controlLvl"
                    valueLabel="#{controlLvl.code}"
                    value="#{AccountMaintenanceController.currentAccount.controlLevel}"
                    label="Control level"
                    converterErrorMessageId="gekko.web.jsf.converter.TextLookupConverter.ControlLevel"
                    resolveEntryMethod="#{AccountDetailsDataBean.findControlLevelWithCode}">
                



                My component looks like this


                public class UITextLookup extends HtmlInputText {
                    private String resolveEntryMethodExpression;
                
                    public void setResolveEntryMethodExpression(String expression) {
                        this.resolveEntryMethodExpression = expression;
                    }
                
                    public String getResolveEntryMethodExpression() {
                        return resolveEntryMethodExpression;
                    }
                
                    @Override
                    public Object saveState(FacesContext _context) {
                
                        Object[] values = new Object[2];
                        values[0] = super.saveState(_context);
                        final ValueExpression valueExpression = 
                            bindings.get("resolveEntryMethod");
                        Validate.notNull(valueExpression, 
                            "No method expression was given for the " +
                            "'resolveEntryMethod' attirbute. This value must be provided.");
                        values[1] = valueExpression.getExpressionString();
                        return values;
                    }
                
                    @Override
                    public void restoreState(FacesContext _context, Object _state) {
                        super.restoreState(_context, ((Object[]) _state)[0]);
                        resolveEntryMethodExpression = (String) ((Object[]) _state)[1];
                    }
                }
                



                NOTE: The weird behaviour of my saveState method. (I'm pretty sure that this could be written much much better, but I wrote this component before I learnt how to make custom attributes work properly with methodExpressions :)


                Now, when it comes to executing your action... Your code seems a lot more complicated then I expected :P. But, I havn't really taken the time to figure out why it is so.


                When I use the expression this is my code.


                String methodExpressionString = 
                    ((UITextLookup)component).getResolveEntryMethodExpression();
                
                //I've saved off the return type in the component so I don't have to work it out now        
                Class<?> clazz = (Class<?>)component.getAttributes().get(CLASS_TYPE_ATTR); 
                
                javax.el.MethodExpression methodExpression = 
                    WebUtils.methodHelper(methodExpressionString, clazz, new Class<?>[]{String.class});
                        
                ELContext elc = context.getELContext();
                Object result = methodExpression.invoke(elc, new Object[]{value});
                



                Obviously my component only deals with a single string as the param so it is much simpler.


                I guess, what I don't understand is why don't you get all the necessary information when you first create this component. The full/normal EL methodExpression is valid right? So when you render pageA.xhtml with your <mytag action=#{PageAController.navigateToPageA(aParam, bParam)}/>


                As you can see in my code I have saved off the return type I expect from the EL expression so that I don't need to work it out later.


                Then in your component during render begin or something surely you could just grab the methodExpression in the action attribute and get the return type from it there and store it for use later in your code that executes the action later. Then you could get rid of at least half of your code where you go off and reflectivly get info about the return type of the method.


                I could be missing something here and I should go off and test what I'm saying. If your component isn't too tightly bound to your app maybe you can post the code for it so I can see if I can make it a bit simpler.



                This tutorial is a really nice simple example of how to make a methodExpression custom attribute. Good place to start. :)


                Ok, I better do some of my own work now :P
                Hope this helps :) - if it has please rate it :)

                • 5. Re: Run stored MethodExpression with parameters
                  Thomas Goettlich Novice

                  Hi, thanks for the info.


                  Yes, the original EL expression is valid. However, I can't just store the expression with the parameters since the parameters might be valid any more if the expression is executed later. I had cases where the parameters were the wrong objects that were bound to the same name after storing the expression or in other cases the parameters were null.


                  If I could extract the parameter values before storing the expression and if I also could pass the deferred expression, there wouldn't be any need to pass the parameter values explicitly in my page and the reflection code could be reduced a lot (depends on how complicated extracting the parameters from the expression would be). This would be my preferred solution.


                  My component is not really tied to the project (except the fact that we use AJAX and don't refresh the entire page but only the content area, which we call our 'view').



                  I'll describe my aproach a bit more, but I won't post all the code in order not to clutter that thread. The relevant portions either have already been posted (see above), can be described in words as they are trivial or will be posted below.


                  It basically consists of 3 components ( + 1 component for rendering the breadcrumb trail):


                  1. A backing bean, called NavigationBean. This one returns an execution handler, whose executeAction method consists primarily of the code I posted in my last post. This one extracts the action and its parameters if the attribute 'nav.breadCrumb' is set to true.


                  2. A BreadCrumbEntry object that is stored on a stack. This entry takes the method expression an its parameters among other attributes (which don't matter here). When the breadcrumb link is clicked the executeAction() method posted in my last post will be called and it in turn calls the methodExpression with the stored parameters (parameters is just a List<Object> in the code above).


                  3. A facelet composition that adds attributes and parameters to the link in which it is nested.
                  Here's the code:


                  <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                    xmlns:h="http://java.sun.com/jsf/html"
                    xmlns:f="http://java.sun.com/jsf/core"
                    xmlns:ui="http://java.sun.com/jsf/facelets"
                    xmlns:rich="http://richfaces.org/rich"
                    xmlns:a4j="http://richfaces.org/a4j">
                  
                      <f:actionListener binding="#{navigation.breadCrumbNavigationListener}"/>
                      <f:attribute name="nav.view" value="#{view}"/>
                      <f:attribute name="nav.label" value="#{label}"/>
                      <f:attribute name="nav.breadCrumb" value="#{breadCrumb}"/>
                      <f:attribute name="nav.breadCrumb.disabled" value="#{breadCrumbDisabled}"/>
                      <f:attribute name="nav.breadCrumb.action" value="#{action}"/>
                      <f:attribute name="nav.breadCrumb.lastValidView" value="#{lastValidView}"/>
                      
                      <ui:insert/>
                      
                  </ui:composition>


                  The action listener is the one described as component 1. The other attributes are added to the command component in which this composition is nested. Of those, the relevant ones are: breadCrumb -> nav.breadCrumb which activates the parsing if set to true and action -> nav.breadCrumb.action which is the custom action attribute I described above. The other ones are visual attributes for the breadcrumb or for maintaining the stack.


                  The <ui:insert/> is meant to take the action parameters as nested <f:param .../> tags.



                  And here's an example of how it is used (note that 'ip' is our custom namespace):



                  <a4j:commandLink value="#{theme.translatedName}" action="#{ThemeBean.reloadTheme(theme)}">
                    <ip:navigation view="themeDetail" label="#{theme.translatedName}" breadCrumb="true">
                      <f:param name="param1" value="#{theme}" />
                    </ip:navigation>
                  </a4j:commandLink>
                  


                  As you can see, the action expression is #{ThemeBean.reloadTheme(theme)}, but 'theme' could be null is I call it later (it isn't at the time of clicking that link, though). Currently, I can't extract the value of 'theme' directly from the expression, so I have to pass it as <f:param name="param1" value="#{theme}" /> (note that the name is not important, it's just there to satisfy JSF).


                  If I wanted to register a different action, I could set the action attribute on <ip:navigation ...>, like this: <ip:navigation view="themeDetail" label="#{theme.translatedName}" breadCrumb="true" action="ThemeBean.doSomethingWithTheme">. Note that 'action' is not an expression as of now (the problem I mentioned in my last post - I'll check your suggestions later). Ideally, I'd like to write <ip:navigation view="themeDetail" label="#{theme.translatedName}" breadCrumb="true" action="#{ThemeBean.doSomethingWithTheme(theme)}"> and have it not evaluated.


                  And if I could extract the parameter values without evaluating the expression, the result could be this:


                  <a4j:commandLink value="#{theme.translatedName}" action="#{ThemeBean.reloadTheme(theme)}">
                    <ip:navigation view="themeDetail" label="#{theme.translatedName}" breadCrumb="true" action="#{ThemeBean.doSomethingWithTheme(theme)}"/>
                  </a4j:commandLink>