Master/Detail using af:tree for master and af:form for detail

A question on OTN JDeveloper and ADF forum about a sample for a master/detail navigation using a tree as master and a form as detail cought my attention. I quickly setup a sample I like to share in this post.

Frank Nimphius wrote an article How-to select multiple parent table rows and synchronize a detail table with the combined resul about this which is quite complex as it not only shows how the synchronization is done but adds CRUD operation to the tree too.
My sample is for beginners and only shows how to do the synchronization of the tree navigation and the detail from. If you need more functionality please refer to Frank’s article.

Problem description
At first the use case sounds easy. However, using a tree has it’s pitfalls. The tree navigation doesn’t synchronize the child node iterators with the node selection. This means that when we select a child node that the current row of the child iterator is not set to the selected row. This is shown in the gallery below:


As we see, in the second picture the form show the wrong data for the selected node. The form shows the first child row of the selected parent node.

Page layout
the sample uses a panel splitter which holds the af:tree on the left and the detail af:form on the right panel. This is shown in the gallery below:


The af:form has a partialTrigger set which listens to updates of the af:tree. The af:tree was build by dragging the Departments from the data control onto the left panel of the splitter. This created the markup
Default selectionListener of af:tree

Default selectionListener of af:tree


with the default selectionListener marked in blue. This is where we need to make the change.

Solution
To make the use case work, we have to use a custom selectionListener for the af:tree. The gallery below shows how a request scoped bean and the selectionListener is created:


The last image shows that the bean is automatically registered in the task flow (in adfc-config.xml in this case).
Now that we have created the selectionListener method in the bean, we can code it like

    /**
     * Custom managed bean method that takes a SelectEvent input argument
     * to generically set the current row corresponding to the selected row
     * in the tree.
     *
     * This method is not generic as it uses the normal binding.iterator.model.makecurrent the ui component uses.
     * The child iterator must be known too to select the child not in the chile view.
     *
     * @param selectionEvent object passed in by ADF Faces when configuring
     * this method to become the selection listener
     */
    public void onTreeNodeSelection(SelectionEvent selectionEvent) {
        //original selection listener set by ADF
        //#{bindings.Departments.treeModel.makeCurrent}
        String adfSelectionListener = "#{bindings.Departments.treeModel.makeCurrent}";
        //make sure the default selection listener functionality is
        //preserved. you don't need to do this for multi select trees
        //as the ADF binding only supports single current row selection

        /* START PRESERVER DEFAULT ADF SELECT BEHAVIOR */
        FacesContext fctx = FacesContext.getCurrentInstance();
        Application application = fctx.getApplication();
        ELContext elCtx = fctx.getELContext();
        ExpressionFactory exprFactory = application.getExpressionFactory();

        MethodExpression me = null;
        me = exprFactory.createMethodExpression(elCtx, adfSelectionListener, Object.class, new Class[] { SelectionEvent.class });
        me.invoke(elCtx, new Object[] { selectionEvent });

        /* END PRESERVER DEFAULT ADF SELECT BEHAVIOR */
        RichTree tree = (RichTree)selectionEvent.getSource();
        TreeModel model = (TreeModel)tree.getValue();

        //get selected nodes
        RowKeySet rowKeySet = selectionEvent.getAddedSet();
        Iterator<Object> rksIterator = (Iterator<Object>)rowKeySet.iterator();
        //for single select configurations,this only is called once
        while (rksIterator.hasNext()) {
            List<Object> key = (List<Object>)rksIterator.next();
            JUCtrlHierBinding treeBinding = null;
            CollectionModel collectionModel = (CollectionModel)tree.getValue();
            treeBinding = (JUCtrlHierBinding)collectionModel.getWrappedData();
            JUCtrlHierNodeBinding nodeBinding = null;
            nodeBinding = treeBinding.findNodeByKeyPath(key);
            Row rw = nodeBinding.getRow();
            //print first row attribute. Note that in a tree you have to
            //determine the node type if you want to select node attributes
            //by name and not index
            String rowType = rw.getStructureDef().getDefName();

            // check the node type as we don'T have to do anything is the parent node is selected
            if (rowType.equalsIgnoreCase("DepartmentsView")) {
                logger.info("This row is a department: " + rw.getAttribute("DepartmentId"));
            } else if (rowType.equalsIgnoreCase("EmployeesView")) {
                // for the child node we search the row which was selected in the tree
                logger.info("This row is an employee: " + rw.getAttribute("EmployeeId"));
                Object attribute = rw.getAttribute("EmployeeId");
                // make the selected row the current row in the child iterator
                DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
                DCIteratorBinding iterBind = (DCIteratorBinding)dcBindings.get("EmployeesOfDepartmentsIterator");
                iterBind.setCurrentRowWithKeyValue(attribute.toString());
            } else {
                // tif you end here your tree has more then two node types
                logger.info("Huh????");
            }
            // ... do more useful stuff here
        }
    }

Line 15 holds the original selectionListener expression from the af:tree. This we need to select the master node and row.
Lines 16-30 preserve the original function by executing the expression.
lines 31-38 check if there are selected nodes in the tree. Here we have to remember that a selected node in a tree isn’t just a rowKey the selectionListener always returns a set of nodes. If the tree is set to single selection the set contains exactly one node.
lines 39-49 get the data model of the tree and getting the node data (or row) and it’s type. This type we use to distinguish between master and detail selection.
Lines 51-65 do exactly this. In case of a master node (Departments) there is nothing to do as the default iterator does all the work. In case of a detail node (Employees) we have to do the magic.
Lines 54-61 Here we get the PK of the child row from the node and set the current row in the child iterator to this row (line 61).

That’s it. when we now run the application we see that the detail form synchronizes with the selected node in the tree.

The sample needs the HR DB schema. You can download the code for the sample, which was build using JDeveloper 11.1.1.9.0, from GitHub. Please note that if you run the sample in your environment, that you have to change the DB connection to the HR DB schema according to your environment.

Advertisements

Change Application Configuration at Run Time through a Properties File (Part 2)

In this second part of the mini series we look into the problem left over from part 1.

We left the task to change the location and name of the property file we read when a configuration property is needed by the application. You my ask why we need to change the path at all. The answer is that most administrators won’t allow you to copy a file to any location on a server. They normally don’t allow access to a production server at all. You can ask them to put put the configuration file to a location of their choice and then configure this path during deployment of the application. This is exactly what we do in this blog.

In the first part we finished the sample application which read a property file from a location we can set via a context parameter in web.xml. The question now is, how to change this parameter during deployment of the application. The answer to this is to use a Deployment Plan.

Typically, deployment plans are created by developers along with the associated application files, then distributed to the administrator or another deployer, who updates the plan for a particular environment (such as staging, testing, or production). The deployment plan is stored outside of an application archive or exploded archive directory. We store the initial plan in the ViewControllers src/META-INF folder as BlogReadConfigFile_Plan.xml.

<?xml version='1.0' encoding='UTF-8'?>
<deployment-plan xmlns="http://xmlns.oracle.com/weblogic/deployment-plan" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                 xsi:schemaLocation="http://xmlns.oracle.com/weblogic/deployment-plan http://xmlns.oracle.com/weblogic/deployment-plan/1.0/deployment-plan.xsd">
    <application-name>BlogReadConfigFile</application-name>
    <variable-definition>
        <variable>
            <name>ResourceFileLocation</name>
            <value>/tmp/readconfigfile.properties</value>
        </variable>
    </variable-definition>
    <module-override>
        <module-name>BlogReadConfigFile_BRCFViewController_webapp.war</module-name>
        <module-type>war</module-type>
        <module-descriptor external="false">
            <root-element>web-app</root-element>
            <uri>WEB-INF/web.xml</uri>
            <variable-assignment>
                <name>ResourceFileLocation</name>
                <xpath>/web-app/context-param/[param-name="de.hahn.blog.readconfigfile.FILENAME"]/param-value</xpath>
                <operation>replace</operation>
            </variable-assignment>
        </module-descriptor>
    </module-override>
</deployment-plan>

There are three notable parts in the plan. The first is the application name which is the same as you set under application properties in the deployment descriptor


The second section is the variable-definition section. Here we define variables which we later can use in the other parts of the descriptor as reference. Without a variable definition we can’t change a thing in the plan.
We name our variable ‘ResourceFileLocation’ and set the value to any appropriate location we like. This location don’t have to exist on the target server. We change it later anyway.
The third part is the module-overwrite where we specify which part of the of any descriptor, which is part of the deployment, we like to change.
It’s essential to name all parts exactly as they are named in the descriptors in your application.

The module name is the name of the war file we build, the type is war, as we build a war artifact. The root element describes which section of the deployed application we want to change and the URI exactly where the descriptor is located relative from its root.
The interesting part is the variable-assignment where we specify the variable name defined in the variable-definition section and which element of the defined module we want to change. This is done with a XPath expression:

<xpath>/web-app/context-param/[param-name="de.hahn.blog.readconfigfile.FILENAME"]/param-value</xpath>

which describes the location of the context parameter in web.xml with the name “de.hahn.blog.readconfigfile.FILENAME” and set it’s value to the variable value.
The operation tells what we want to do. As we want to replace the value we choose REPLACE as operation.

To make the setup complete we have to specify the BlogReadConfigFile_Plan.xml in the application properties

EAR Deployment Profile Properties

EAR Deployment Profile Properties


For more info about how to use deployment plans refer to the documentation at How to Use Deployment Plans

We can now deploy the application as usual and run the application from within JDeveloper. To show how it’s done when you deploy the application to a stand alone server we first create the ear file, then start the integrated WLS in JDevloper and open the admin console to deploy the application

Deploy the Application

Deploy the Application


which will create the ear file as we see in the log window

[06:33:59 PM] ----  Deployment started.  ----
[06:33:59 PM] Target platform is  (Weblogic 12.x).
[06:33:59 PM] Running dependency analysis...
[06:33:59 PM] Building...
[06:33:59 PM] Deploying 2 profiles...
[06:33:59 PM] Wrote Web Application Module to /data/development/ENTW_12.1.3.0.0/QT/BlogReadConfigFile/BRCFViewController/deploy/BlogReadConfigFile_BRCFViewController_webapp.war
[06:33:59 PM] Wrote Enterprise Application Module to /data/development/ENTW_12.1.3.0.0/QT/BlogReadConfigFile/deploy/BlogReadConfigFile.ear
[06:33:59 PM] Elapsed time for deployment:  1 second
[06:33:59 PM] ----  Deployment finished.  ----

Next step is to open the admin console and to deploy the ear file


If we run the application we see that the application tries to read the properties file in the log window
Running Application

Running Application


We now want to change the properties file the application is using. For this we change the location and name of the properties file we configured in the web.xml file and change the content of the properties file:

The UI has not changed, however, after you click the refresh Properties button and look into the log output you see
Application Tries to Read Properties from New Location

Application Tries to Read Properties from New Location


Please note that the location of the properties file has changed. If we had the file at the position defined in the deployment plan, the application would have read the properties from there.

The Git Experience (Part 3)

Almost a year ago I posted the second part of my git experience series (JDeveloper 11.1.1.6.0: The Git Experience (Part 2)). In this part I announced a third part which should handle working with remote git repositories.

I totally forgot about this. Only after a question on OTN JDeveloper and ADF forum I looked up the older blogs about git. Since the last post a new JDev version 12.1.3 has arrived so I decided to use this new version to continue the series.

In this part we are talking about setting up a remote repository. As remote git server I use GitHub which allows you to easily set up an account for non commercial use without any cost. Please read the all about the process on the GitHub Help web page.

Before we begin we have to talk about security accessing the remote repository. There are multiple possible connection strategies. The more common are ssh and https. Both are supported by GitHub, https is the recommended.

In this blog I use https to authenticate and work with GitHub, all you need to remember is your GitHub username and it’s password. I tested ssh too but like https better as you don’t have to handle the ssh keys on every pc you use (and I use up to 10 different machines).

I assume you have built yourself an account on GitHub which you can use. If you don’t want to create one yourself you can use the repository of this blog (link available at the end of the blog). Creating a new remote repository yourself can’t be done without an account. For those of you part 4 of the series which will be published in a couple of days will talk about how to do real work with remote repositories and introduce GitFlow as work template.

Let’s start with a typical workflow. We want to implement a new application using JDev 12.1.3 and want to use git as version control system. As the development is done on multiple locations, the repository should be accessible for all team members 24/7. This exclude a repository on a local pc which others might not be able to reach (e.g. if it’d down).

We generate a new ADF Fusion Web Application. I spare the walk through this process. As name of the new application workspace we choose BlogReadConfigFile, every other name you like is OK too.

Once the new application workspace is ready we like to put it under git control. We first do this locally by using the ‘Team->Version Application…’ menu. See the gallery below fro the whole process.

Notice the ‘.git’ folder which holds all information about the git versions of the application on your local pc. The fresh created local repository can be used already to make changes and commit them to the local repository. Other team members can clone the repository from your pc if they have access to it.

As the new workspace is under version control we can and should open a new window in JDev via menu ‘ Window->Team->Version’ to get a view of the version information available in JDev:

Open Version Window

Open Version Window

Expanded Version Window

Expanded Version Window

As you’ll notice all remote nodes in the tree are empty. The local master branch is the current branch we are working on, visible through the green badge. Now we want to make this local repository available to a remote git server GitHub. First problem is that JDev 12.1.3 (and all other version I know of) don’t support creating a remote repository. We can add existing remote repositories by right clicking to the ‘Remotes’ node and ‘add remote…’

Add a Remote Repository

Add a Remote Repository

Add Remote Dialog

Add Remote Dialog

As you see you can only add the URL to the remote repository but can’t create it. So we have to create the remote repository on GitHub first. Read ‘Setup a new Repository’ to find out how to do this. Make sure you make it a public repository and leave the check box to init the repository with a README.md file empty for the moment.

Create Repository in GitHub

Create Repository in GitHub

You can add default ‘.gitignore’ fie and a licence via this dialog too. We use a ‘.gitignore’ file from other JDev projects as the default java one doesn’t know about some of the artifacts used by JDev. After finishing the dialog you get the information from GitHub how to get data into the new repository.

How to add Data

How to add Data

If you decide to use ssh instead of https you can click the button on the left side of the URL and the info switches accordingly.

As we already have our local repository we use the second method ‘…or publish an existing repository from the command line’. For this we open a command shell and open the location of the workspace (‘/data/development/ENTW_12.1.3.0.0/QT/BlogReadConfigFile’ on my machine). Copy the commands from the second option into the command shell. You are asked for your credentials to login into GitHub, after that the local repository is pushed to GitHub.

Command Lines to Push Data to Repository

Command Lines to Push Data to Repository

When we look at the GitHub web page we see the data from the workspace

Finished Remote Repository

Finished Remote Repository

As mentioned at the button of the page we should add a README.md file. This can be done directly on the web page or you can add such a file on your local pc and push it to the repository. I like to do this through the web page as it copies the description from the creation of the repository into the file.

Add README.md

Add README.md

Now if we look at the Versions window in JDev we see that the remote repository is visible in JDev too.

Updated Version Window

Updated Version Window

Dev knows about the changes as they all in the ‘.git’ folder in the workspace. Right now command line git , JDev git and third party tools like SmartGit, SourceTree or mysygit (or Tortoise Git on windows) play well together. You can use them interchangeably.

As we have added the README.md file using the web page, out first action is to refresh the workspace to get the README.md file into our local repository. For this we use the menu ‘Team->Git->Pull…’, work through the wizard and check the file system after the work has finished.

This concludes this part 3 of the series. You can clone the repository from GitHub. Stay tuned for the next part where we introduce GitFlow and start working on the repository.

JDev: Always Test Your App with ApplicationModule Pooling turned off

In the last couple of weeks I saw a couple of question which mentioned a sporadic misbehavior of the application under different circumstances. In the end they could be answered:

“Your application has not been tested with application module pooling turned off”

Whenever you encounter a sporadic misbehavior of the application under different circumstances, this should ring a bell. Most developers came across a situation like this when programming ADF applications. At some point the application does not react normal or throws exceptions. The errors are not reproducible (most of the times) and you only see them on the production server (never on the developer machine).

The first time I came across this problem it took me about a week to figure it out and solve it. Basically the problem has been private data (stored with the session) which is part of the application module and is stored in the user data hash map. This is not a problem as long as you can guarantee that each user always works with the same application module. This is the case when you test run your application on the developers machine (you are the only user and the application module pool always returns the same application module to the client). On a production server where more then one user uses the application at the same time, the application may be forced to reuse an application module which was formally used by a different user. At this point application module pooling take over.

The general algorithm used is that the application module pool has a number of application module available to use. If more requests arrive the pool generates the additional module until a high water mark is reached. Further requests getting rejected. Currently not used modules are given back to the pool and are available for the next request. The pool tries to return the same application module as long as it’s available to the same session for subsequent requests. If it is not available it uses a currently available module stores the current status of the module into a store (DB or file), clears the module from all information and reconstructs the state from the other session from saved state information. Once the application module is restored you can’t distinguish if it’s a new application module or a reused one. This way your application don’t need an module for each user request, but it shares the available modules between the requests, saving lots of resources.

All this can be read about here 43.2 Introduction to Fusion Web Application State Management.

After this more theoretical prologue, lets do a practical project (workspace for JDev 11.1.2 available, see end of article). To make it as simple as possible, but still useful for anybody running into problems with activation/passivation, we use the HR schema and try to emulate a scenario where a user only sees employee data which depends on a department number. This department number should be set in the application module and be accessible for all queries. In a real world scenario this information is connected to the login of a user and stored in a central place. In the sample we use the user data of the application module to store the number.
The applications start page (I do spare the login part) has a af:query panel to select employees which might be filtered by their last name. As there is no login I added a field to enter the department number which should be used to further filter the result set.The Web UI lokke like

Test app UITest Application UI

As you see there is an input field for the last name, in the bottom era an input field to insert the department number and an other panel to retrieve the currently set department number from the application module.
Lets have a look at the service method to get/set the department number and how it is stored:

    private static final String PRIVATEDATA = "privData";

    public void setPrivateToUserData(String aVal) {
        mLogger.info("Set PrivData:" + aVal);
        if (aVal == null)
            return;

        Session lSession = getSession();
        if (lSession == null) {
            mLogger.warning("getSession returned null!");
            return;
        }
        Hashtable lHashtable = lSession.getUserData();
        if (lHashtable == null) {
            mLogger.warning("getUserData returned null!");
            return;
        }
        lHashtable.put(PRIVATEDATA, aVal);
    }

    public String getPrivateToUserData() {
        Session lSession = getSession();
        if (lSession == null) {
            mLogger.warning("getSession returned null!");
            return null;
        }
        Hashtable lHashtable = lSession.getUserData();
        if (lHashtable == null) {
            mLogger.warning("getUserData returned null!");
            return null;
        }
        String lData = (String) lHashtable.get(PRIVATEDATA);
        mLogger.info("Get PrivData: " + lData);
        return lData;
    }

As you see the string from the UI is stored and retrieved under the key PRIVATEDATA = “privData” in the userData hash map of the application module. This is the place to store data for the current user session (Storing Information About the Current User Session)
The configuration of the application module is the default you see after generating a ‘Fusion Web Application’.

ApplicationModule Default Configuration

ApplicationModule Default Configuration


As you can see ‘Application Module Pooling’ is on. When we run the application, set the department number to e.g. 30, send the data to the AM and hit the search button in the query panel we see
Pooling On

Pooling On


You can hit the edit button in a row to edit the employee and come back to the page and see the application running as expected.
Now, we switch ‘Application Module Pooling’ off and run again:
Application Module Pooling Off

Application Module Pooling Off


Doing the same actions as earlier it look like the application does not see the department number at all:
Application Module Pooling Off

Application Module Pooling Off


Even if you set the department number and directly hit the ‘Get User Data’ button you’ll don’t get the department number back.

The reason for this behavior is that we store the department number in the user data hash map which is NOT passivated when the application module is given back to the pool and given to an other requester. This happens every time you go the the server when am pooling is switched off.
What we need to to is to passivate the session user data together with the other state data stored by the framework and load it back when the AM is requested the next time (when it gets activated again). To do this we have to overwrite two methodes in the ApplicationModuleImpl class.

    @Override
    protected void activateState(Element aElement) {
        super.activateState(aElement);
        mLogger.info("++++++++++ activateState");

        Hashtable lData = getSession().getUserData();
        if (aElement != null) {
            // 1. Search the element for any <PrivData> elements
            NodeList nl = aElement.getElementsByTagName(PRIVATEDATA);
            if (nl != null) {
                // 2. If any found, loop over the nodes found
                for (int i = 0, length = nl.getLength(); i < length; i++) {
                    // 3. Get first child node of the <PrivData> element
                    Node child = nl.item(i).getFirstChild();
                    if (child != null) {
                        // 4. Set the data value to the user data hashmap
                        String lDataString = child.getNodeValue();
                        String[] lSplitkeyval = lDataString.split(";");
                        for (int ii = 0; ii < lSplitkeyval.length; ii++) {
                            mLogger.fine("..."+lSplitkeyval[ii]);
                            String[] lSplit = lSplitkeyval[ii].split("=");
                            lData.put(lSplit[0], lSplit[1]);
                        }
                        break;
                    }
                }
            }
        }
    }


    @Override
    protected void passivateState(Document aDocument, Element aElement) {
        super.passivateState(aDocument, aElement);
        mLogger.info("---------- passivateState");

        // 1. Retrieve the value of the user data to save and build a string representation
        Session lSession = getSession();
        Hashtable lData = lSession.getUserData();
        String lDataString = "";
        Set<String> keyset = lData.keySet();
        if (!keyset.isEmpty()) {
            Iterator<String> keys = keyset.iterator();
            while (keys.hasNext()) {
                String key = keys.next();
                mLogger.fine("..."+key + "=" + lData.get(key));
                lDataString += key + "=" + lData.get(key) + ";";
            }
        }

        // 2. Create an XML element to contain the value
        Node node = aDocument.createElement(PRIVATEDATA);
        // 3. Create an XML text node to represent the value
        Node cNode = aDocument.createTextNode(lDataString);
        // 4. Append the text node as a child of the element
        node.appendChild(cNode);
        // 5. Append the element to the parent element passed in
        aElement.appendChild(node);
    }

The passivateState method gets a Document and an Element as parameter. After calling super() to let the framework do its work, we get the user data hash map and store each key-value pair in a string which is then appended as a node to the element we got as parameter. In a real world application I would use a java to XML serialization tool like XStream which is capable to store more complex data.
The activateState method gets an Element as parameter which we search for the node we save when passivateState was called and restore the user data hash map.

After putting the two methods in the ApplicationModuleImpl class (the one you get when you create the java classes for an application module) the application run OK again, application module pooling still turned off.

When you examine the sample workspace which you can get here BlogActivatePassivateSample_V1.zip (remove the ‘.doc’ extension after downloading the workspace!) you’ll notice, that EmployeesViewImpl class too have the two methods to store private data which is not automatically saved by the framework. Further there are log messages throughout the code to let you follow the action in the log window.

As you see it’s essential to test an application with application module pooling turned off to find activation/passivation errors before the application goes to production.