JDeveloper 12c: using Expression Language in pageDef to switch ControllerClass

An interesting question came up late 2018 in the JDeveloper & ADF forum. A user asked how to use Expression Language (EL) in a pageDef file to switch the ControllerClass at runtime depending on some condition of a page.

The ControllerClass can be used to add custom code into the lifecycle of a page or fragment (see the full details at 27.4 Customizing the ADF Page Lifecycle). A tip

Tip:
You can specify the value of the page definition’s ControllerClass attribute as a fully qualified class name or you can enter an EL expression that resolves to a class directly in the ControllerClass field.
When using an EL expression for the value of the ControllerClass attribute, the Structure window may show a warning indicating that e “#{YourExpression}” is not a valid class. You can safely ignore this warning.

given in the documentation mentioned that you can use EL to specify the ControllerClass. The missing information is exactly how to do it.

Use Case

For a JSF page, a ControllerClass should be defined at runtime. The selected ControllerClass should depend on a condition.

Solution

There are a couple of blogs available which use a custom ControllerClass in a pageDef, but they use the direct specification of the custom class in the pageDef. Only one sample (https://github.com/oracle/adf-samples/releases/download/v1.0.0/OnPageLoad.zip) from Duncan Mills uses EL to set the ControllerClass. However, this sample was built for JDev 10.3.1! None of the samples I found use EL to switch the ControllerClass at runtime.

In summary, it’s time for a fresh sample using JDev 12.2.1.3

Building the UI

We start by creating a new Fusion Web Application from the gallery. The steps to follow can be looked up at Why and how to write reproducible test cases, so I skip them here.

Once the basic Fusion Web Application is built we open the adfc-config.xml (the unbounded ADF task flow) and add a page onto it.

We name the page index and create it using a Quick Layout. I normally use

but you can use whatever layout like. We add a Text to the header section and an af:inputText and an af:button to the content section. The page markup will look like

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html>
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:document title="index.jsf" id="d1">
        <af:form id="f1">
            <af:panelGridLayout id="pgl1">
                <af:gridRow height="50px" id="gr2">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                        <!-- Header -->
                        <af:outputText value="Using EL to switch ControllerClass" id="ot1"
                                       inlineStyle="font-size:x-large;"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow height="100%" id="gr1">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
                        <!-- Content -->
                        <af:panelGroupLayout id="pgl2" layout="horizontal">
                            <af:inputText label="Use Listener" id="it1"/>
                            <af:button text="Update" id="b1"/>
                        </af:panelGroupLayout>
                    </af:gridCell>
                </af:gridRow>
            </af:panelGridLayout>
        </af:form>
    </af:document>
</f:view>

Next, we open the properties of the af:button and edit the actionListener property. Here we create a new bean, name it ‘IndexBean’, create a new method ‘updatePage’. The bean we create in sessionScope. Once the bean has been created, we delete the actionListener from the af:button we just created. The ‘updatePage’ method in the IndexBean we delete too. We don’t need the listener and I only used it to create the bean in the right scope for other things.

I created the bean in session scope as I will use it to store the data from the inputText we added to the page. This is just a convenience, we could have used a pageDef variable for this.

We add a String property ‘usePPListener’ to the IndexBean add the needed getter and setter methods.

public class IndexBean {
    String usePPListener = "1";

    public IndexBean() {
    }

    public void setUsePPListener(String usePPListener) {
        this.usePPListener = usePPListener;
    }

    public String getUsePPListener() {
        return usePPListener;
    }
}

We set the default value of the ‘usePPListener’ to “1”. We set the ‘usePPListener’ to the value property of the af:inputText field of the page. And set the autoSubmit property of the af:inputText to true. The page markup for the content:

<af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
    <!-- Content -->
    <af:panelGroupLayout id="pgl2" layout="horizontal">
        <af:inputText label="Use Listener" id="it1" value="#{IndexBean.usePPListener}"
                      autoSubmit="true"/>
        <af:button text="Update" id="b1"/>
    </af:panelGroupLayout>
</af:gridCell>

Running the application at this stage shows

Creating the PagePhaseListener Class

Now we can go on and create the custom PagePhaseListener classes which we then use to switch using an EL. To create such a custom class, the documentation tells us that all we have to do is to create a class which implements the ‘PagePhaseListener’ interface.

This will create the first PagePhaseListener named ‘MyPagePhaseListenerA’. The resulting class looks like

package de.hahn.blog.elpagedef.view.beans;

import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
import oracle.adf.controller.v2.lifecycle.PagePhaseListener;

public class MyPagePhaseListenerA implements PagePhaseListener {
    public MyPagePhaseListenerA() {
        super();
        System.out.println("MyPagePhaseListenerA created");
    }

    @Override
    public void afterPhase(PagePhaseEvent pagePhaseEvent) {
        // TODO Implement this method
        System.out.println("MyPagePhaseListenerA afterPhase called");
    }

    @Override
    public void beforePhase(PagePhaseEvent pagePhaseEvent) {
        // TODO Implement this method
        System.out.println("MyPagePhaseListenerA beforePhase called");
    }
}

We do it again but name the class ‘MyPagePhaseListenerB’. As you see I added some System.out.println(“…”) statements so that we can see which PagePhaseListener is used later.

Using EL in pageDef.xml

Finally, we start with the interesting part, the EL to use in the pageDef ControllerClass property to switch the listener. The use case demands that we switch the PagePhaseListener depending on a condition. The condition we use is pretty simple:

  • Use MyPagePhaseListenerA if the inputText value stored in usePPListener is equal to “1”
  • Use MyPagePhaseListenerB if the inputText value stored in usePPListener is equal to “2”

We have two options to make the decision,

  1. directly in EL in the pageDef
  2. use a method in a bean where we check the condition

The essential part to know is the type of result both options need to return to work. The ControllerClass expects an object of type PagePhaseListener, or a class which implements this interface.

Solution 1

The classes we created before are implementing this interface, so they should work. To implement option 1 we need to need to instantiate an object of one of the classes and return it on the EL like in the pageDef.xml:

ControllerClass=”#{IndexBean.usePPListener eq ‘1’ ? MyPagePhaseListenerA : MyPagePhaseListenerB}”

And we need to register the bean named ‘MyPagePhaseListenerA’ and ‘MyPagePhaseListenerA’ in the task flow. This will instantiate the object when the task flows starts.

Running this application this way we get

Entering ‘2’ into the af:inputText and clicking ‘Update’ we get

Heureka! The PagePhaseListener have switched as expected.

Solution 2

We implement a method in a bean which returns the right PagePhaseListener class. The EL in the pageDef.xml looks like

ControllerClass="#{IndexBean.pagePhaseListener2Use}"

The method getPagePhaseListener2Use() we implement in the IndexBean as

public PagePhaseListener getPagePhaseListener2Use() {
    if (getUsePPListener() != null && getUsePPListener().equals("2")) {
        return new MyPagePhaseListenerB();
    } else {
        return new MyPagePhaseListenerA();
    }
}

The method returns the PagePhaseListener interface instead of the real class object. This is necessary as a method can only return one object type.

Running the application in this configuration results in exactly the same output we saw before.

Using EL in PageDef for Fragments used in Regions

If you plan to use this technique for fragments which run in regions you would need to return a RegionController instead of a PagePHaseListener. However, as it turned out, you can’t use EL at all in a pageDef of a fragment!

The problem is, that the framework simply doesn’t evaluate the EL you specify for the ControllerClass of a fragment, but uses the EL as the class name. This results in a ‘ClassNotFoundException’ as there is no class named ‘#{your_EL}’.

I’m not sure if this is a bug or a feature. I could not think of a valid use case to use EL for a fragment pageDef.

If you do have one, share it in the comments to this post, please.

If you really think you need to switch the behavior for a RegionController at runtime, you can create one Class which implements the different in the overwritten methods and decide which part to execute inside the method.

Sample

You can download the sample application, which was built using JDev 12.2.1.3 and no DB connection, from GitHub BlogELPageDef

Advertisements