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:
- add the operationBinding (ActionBinding) in every Page Definition, either manually or with some automated mechanism
- try to manipulate all BindingContainers at run-time, right after they have been initialized by the ADF Binding Filter
- 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)
- 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.
Hi Lucas. Do you think that this workaround might solve the problem mentioned the comments section of your article at http://technology.amis.nl/blog/?p=1709#comment-304824? Namely: The problem of not being able to display data bound components of a “content” page referenced in an adf region tag…I appreciate your help!
Sort of. Actually we are working round the fact that a record presented in the screen may have a reference to a record – created by another user – to a record we are not allowed to access. For example: we are looking at Employee SCOTT who is assigned to Department 10 – SALES. However, we are not privileged to see Department 10. The business requirements for our project state that in this case we should have a Dropdownlist that is disabled – we cannot assign SCOTT to another Department as he is currently in a department we are not privileged for. However, the Dropdownlist should show the label SALES for the referenced Department. And to retrieve that label for a user who strictly speaking cannot see SALES at all, we had to punch a hole in our VPD like data authorization mechanism – which we did in a rather elegant way I believe, for which we used this solution by Steve Muench.
regards,
Lucas
Thanks for your reply Lucas.
Indeed that sounds like a fairly complicated story. I’m not sure I understand correctly, but are you implementing (part of) your data authorization rules in the view layer which requires you to have this operation binding available?
regards
Jan Vervecken
Jan,
That is a fairly complicated story involving dropdown items that are displayed as LOVs when they contain more than a specific configurable threshold and that may contain a value that is associated with a (lookup) value that the current user is not allowed to see considering the data authorization rules for which we will display the read-only label. For this, we have a bean that can complement the set of allowable values for the dropdown element with that one value it would normally not contain, by calling a method on the application module that in turn consults a PL/SQL function to return the display label for that particular value. Since potentially every page can contain one or more dropdown elements, we may need to invoke the ApplicationModule’s method from every page in the application, hence we need the operation binding in all PageDef files.
Perhaps you should come and see it in order to understand exactly what we are trying to achieve….
Best regards, Lucas
Thank you Lucas.
I’m just wondering what the specific use case would be for this, “from every one of our 100+ pages we would like to be able to invoke a specific operation on the ApplicationModules Client Interface”.
regards
Jan Vervecken
Thats what we hoped for. Good start of the weekend 😉
Very nice. In jdev 11g you can add this in the pagedef of the jsf page template.