ADF: making an operation (binding) available in every Page Definition (Binding Container) javacode 9085791

ADF: making an operation (binding) available in every Page Definition (Binding Container)

Our challenge: from every one of our 100+ pages we would like to be able to invoke a specific operation on the ApplicationModules Client Interface. Ideally, we should create an OperationBinding (MethodAction) in every PageDef that binds this AppMod Method. However, that is too much trouble…

Our options:

  1. add the operationBinding (ActionBinding) in every Page Definition, either manually or with some automated mechanism
  2. try to manipulate all BindingContainers at run-time, right after they have been initialized by the ADF Binding Filter
  3. use a central Page Definition that complements all of the others; create the Operation Binding in that one Page Def and ensure – for example in a prepareModel phase listener – that it is always initialized and prepared; then have the button invoke not #{bindings.operation.execute} but #{data.OtherPageDef.operation.execute} instead. (this is similar to the method described in an earlier post Provide your ADF Faces application with a central BindingContainer for generic access to application wide services)
  4. not use an OperationBinding and instead create a managed bean that is invoked whenever we want the AppMod Client Interface Method called; in the managed bean get hold of the DataProvider for the current BindingContainer (#{bindings}), cast it to the ApplicationModule interface (here we cross the boundary to the Model/Persistence Realm that we normally would not want to cross in ADF applications) and directly call the method

I have not succeeded in run time manipulation of the BindingContainers – though I have come close. My hope rested on Steve Muench (http://radio.weblogs.com/0118231
) to help me with that approach. And fortunately Steve delivered the goods: he provided me very promptly with the code I needed for creating an ActionBinding on the fly.

That means that my solution now consists of:

1. A ServletFilter that intercepts the first request made for the application in a user session. The filter gets hold of either all or a selected set of BindingContainers and adds an ActionBinding to them.

 

The filter class looks like this:

public class ADFBindingConfiguratorFilter implements Filter {

    private FilterConfig config;
    public static final String INITIAL_REQUEST_KEY = 
        "INITIAL_REQUEST_IN_CXS_SESSION";

    public ADFBindingConfiguratorFilter() {
    }

    public void init(FilterConfig filterConfig) throws ServletException {
        this.config = filterConfig;
    }

    /**
     * The doFilter method is called every time a request is made that matches
     * the URL pattern set up in the filter mapping
     */
    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, 
                                                         ServletException {
        // Initialize some convenience variables
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpSession session = request.getSession(false);

        Boolean initialrequest = 
            (Boolean)session.getAttribute(ADFBindingConfiguratorFilter.INITIAL_REQUEST_KEY);
        if (initialrequest == null) {
            session.setAttribute(ADFBindingConfiguratorFilter.INITIAL_REQUEST_KEY, 
                                 Boolean.FALSE);
            configurePageDef(request);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

The configurePageDef method is the one that initiates the real work this filter was created for.

    /**
     * In this example, locate the DeptPageDef binding container and add an action binding to it.
     * 
     * In serious applications, we could loop over all BindingContainers and extend them with additional bindings. 
     * 
     * @param request The HttpRequest object
     */
    private void configurePageDef(HttpServletRequest request) {
        BindingContext bcx = DCUtil.getBindingContext(request);
        if (bcx != null) {
            DCBindingContainer container = null;
            container = bcx.findBindingContainer("DeptPageDef");
            DCDataControl x = container.getDataControl();
            ensureFacesCtrlActionToCallDataControlMethod("CallMyMethod", 
                                                         x.getName(), 
                                                         "MyMethod", 
                                                         new String[][] { { "param1", 
                                                                            "String", 
                                                                            "${'Hoera'}" }, 
                                                                          { "param2", 
                                                                            "String", 
                                                                            "${MyBean.param1}" } }, 
                                                         container);
        }
    }

The important method – the one Steve Muench put together for me – is this one:

    /*
              * It if doesn't already exist in the current binding container, create a FacesCtrlActionBinding
              * for the data control method whose information is passed in.
              */

    private void ensureFacesCtrlActionToCallDataControlMethod(String actionBindingName, 
                                                              String dataControlName, 
                                                              String methodName, 
                                                              String[][] argNameTypeValues, 
                                                              DCBindingContainer container) {
        DCBindingContainer bc;
        if (container == null) {
            HttpServletRequest request = 
                (HttpServletRequest)ADFContext.getCurrent().getEnvironment().getRequest();
            bc = (DCBindingContainer)request.getAttribute("bindings");

        } else
            bc = container;

        if (bc.getControlBinding(actionBindingName) == null) {
            FacesCtrlActionDef def = new FacesCtrlActionDef();
            HashMap initVals = new HashMap();
            initVals.put(JUCtrlActionDef.PNAME_ActionID, "999");
            initVals.put(JUCtrlActionDef.PNAME_DataControl, dataControlName);
            initVals.put(JUCtrlActionDef.PNAME_InstanceName, 
                         dataControlName + ".dataProvider");
            initVals.put(JUCtrlActionDef.PNAME_ReturnName, 
                         dataControlName + ".methodResults." + 
                         dataControlName + "_dataProvider_" + methodName + 
                         "_result");
            initVals.put(JUCtrlActionDef.PNAME_IsViewObjectMethod, 
                         Boolean.FALSE);
            initVals.put(JUCtrlActionDef.PNAME_RequiresUpdateModel, 
                         Boolean.TRUE);
            initVals.put(JUCtrlActionDef.PNAME_IsLocalObjectReference, 
                         Boolean.FALSE);
            initVals.put(JUCtrlActionDef.PNAME_MethodName, methodName);
            // support input parameters for the action binding
            // input parameters are defined through the argNameTypeValues input parameter that is an Array of Array, 
            // with the second Array containing three strings: the name, Java type and value (EL expression that gets hold of the value) for the input parameter
            int numArgDefs = 
                argNameTypeValues == null ? 0 : argNameTypeValues.length;
            if (numArgDefs > 0) {
                DCMethodParameterDef[] argDefs = 
                    new DCMethodParameterDef[numArgDefs];
                for (int z = 0; z < numArgDefs; z++) {
                    String[] argNameTypeValue = (String[])argNameTypeValues[z];
                    if (argNameTypeValue == null || 
                        argNameTypeValue.length != 3) {
                        throw new RuntimeException("Expecting argDef as String[3] of {name,type,value}");
                    }
                    argDefs[z] = 
                            new DCMethodParameterDef(argNameTypeValue[0], argNameTypeValue[1], 
                                                     argNameTypeValue[2]);
                }
                initVals.put(JUCtrlActionDef.PNAME_Arguments, argDefs);
            }
            def.init(initVals);
            // at this point we can add the actionBinding to the binding container
            FacesCtrlActionBinding b = 
                (FacesCtrlActionBinding)def.createControlBinding(bc);
            // finally set the name under which we want to be able to find this binding in the container
            b.setName(actionBindingName);
        }
    }

The filter is configured in the web.xml like this:

    <filter>
        <filter-name>adfBindings

PostLoadConfigurator</filter-name>
        <filter-class>view.ADFBindingConfiguratorFilter</filter-class>
    </filter>
    ...
    <filter-mapping>
        <filter-name>adfBindings</filter-name>
        <url-pattern>*.jspx</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
    </filter-mapping>
    <filter-mapping>
        <filter-name>adfBindingsPostLoadConfigurator</filter-name>
        <url-pattern>*.jspx</url-pattern>
    </filter-mapping>

 

It is very important to set up the filtermapping after the mapping of the adfBindingFilter – as that one has to initialize the binding context that my filter digs into.

This will service the following set up:

1. CommonApplicationModuleImpl that is extended from in every ADF BC ApplicationModule in our application; this CommonApplicationModuleImpl contains the method in question, that we want to be able to invoke from virtually every page in the application. The method is added to the Client Interface of every ApplicationModule that should support it. That means it is available on the DataControl published for that ApplicationModule, and can be used to bind in a PageDefinition.

The ApplicationModuleMethod in this example is somewhat simple and useless, but you hopefully get the picture:

    public String MyMethod(String param1, String param2) {      
      return "Hello there "+param1+":::"+param2;
    }

2a. A managed bean that has code to programmatically get hold of the OperationBinding in the context of the current page, whichever it is, and invokes it

The bean has the following method

    public void callMyMethodCaller() {
        FacesContext ctx = FacesContext.getCurrentInstance();
        Application app = ctx.getApplication();
        // the bean is session scope hence we cannot set bindings as a managed property and have to get it using an EL Expression
        ValueBinding bind = app.createValueBinding("#{bindings}");
        BindingContainer bindings = (BindingContainer)bind.getValue(ctx);
        // in the Binding Container for the current page - could be any page! - locate the ActionBinding CallMyMethod
        OperationBinding operationBinding = 
            bindings.getOperationBinding("CallMyMethod");
        Object result = operationBinding.execute();
        this.result = result;
    }

that is invoked for example from a button like this:

<af:commandButton text="Alternative, roundabout way to call very special method" action="#{MyBean.callMyMethodCaller}"/>

Using a managed bean in between gives us a little bit more last minute control over either the parameters sent into the method or the result returned from it.

2b. A button on virtually every page that directly invokes the dynamically created OperationBinding through its actionListener.

The button looks like this:

<af:commandButton id="mybeanbutton"  partialSubmit="true"  
                  actionListener="#{bindings.CallMyMethod.execute}"
                  text="Call very special method"/>

Now with very little effort, I have added the binding of a method on the Business Service to every page in the application, making it available throughout the pages.

7 Comments

  1. AdamA April 4, 2008
  2. Lucas Jellema February 3, 2008
  3. Jan Vervecken February 3, 2008
  4. Lucas Jellema February 3, 2008
  5. Jan Vervecken February 2, 2008
  6. Rob Brands February 2, 2008
  7. Edwin Biemond February 2, 2008