dvt:treemap showing node detail in popup

This post describes how to implement an dvt:treemap which shows a af:popup when the user clicks on a detail node in the map.
The documentation of the dvt:treemap component tell us that the dvt:treemapnode supports the af:showPopupBehaviortag and reacts on the ‘click’ and ‘mouseHover’ events.
This is part of the solution and allows us to begin implementing the use case. We add an af:showPopupBehavior to the nodes we want to show detail information for.

After creating a default Fusion Web Application which uses the HR DB schema, we begin with creating the data model for the model project. For this small sample the departments and employees tables will be sufficient.


The views are named according to their usage to make it easier to understand the model. This is all we need for the model.

Let’s start with the UI which only consist of a single page. The page has a header part and a center part. In the center area we build the treemap by dragging the Departments from the data controls onto the page and dropping it as treemap. After that, in the dialog we specify the first level of the map to be the departmentId (which shows the department name as the label) and the for the second level we choose the employeeId (which shows the last name of the employee as label) from the employees. The whole process is shown in the gallery below.


The resulting treemap is very basic in it’s features, e.g. there is no legend as you see later.
In the next step we create an af:popup to show the nodes detail information. This process is outlined in the next gallery. We drag the popup component onto the page below the af:treemap component

One thing to take note of are the properties of the popup. First we set the content delivery to ‘lazyUncached’, which makes sure that the data is loaded every time the popup is opened. Otherwise we’ll see only the data from the first time the popup has been opened. Second change is to set the launcherVar to ‘source’. This is the variable name we later use to access the node data. Third change is to set the event context to ‘launcher’. This means that events delivered by the popup and its descendents are delivered in the context of the launch source.

The treemap for example, when an event is delivered ‘in context’ then the data for the node clicked is made ‘current’ before the event listener is called, so if getRowData() is called on the collectionModel in the event listener it will return the data of the node that triggered the event. This is exactly what we need.

Finally we add a popupFetchListener to the popup which we use to get the data from the current node to a variable in the bindings. In the sample this variable ‘nodeInfo’ is defined in the variable iterator of the page and an attribute binding ‘nodeInfo1’ is added. More info on this can be found here.


The code below shows the popupFetchListener:

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

import javax.el.ELContext;
import javax.el.ExpressionFactory;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;

import oracle.adf.model.BindingContext;
import oracle.adf.share.logging.ADFLogger;
import oracle.adf.view.rich.event.PopupFetchEvent;

import oracle.binding.AttributeBinding;
import oracle.binding.BindingContainer;


/**
 * Treemap handler bean
 * @author Timo Hahn
 */
public class TreemapBean {
    private static ADFLogger logger = ADFLogger.createADFLogger(TreemapBean.class);

    public TreemapBean() {
    }

    /**
     * listen to popup fetch.
     * @param popupFetchEvent event triggerd the fetch
     */
    public void fetchListener(PopupFetchEvent popupFetchEvent) {
        // retrieve node information 
        String lastName = (String) getValueFromExpression("#{source.currentRowData.lastName}");
        Integer id = (Integer) getValueFromExpression("#{source.currentRowData.EmployeeId}");
        //build info string
        String res = lastName + " id: " + id;
        logger.info("Information: " + res);
        // get the binding container
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();

        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("nodeInfo1");
        //set the value to it
        attr.setInputValue(res);
    }

    // get a value as object from an expression
    private Object getValueFromExpression(String name) {
        FacesContext facesCtx = FacesContext.getCurrentInstance();
        Application app = facesCtx.getApplication();
        ExpressionFactory elFactory = app.getExpressionFactory();
        ELContext elContext = facesCtx.getELContext();
        Object obj = elFactory.createValueExpression(elContext, name, Object.class).getValue(elContext);
        return obj;
    }
}

Finally we have to design the popup to show the node info from the attribute binding ‘nodeInfo1’. The popup uses a dialog with an af:outputText like

Show the node info in the popup

Show the node info in the popup


and set an af:showPopupBehavior to the node showing the employees

Running the finished application brings up the treemap, not pretty but enough to see this use case working. If we click on an employee node we see the popup with the last name of the employee and the employee id, the primary key of the selected row in the employees iterator.

You can download the sample application which was build using JDeveloper 12.1.3 and the HR DB schema from GitHub.

Advertisements

Pitfalls when using libraries of newer version than shipped with JDeveloper or WebLogic Server

A question on JDeveloper & ADF OTN forum cought my attention. A user wanted to use a method of the Apache Commons-IO library named FileUtils.getTempDiretory() but got an error when he tried to use code completion or when he tried to compile the code. The problem was that the compiler (or code completion) did not pick up the right java class from the library even as it was installed in the project a library.
As the original code used belongs to one of my samples I was interested in finding a reason for this behavior as I could see no obvious reason for this behavior.

An inspection of a provided test case quickly revealed the problem and a solution was found too. This blog is about the problem and the solution to it. Lets start with building a test case:


The test case had a model project which used a couple of libraries which we add too to make this sample as close as possible to the test case.
 Model Project Properties

Model Project Properties


There is no code whatsoever used in the model project just the libraries are defined!

To make use of the FileUtils.getTempDiretory() method we have to first download the Apache Commons-IO in a version higher then 2.0. The current version is 2.4 which you get from the given link. Once you unzip the zip (or tar.gz) to a directory of your choice we create a new library for JDeveloper (Tools->Manage Libraries…)

We add This new library to the view controller project


Next is to create a java bean where we try to use the FileUtils.getTempDiretory() method

Here we see the problem mentioned in the OTN question. The FileUtils.getTempDiretory() does not show up at all. The JavaDoc of the Apache Commons-IO 2.4 package shows that the method is available since version 2.0
JavaDoc of FileUtils Class

JavaDoc of FileUtils Class


If we try to compile the code we get an compilation error as seen in the last image.

What is the problem?
Well, it looks like there is another version of the Apache Commons-IO library already loaded in the classpath which gets loaded first. Once a library or class is loaded, another version of the same class will not overwrite the existing one.
First thing we can try is to move the new commons-io library to the top of the list of libraries.
In the test case presented here, this doesn’t work. We still get the same error. So there are libraries loaded before the view controller project libraries come to play.
Remember we added some libraries to the model project even as there is no code in the project at all?
Because the view controller project has dependency defined to the model project when we create an Fusion Web Application by default, libraries of the model project are loaded before the view controller projects.
We have can solve the problem in multiple ways:
1. remove the dependency to the model project. This is not recommended as it would mean that we have to build the model project ourselves if we have to change something in the model and want to run the application.
2. find the library which loads the FindUtils class and see if we can remove it (not all libraries are needed).
3. add the new Apache Commons-IO library to the model project and move it up front. This should load the newer version of the FindUtils class before any other.

Solution 1 isn’t really one. Solution 2 is possible and I’ll blog about it later. For this blog we use solution 3.

Solution
All we have to do is to add the Apache Commons-IO 2.4 library to the model project and move to the top of the list.

Model Project Properties with Commons-IO

Model Project Properties with Commons-IO


If we now rebuild the workspace we see that to error is gone
No Compilation Error

No Compilation Error


The code completion still shows the method red underlined. This is a bug in JDeveloper which doesn’t pick up the right library. Anyway, the compiler will use the right library and we can compile the application.

Now we add another method to the FileBean which returns the path to the temporary directory. This we use in a page index.jsf to show it on the ui.

<?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="Preferred Package Test" 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:outputText value="Tempdir path = #{FileBean.tempDir}" id="ot2"/>
          </af:gridCell>
        </af:gridRow>
      </af:panelGridLayout>
    </af:form>
  </af:document>
</f:view>

When we run the application we get an exception

    at weblogic.work.ExecuteThread.execute(ExecuteThread.java:311)
    at weblogic.work.ExecuteThread.run(ExecuteThread.java:263)
Caused by: java.lang.NoSuchMethodError: org.apache.commons.io.FileUtils.getTempDirectoryPath()Ljava/lang/String;
    at de.hahn.blog.preferredpackages.view.beans.FileBean.getTempDir(FileBean.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)

Why’s that?
The application compiled without an error and we still get a NoSuchMethodError. The reason for this is that when we start the WebLogic Server the older version of the Apache Commons-IO jar is loaded first, blocking loading of the newer version we need to get to the FileUtils.getTempDirectoryPath() method.
To allow the server to load our newer version of the jar we need to change a descriptor named weblogic-application.xml which is specific for WebLogic Server. For other servers there exist other descriptors allowing the same.
In this descriptor we add a preferred package for the org.apache.commons.io package. Open the weblogic-appliaction.xml descriptor and select the ‘Class Loading…’ node.

Application Descriptors: weblogic-application.xml

Application Descriptors: weblogic-application.xml


Here we enter the package name org.apache.commons.io to the ‘Application Preferred Libraries’ section.

to get this result in the source view of the descriptor:

    <prefer-application-packages>
        <package-name>org.apache.commons.io</package-name>
    </prefer-application-packages>

After restarting the application the index.jsf page show up OK

Running Test Page

Running Test Page

You can download the sample application which was build using JDeveloper 12.1.3 from GitHub.

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.

How-to filter ADF bound tables by date range (JDeveloper 12.1.x)

Based on an older article from Frank Nimphius How-to filter ADF bound tables by date range JDeveloper 11.1.1.4 I got a interesting question on the OTN JDeveloper & ADF forum why the solution provided in the article does not work in JDev 12c.

The solution from Frank’s article is designed for JDev 11.1.1.4.0. Today’s version of JDev is 12.1.3 where the solution does not seem to work. Migrating the source of the article and running it under JDev 12.1.3 indeed shows, that filtering the employees records for a date range does not work at all. Setting dates into the filter and hitting enter to activate the filter does not filter the data in the table.
The reason for this was easily found by debugging the code. Set a breakpoint into the query listener which is setup in the table

<af:table value="#{bindings.allEmployees.collectionModel}" var="row" 
  rows="#{bindings.allEmployees.rangeSize}"
  emptyText="#{bindings.allEmployees.viewable ? 'No data to display.' : 'Access Denied.'}"
  fetchSize="#{bindings.allEmployees.rangeSize}" rowBandingInterval="0"
  filterModel="#{bindings.allEmployeesQuery.queryDescriptor}" filterVisible="true" 
  varStatus="vs" selectedRowKeys="#{bindings.allEmployees.collectionModel.selectedRow}"
  selectionListener="#{bindings.allEmployees.collectionModel.makeCurrent}" 
  rowSelection="single" id="t1" styleClass="AFStretchWidth"  partialTriggers="::cb1"
  queryListener="#{EmployeeQueryBean.onEmployeeQuery}">

As you can see it’s pointing to a bean method ‘onEmplyoeeQuery’. A look into this method reveals that the method FilterableQueryDescriptor.getFilterCriteria() has been deprecated.

        FilterableQueryDescriptor fqd = (FilterableQueryDescriptor) queryEvent.getDescriptor();
        Map map = fqd.getFilterCriteria();

Instead of the deprecated method you should use the method FilterableQueryDescriptor.getFilterConjunctionCriterion() which now holds the map of parameters.

        FilterableQueryDescriptor fqd = (FilterableQueryDescriptor) queryEvent.getDescriptor();
        ConjunctionCriterion cc = fqd.getFilterConjunctionCriterion();
        Map<String, Criterion> criterionMap = cc.getCriterionMap();

When you set a breakpoint in this method and step through the code you see that the values entered into the filter fields in the UI are not visible in the map as Frank describes in his article.

Criterion Map and old FilterCriteria Map

Criterion Map and old FilterCriteria Map


As you can see there are no map entries for the made up variables ‘HireStartRange’ and ‘HireEndRange’. This is the reason the filter by date range does not work. There are simply not dates to filter the rows.

I’m not sure if this is a bug or a change in behavior which was made for a reason. Anyway, you can’t just simply add values to the map anymore.

The solution to fix the problem is simple. As you can’t store additional values in the criterion map, you have to store the values entered by the user somewhere else. A valid storage area is the variables iterator each pagedef holds.
In one of my other blogs Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef I showed how to add temporary variables in this iterator.

Create two new variables inside the variable iterator of type oracle.jbo.domain.Date, name them ‘startDate’ and ‘endDate’. Then create attribute bindings for them.
The final touch is to wire the new variables up in the HireDate filter for start range and end range:

                                    <af:column sortProperty="HireDate" filterable="true" sortable="true"
                                               headerText="#{bindings.allEmployees.hints.HireDate.label}" id="c1" width="277">
                                        <f:facet name="filter">
                                            <af:panelGroupLayout id="pgl2" layout="horizontal">
                                                <af:panelLabelAndMessage label="From: " id="plam1">
                                                    <af:inputDate id="id2" value="#{bindings.startDate1.inputValue}" clientComponent="false">
                                                        <af:convertDateTime pattern="#{bindings.allEmployees.hints.HireDate.format}"/>
                                                        <f:validator binding="#{bindings.HireDate.validator}"/>
                                                    </af:inputDate>
                                                </af:panelLabelAndMessage>
                                                <af:spacer width="5" height="5" id="s1"/>
                                                <af:panelLabelAndMessage label="To:" id="plam2">
                                                    <af:inputDate id="id3" value="#{bindings.endDate1.inputValue}" required="false" clientComponent="false">
                                                        <f:validator binding="#{bindings.HireDate.validator}"/>
                                                        <af:convertDateTime pattern="#{bindings.allEmployees.hints.HireDate.format}"/>
                                                    </af:inputDate>
                                                </af:panelLabelAndMessage>
                                            </af:panelGroupLayout>
                                        </f:facet>
                                        <af:inputDate value="#{row.bindings.HireDate.inputValue}" label="#{bindings.allEmployees.hints.HireDate.label}"
                                                      required="#{bindings.allEmployees.hints.HireDate.mandatory}"
                                                      shortDesc="#{bindings.allEmployees.hints.HireDate.tooltip}" id="id1" styleClass="AFStretchWidth">
                                            <f:validator binding="#{row.bindings.HireDate.validator}"/>
                                            <af:convertDateTime pattern="#{bindings.allEmployees.hints.HireDate.format}"/>
                                        </af:inputDate>
                                    </af:column>

The code above shows the new column for the HireDate and the new storage location for the startDateRange as ‘value=”#{bindings.startDate1.inputValue}”‘ and EndDateRange as ‘value=”#{bindings.endDate1.inputValue}”‘. Next we change the bean method which reads the filter values and calls the query:

    public void onEmployeeQuery(QueryEvent queryEvent) {
        //default EL string created when dragging the table
        //to the JSF page
        //#{bindings.allEmployeesQuery.processQuery}

        BindingContext bctx = BindingContext.getCurrent();
        DCBindingContainer bindings = (DCBindingContainer) bctx.getCurrentBindingsEntry();

        //access the method bindings to set the bind variables on the ViewCriteria
        OperationBinding rangeStartOperationBinding = bindings.getOperationBinding("setHireDateRangeStart");
        OperationBinding rangeEndOperationBinding = bindings.getOperationBinding("setHireDateRangeEnd");

        // get the start date and end date from the temporary valiables
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("startDate1");
        oracle.jbo.domain.Date sd = (oracle.jbo.domain.Date) attr.getInputValue();
        attr = (AttributeBinding) bindings.getControlBinding("endDate1");
        oracle.jbo.domain.Date ed = (oracle.jbo.domain.Date) attr.getInputValue();

        //set the start and end date of the range to search
        rangeStartOperationBinding.getParamsMap().put("value", sd);
        rangeEndOperationBinding.getParamsMap().put("value", ed);

        //set bind variable on the business service
        rangeStartOperationBinding.execute();
        rangeEndOperationBinding.execute();

        invokeMethodExpression("#{bindings.allEmployeesQuery.processQuery}", Object.class, QueryEvent.class, queryEvent);
    }

In line 14-17 you see that we read the values from the newly created attribute bindings for the temporary variables. After removing the unnecessary parts of the code, which tried to read the values from the map, the rest of the code remains as is.

Here is an image of the now working filter by date range

Filter Table by Date Range

Filter Table by Date Range

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. You can download the changed code for the sample from GitHub

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.

Handling images/files in ADF (Part 5)

I received a couple of questions regarding the handling of the images directly after upload for the sample application done in part 1-4.

    Part 1 gives an overview of the sample application I’m going to build and how to set it up
    Part 2 shows how to upload a file, store it and download it back to the client
    Part 3 implements two techniques to show the data (image) on the user interface
    Part 4 backport of the sample to JDeveloper 11gR1
    Part 5 implements a technique to show the uploaded file right after upload without the need to commit first

The sample application finished in part 3 (part 4 is a backport to JDev 11gR1 only) has one minor glitch: it doesn’t display an uploaded image directly to the user after uploading it. The user has to commit the data after insert or update of an image before the image becomes visible. Users like to see the newly uploaded image before committing the row. This allows the users to cancel the change or select and upload another image. In this 5th part of the series we implement this.

Before we start to implement let’s talk about how to implement this enhancement. Why isn’t it possible to upload the image data into the blob and then just show the image from the blob via the servlet (see part 3)?
The problem is that the BlobDomain uses a stream to read the data uploaded from the user. This stream can only be read after the BlobDomain is saved, meaning after the commit.

The solution we implement in this part stores the uploaded data (inserted or updated) in a temporary file on the server. Then the server uses the image data from the temporary file to visualize the data. This sounds easy enough, however there is some house keeping to do to make it work.

First we have have to find a place (folder) where we can store the uploaded data until it’s stored in the db or the operation is canceled. Then we need to distinguish which data to show from the servlet (file or blob). Finally we have to clean up the temporary file when we are done.

Lets dive into the implementation. We start from the application at the end of part 3. As the current JDeveloper version is 12.1.3 we do the implementation in this version. The first task is to migrate the old application to 12.1.3. This is done automatically when opening the old work space in JDev 12.1.3 by answering the ‘OK’ to the migration popup. Nothing need to be done here. However, when you download  the work space you’ll notice some clean up I did, like changing the old af:commandButton to the new af:button.

One thing to notice is that the Apache Commons-IO version is updated to 2.4. This update made one other change necessary in the weblogic-application.xml file.

  <prefer-application-packages>
    <package-name>org.apache.commons.io</package-name>
  </prefer-application-packages>

This entry allows the application to use the included commons-io jar to be loaded before the already available commons-io jar, of an older version, in WebLogic server 12.1.3.

Here are the steps we take to implement the tasks:
1) Save the uploaded data to a temporary file as well as to the blob. This is done for convenience. It’S possible to store the data first in the temporary file and only copy it to the BlobDomain when the user commits the changes.
We implement a new java class UploadBlob which holds the BlogDomain and the path to the temporary file. This class also allows to test if a temporary is available.

package de.hahn.blog.uldl.view.types;

import oracle.jbo.domain.BlobDomain;

/**
 * This type class holds the BlogDomain and a path to a temporary file holding the uploaded image data
 */
public class UploadBlob {
    /**
     * Holds the uploaded data
     */
    BlobDomain dataBlob;

    /**
     * Path to the temporary file if availabe
     */
    String tempFile;

    /**
     * C'tor.
     */
    public UploadBlob() {
        super();
        tempFile = null;
        dataBlob = null;
    }

    /**
     * Gets the status of the temporary file
     *
     * @return true if a temporary file is available, false otherwise
     */
    public Boolean getTempFileAvailabe() {
        return (tempFile != null ? Boolean.TRUE : Boolean.FALSE);
    }

    /**
     * @param inageBlob
     */
    public void setInageBlob(BlobDomain dataBlob) {
        this.dataBlob = dataBlob;
    }

    /**
     * Gets the BlobDomain holding the uploaded data
     * @return
     */
    public BlobDomain getDataBlob() {
        return dataBlob;
    }

    /**
     * Sete the path to the temporary file holding the uploaded data
     * @param tempFile path to the temporary file
     */
    public void setTempFile(String tempFile) {
        this.tempFile = tempFile;
    }

    /**
     * Getter for path to temp file holding the data of the uploaded data
     * @return path to the temporary file holding the uploaded data
     */
    public String getTempFile() {
        return tempFile;
    }
}

2) Use this class in the ImageBean.java class where the uploaded data is read. This happens in the valueChangeListener uploadFileValueChangeEvent(ValueChangeEvent valueChangeEvent).

    /**
     * @param valueChangeEvent
     */
    public void uploadFileValueChangeEvent(ValueChangeEvent valueChangeEvent) {
        // The event give access to an Uploade dFile which contains data about the file and its content
        UploadedFile file = (UploadedFile) valueChangeEvent.getNewValue();
        // Get the original file name
        String fileName = file.getFilename();
        // get the mime type
        String contentType = ContentTypes.get(fileName);
        // get the current roew from the ImagesView2Iterator via the binding
        DCBindingContainer lBindingContainer = (DCBindingContainer) BindingContext.getCurrent().getCurrentBindingsEntry();
        DCIteratorBinding lBinding = lBindingContainer.findIteratorBinding("ImagesView2Iterator");
        Row newRow = lBinding.getCurrentRow();
        // set the file name
        newRow.setAttribute("ImageName", fileName);
        // create the BlobDomain and set it into the row
        UploadBlob blob = createBlobDomain(file, Boolean.TRUE);
        newRow.setAttribute("ImageData", blob.getDataBlob());
        // set the mime type
        newRow.setAttribute("ContentType", contentType);
        String tmp = (blob.getTempFileAvailabe() ? blob.getTempFile() : null);
        setTemporaryFileVar(tmp);
        UIComponent ui = (UIComponent) valueChangeEvent.getSource();
        // PPR refresh a jsf component
        ui = ui.getParent();
        AdfFacesContext.getCurrentInstance().addPartialTarget(ui);

    }

Instead of reading the data into the BlobDomain a changed method createBlobDomain is called (line 18). the method now returns an instance of the new class UploadBlob. Below is the code of the new method:

    private UploadBlob createBlobDomain(UploadedFile file, Boolean createTempFile) {
        // init the internal variables
        InputStream in = null;
        OutputStream outTmp = null;
        UploadBlob blobDomain = null;
        OutputStream out = null;
        File tempfile = null;
        logger.info("Starting to create UploadBlog from data...");
        try {
            logger.info("... create BlobDomain...");
            blobDomain = new UploadBlob();
            // Get the input stream representing the data from the client
            in = file.getInputStream();
            // if a temporary file should be created , we do this first as we can't get
            // data data back from the blob until we commit the row. in the next step we
            // write the upload data to a temp file and then copy it into the blob
            if (createTempFile) {
                logger.info("... Creating temporary file...");
                File tempdir = FileUtils.getTempDirectory();
                String ext = FilenameUtils.getExtension(file.getFilename());
                if (!ext.isEmpty()) {
                    ext = "." + ext;
                }
                logger.info("... set extension to " + ext + "...");
                tempfile = File.createTempFile("upl", ext, tempdir);
                logger.info("... " + tempfile.getAbsolutePath() + "...");
                // set path to temporary file
                blobDomain.setTempFile(tempfile.getAbsolutePath());
                FileOutputStream fileOutputStream = FileUtils.openOutputStream(tempfile);
                logger.info("... copy data to temporary file...");
                IOUtils.copy(in, fileOutputStream);
                in = FileUtils.openInputStream(tempfile);
                logger.info("... set inputstream for blog to temporary file...");
            }
            // create the BlobDomain datatype to store the data in the db
            blobDomain.setInageBlob(new BlobDomain());
            // get the outputStream for hte BlobDomain
            out = blobDomain.getDataBlob().getBinaryOutputStream();
            // copy the input stream into the output stream
            logger.info("... copy data to BlobDomain ...");
            /*
             * IOUtils is a class from the Apache Commons IO Package (http://www.apache.org/)
             * Here version 2.0.1 is used
             * please download it directly from http://projects.apache.org/projects/commons_io.html
             */
            IOUtils.copy(in, out);
            logger.info("... Finished OK");
        } catch (Exception e) {
            logger.severe("Error!", e);
            if (tempfile != null) {
                // delete temp file on exception but don'T throw one if there is another exception
                logger.info("Deleted temporary file " + tempfile.getAbsolutePath());
                FileUtils.deleteQuietly(tempfile);
            }
        }
        // return the filled BlobDomain
        return blobDomain;
    }

Depending on the new boolean parameter passed to the method a temporary file is created and the uploaded data is first saved to the temporary file. After that the data is copied from the temporary file into the BlobDomain. At this point the path to the temporary file is saved in the new class for later reference. In case of an exception the temporary file is removed.
Finally in line 22 and 23 of the value change listener we check if a temporary file was generated and we set the path to it to a pageDef variable (see Creating Variables and Attribute Bindings to Store Values Temporarily in the PageDef). For this we use the code below.

    /**
     * Set the temporary file name into a page variable for later use
     * @param name
     */
    private void setTemporaryFileVar(String name) {
        // set pathto temporary file to page variable
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("TemporaryFile1");
        if (attr != null) {
            attr.setInputValue(name);
        }
    }

The variable is used in the af:image component in the editImage.jsff fragment

                   <af:image source="/render_image?id=#{bindings.ImageId.inputValue}&tmp=#{bindings.TemporaryFile1.inputValue}" id="i1"
                              shortDesc="#{bindings.ImageName.hints.tooltip}" inlineStyle="width:200px;" partialTriggers="cb3" visible="true"/>

here the path to the temporary file is passed to the servlet as second parameter ‘tmp’. In lines 24-27 of the value change listener we send a ppr to the parent component of the af:image to show the now uploaded image.

Another thing to do is to cleanup after the user either cancel or commit the changes. This is done in the cancel_action() or the commit_action() in the ImageBean. Here we call the deleteTemporaryFile() method which checks the existence of a temporary file and deletes it.

    /**
     * delete the temporary file if is present
     */
    public void deleteTemporaryFile() {
        String tempfile = getTemporaryFileVar();
        removeTemporaryFile(tempfile);
        setTemporaryFileVar(null);
    }

3) The final part of the implementation is done in the servlet which is used to get the data back to the client. This is simple as we read the second parameter passed to the servlet. If it’s not empty we always read the image data from the temporary file. If the parameter is empty the servlet gets the data by reading the row from the DB and read the data from the blob. Here are the relevant parts from the servlet:

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        StringBuilder sb = new StringBuilder(100);
        String appModuleName = "de.hahn.blog.uldl.model.facade.ULDLAppModule";

        sb.append("ImageServlet ").append(appModuleName);

        try {
            // get parameter from request
            Map paramMap = request.getParameterMap();
            oracle.jbo.domain.Number id = null;
            String tmporaryFilePath = "";
            if (paramMap.containsKey("id")) {
                String[] pVal = (String[]) paramMap.get("id");
                id = new oracle.jbo.domain.Number(pVal[0]);
                sb.append(" id=").append(pVal[0]);
            }
            // check if we find a temporary file name. In this case we allways use this!
            if (paramMap.containsKey("tmp")) {
                String[] pVal = (String[]) paramMap.get("tmp");
                tmporaryFilePath = pVal[0];
                sb.append(" tmp=").append(pVal[0]);
            }

            OutputStream outputStream = response.getOutputStream();
            InputStream inputStream = null;
            BlobDomain image = null;
            String mimeType = null;
            // no temporary file path given, read everything from DB
            if (tmporaryFilePath.isEmpty()) {
                // get method action from pagedef
                BindingContext bindingContext = BindingContext.getCurrent();
                DCBindingContainer amx = bindingContext.findBindingContainer("de_hahn_blog_uldl_view_image_dummyPageDef");
                JUCtrlActionBinding lBinding = (JUCtrlActionBinding) amx.findCtrlBinding("getImageById");
                // set parameter
                lBinding.getParamsMap().put("aId", id);
                // execute method
                lBinding.invoke();
                // get result
                Object obj = lBinding.getResult();
                ImageAccessViewRow imageRow = (ImageAccessViewRow) obj;

                // Check if a row has been found
                if (imageRow != null) {
                    // Get the blob data
                    image = imageRow.getImageData();
                    mimeType = imageRow.getContentType();
                    // if no image data can be found and no temporary file is present then return and do nothing
                    if (image == null) {
                        mLogger.info("No data found !!! (id = " + id + ")");
                        return;
                    }
                    inputStream = image.getInputStream();
                } else {
                    mLogger.warning("No row found to get image from !!! (id = " + id + ")");
                    return;
                }
                sb.append(" ").append(mimeType).append(" ...");
                mLogger.info(sb.toString());
            } else {
                // read everything from temporary file path
                mimeType = ContentTypes.get(tmporaryFilePath);
                File file = FileUtils.getFile(tmporaryFilePath);
                FileInputStream fileInputStream = FileUtils.openInputStream(file);
                inputStream = fileInputStream;
            }

            // Set the content-type. Only images are taken into account
            response.setContentType(mimeType + "; charset=utf8");
            IOUtils.copy(inputStream, outputStream);
            if (tmporaryFilePath.isEmpty()) {
                // cloase the blob to release the recources
                image.closeInputStream();
            }
            inputStream.close();
            // flush the outout stream
            outputStream.flush();
        } catch (Exception e) {
            mLogger.log(Level.WARNING, "Fehler bei der Ausführung: " + e.getMessage(), e);
        } finally {
            mLogger.info("...done!");
        }
    }

The gallery below shows the new work flow.

The work space for part 5 can be downloaded from the ADF EMG Sample side BlogUploadDownload_12.1.3V4.zip.
Or if you are in GIT you can get the work space from GitHub BlogUploadDownload_12.1.3V4

Lazy Initalizing Beans

A questions on the OTN JDeveloper & ADF forum asked how to initialize attributes with data in a bean before showing the attributes.
The first solution which comes to mind is to initialize attributes and data in the constructor of the bean. This however is not a wise thing to do as you can’t predict when the constructor is called and in which phase of the life cycle you are (as it depends on the scope of the bean).
Here you can use a solution I call lazy initializing the data of a bean. It’s based on the assumption that once the page tries to get an attribute from the bean, the life cycle already has done all other initializations (like bindings). Now if we defer the init of the bean data until a (or the first) getter is called we are save.
To implement this we set an attribute to null and check if it’s null in the getter of the attribute. If we find it’s still null, we init the data and set the attribute to a not null value. Here is the sample code:

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

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

import javax.servlet.ServletContext;

import oracle.adf.model.BindingContext;
import oracle.adf.share.logging.ADFLogger;

import oracle.binding.AttributeBinding;
import oracle.binding.BindingContainer;

public class LazyInitBean {
    private static transient final ADFLogger LOGGER = ADFLogger.createADFLogger(LazyInitBean.class);

    private String myName;

    public void setMyName(String myName) {
        LOGGER.info("set data: " + myName);
        this.myName = myName;
    }

    public String getMyName() {
        // lazy init the data only when it's null
        if (myName == null) {
            LOGGER.info("init data through layz init");
            initData();
        }
        return myName;
    }

    public LazyInitBean() {
        LOGGER.info("LazyInitBean: c'tor");
    }

    private void initData() {
        LOGGER.info("data intialized");
        // this method inits the beans attributes (only one here)!
        myName = "just init myself";
        //Get ServlerContexct
        FacesContext ctx = FacesContext.getCurrentInstance();
        ServletContext servletContext = (ServletContext) ctx.getExternalContext().getContext();
        // get the binding container
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();

        // get an ADF attributevalue from the ADF page definitions
        AttributeBinding attr = (AttributeBinding) bindings.getControlBinding("myTestValue1");
        if (attr != null) {
            String old = (String) attr.getInputValue();
            attr.setInputValue("NEW DEFAULT VALUE");
            LOGGER.info("LazyInitBean: setnew default value to 'NEW DEFAULT VALUE' old: " + old);
        } else {
            LOGGER.info("LazyInitBean: bindings not present!");
        }
    }

    /**
     * Force the init of the beans attributes
     */
    public void resetData() {
        LOGGER.info("LazyInitBean: reset!");
        // setting the myName to null causes a re initialization
        myName = null;
        // you can call initData() here too;
        initData();
    }

    public void buttonListener(ActionEvent actionEvent) {
        LOGGER.info("Action initData");
        resetData();
    }
}

Now, whenever in a page or fragment the getter to the bean parameter is called (this can be the getter to every property of a component which can have EL) the bean is checkes if the attribute is already initialized and if not calls the initData() method in the bean. If a value is present, the getter returns the value. The initData() method show that attributes in the binding layer can initialized this way too. This is shown with the myTestValue1 attribute which is defined as pageDef variable. You can overwrite the text in the inputText and when you click the button ‘Clear Data’ the data is initialized again removing the data from the inputText and setting it back to the initial values.
Below is a sample of a fragment which uses the bean to lazy initialize the attribute.

<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <af:panelGridLayout id="pgl1">
        <af:gridRow height="100%" id="gr1">
            <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                <!-- Content -->
                <af:panelGroupLayout id="pgl2" layout="vertical">
                    <af:outputText value="Lazy Region" id="ot1" inlineStyle="font-size:large;"/>
                    <af:inputText label="Name:" id="it1" value="#{backingBeanScope.LazyInitBean.myName}" partialTriggers="b1 b2"/>
                    <af:outputText value="Hello: #{backingBeanScope.LazyInitBean.myName}!" id="ot2" partialTriggers="b2 b1"/>
                    <af:button text="ShowMessage" id="b2"/>
                    <af:button text="Clear Data" id="b1" actionListener="#{backingBeanScope.LazyInitBean.buttonListener}"/>
                    <af:inputText label="MyTestValue" id="it2" value="#{bindings.myTestValue1.inputValue}" partialTriggers="b1"/>
                </af:panelGroupLayout>
            </af:gridCell>
        </af:gridRow>
    </af:panelGridLayout>
</ui:composition>

Be aware of the fact, that this solution depends on the component(s) which calls the getter which in due course init the data. As the getter methods of the components are called in the order they appear in the component tree you have be careful which getter you use. It should be the first if you want to setup everything up front, but use the last if you want to load data at the end of the page render cycle (e.g. to get more data from a web service but already see the page).

In a comment Aino Andriessen mentioned that you can use the @PostConstruct annotation on a method which then will be called whenever the bean is constructed after all other injections are done. That is the difference to lazy init method. The method is called every time after all other injections are done. If you only want to setup data if the data is really needed this is not possible this way. Nevertheless, to make the sample application show the @PostConstruct method too, I added a method postconstructMethod() in the bean, which writes a log message.

    @PostConstruct
    public void postconstructMethod() {
        LOGGER.info("PostConstruct Called!");
        // init everything here which can be done quickly and is needed to init UI components before showing them
        // or call initData() from here
    }

You can download the sample workspace from the ADF-EMG Sample Project page of get it from GitHub. The sample does not need a DB. The samples are updated to show the @PostConstruct technique.

JDev 12.1.3: Using Parent Action to Navigate Tabs of a af:panelTabbed from Inside a Region

This blog is based on a question in the OTN JDeveloper and ADF forum. The Question was how to navigate from one selected tab to the next tab when the af:showDetailItem in the tab is a region and the button to navigate is inside the region.

We implement two cases, the first is the easy one where the button to navigate is in the page holding the af:panelTabbed. The second one uses a button is inside a bounded task flow which is shown in the af:showDetailItem in a tab to navigate the af:panelTabbed.

We start with creating a new ‘ADF Fusion Web Application’ from the gallery. We only change the application name and the path of the application, otherwise we can just use the default values. The sample is simple and doesn’t need a model project or connection to a DB. You can download the finished workspace using the link provided at the end of the post.

We skip all the needed steps and going right into creating the starting page which holds the af:panelTabbed. It has three af:showDetailItem and a af:Button to navigate the tabs directly from the page. This button implements the first use case.

Start Page with Outer Navigation

Start Page with Outer Navigation

The button has a listener attached which is implemented in a viewScope bean ‘NavigateTabBean’. The listener implements the needed logic to navigate from the selected tab to the next tab. If the last tab is reached the first tab is selected.

    private static ADFLogger _logger = ADFLogger.createADFLogger(NavigateTabBean.class);
    private static final String PANELTAB = &quot;pt1&quot;;

    /**
     * Eventhandler to navigate to the next tab in a af:panelTabbed
     * @param actionEvent event which called the listener
     */
    public void naviGateButtonAction(ActionEvent actionEvent) {
        UIComponent ui = getUIComponent(PANELTAB);
        if (ui == null) {
            _logger.info(&quot;PanelTab component not found!&quot;);
            return;
        }
        if (!(ui instanceof RichPanelTabbed)) {
            _logger.info(&quot;Component is not an af:panelTabbed&quot;);
            return;
        }

        RichPanelTabbed rpt = (RichPanelTabbed) ui;
        int childCount = rpt.getChildCount();
        List&lt;UIComponent&gt; children = rpt.getChildren();
        for (int ii = 0; ii &lt; childCount; ii++) {
            UIComponent uiSDI = children.get(ii);
            if (uiSDI instanceof RichShowDetailItem) {
                RichShowDetailItem rsdi = (RichShowDetailItem) uiSDI;
                if (rsdi.isDisclosed()) {
                    //close current tab
                    rsdi.setDisclosed(false);
                    //calculate next tab to disclose as next_tab_index = (current_tab_index + 1) % number_of_tabs
                    int kk = ii + 1;
                    int jj = kk % childCount;
                    _logger.info(&quot;old disclosed tab: &quot; + ii + &quot; new disclodes tab: &quot; + jj);
                    RichShowDetailItem newSDI = (RichShowDetailItem) children.get(jj);
                    //open new tab
                    newSDI.setDisclosed(true);
                    AdfFacesContext.getCurrentInstance().addPartialTarget(rpt);
                    return;
                }
            }
        }
    }

    // find a jsf component
    private UIComponent getUIComponent(String name) {
        FacesContext facesCtx = FacesContext.getCurrentInstance();
        return facesCtx.getViewRoot().findComponent(name);
    }

    public void nextTab() {
        naviGateButtonAction(null);
    }

The logic in the action listener first searches for the af:panelTabbed in the viewRoot and gets the number of children from it. Each child is one of the af:showDetailItem representing a tab. Then we iterate over the child list and search the currently disclosed tab. We close this tab and the next tab in the list gets disclosed. If the currently selected tab is the last in the list, the first tab is disclosed (see the comments in the code section).

To Implement the second use case, the one we really want to talk about, we first need to implement three bounded task flows which we later use as regions in the tabs.

Bounden Task Flow with Parent Action

Bounden Task Flow with Parent Action

The image shows the bounded task flow for one tab. The other bounded task flows are build in the same way and are just showing different text. The reason for this is that you normally would use different regions aka different task flows in the tabs. We could have used only one bounded task flow with a parameter to change the text shown in the fragment. In the sample you’ll find this implemented for tabs 4 and 5.
The region is simple and only shows one fragment which has a button to navigate to the next tab and a test to distinguish the regions when navigating. The whole magic is the parent action in the bounded task flow. This parent action executes a navigation case ‘nextTab’ in the parent task flow.

Unbounded Task Flow with Start Page

Unbounded Task Flow with Start Page

In the image above we the the unbounded task flow which is the parent of the bounded task flow. Here a wild card rule navigates to a method call activity ‘selectNextTab’ using the navigation case ‘nextTab’ we entered to the parent action of the regions.
The method action calls the ‘nextTab()’ in the managed bean from the code section above. All this method does is to call the action listener which is called from the af:Button of the start page (Start.jsf). As the action listener needs an ActionEvent as parameter, which we don’t use in the code we pass ‘null’ when we call the listener from the method call activity.

This concludes the implementation. Here are some images from the running application

The sample application can be downloaded form ADFEMG Sample Project.

A version of the software build with JDeveloper 11.1.1.7.0 can be downloaded from GitHub

JDev 12.1.3: Use Default Activity Instead of the Deprecated Invoke Action

Since JDeveloper 12.1.3 the invoke action used in earlier version has been deprecated. Users still using the old invoke action to load data on page load should migrate their code to using the default activity in a bounded task flow instead. This article describes how to use the executeWithParams method as a default activity in a bounded task flow (btf) to load data to be shown in a region. For this we implement a common

Use Case:
in a text field the user enters a string which should be used to look-up data in the DB and show the data as a table in a region.
For this we use the HR schema and build a look-up for locations after the name of the city of the location. In a page the user can insert the name or part of a cities name into a text field. This input is send as parameter to a bounded task flow. The default activity of the btf calls a method in the view object which uses a view criteria to search for cities starting with the given input data. In a second implementation the same technique is used but a where clause is used in the VO and the VO is called with executeWithParams. The result of the search is displayed as a table in a region.

Implementation

Model Project:
We start by creating a new ‘Fusion Web Application’ and creating a model project of the HR DB schema. Here we only use the location table for which we create entity object and view object.
Now we create the view criteria which we use to find locations by part of the city name.

Next step is to create the java class for the view object including the method to safely access the created bind variable. In the class we add a method to apply the created view criteria which we expose in the client interface well as the methods to access bind variables.


Finally we have to make sure that the locations view object is part of the data model of the application module.
Resulting Application Module Data Model

Resulting Application Module Data Model


Next we add another view object to the data model which we use to implement the use case a second time. This time we use the view criteria we defined in the view object LocationsView and select it as the default where clause.

ViewController Project:
We start implementing the view controller project by first adding a start page, ‘Start’, to the unbounded task flow in adfc-config.xml. For this page we use a quick layout (One Column, Header stretched).

After opening the page (which creates it) we add a third grid row to the panelGridLayout we got from the quick layout which later holds the result table. In the first grid row we add a captain for the page, ‘Execute with param sample’, the second grid row we add an af:inputText which holds the users input for the city name to search for.
The page looks 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="Start.jsf" id="d1">
        <af:form id="f1">
            <af:panelGridLayout id="pgl1">
                <af:gridRow height="50px" id="gr1">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                        <!-- Header -->
                        <af:outputText value="ExecuteWithParams Test" id="ot1" inlineStyle="font-size:x-large;"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow height="50px" id="gr2">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
                        <!-- Content -->
                        <af:inputText label="City" id="it1" value="" autoSubmit="true"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow id="gr3">
                    <af:gridCell id="gc3">
                        <!-- REGION HERE -->
                    </af:gridCell>
                </af:gridRow>
            </af:panelGridLayout>
        </af:form>
    </af:document>
</f:view>

Now we create a pageDefinition for the page, where we define a variable and an attribute binding which holds the users input into the inputText we added to a grid row below the header.


The final inputText look like

<af:inputText label="City" id="it1" value="#{bindings.searchCityName1.inputValue}" autoSubmit="true"/>

As you see we set the autoSubmit property to true as we don’t have (and need) a button to submit the data to the binding layer.

The next task is to create a new bounded task flow which has one input parameter, which is used to search for locations with cities starting with the given parameter from the inputText component.

Once the bounded task flow is created we can drag this btf onto the start page and drop it in the girdCell in the third gridRow and wire the parameter for the task flow to the value we have stored in the in the variable iterator via the inputText.

Finally we make the region refresh whenever the inputParamter of the task flow changes by setting the regions refresh property to ‘ifNeeded’.
The final ‘Start’ page layout looks 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="Start.jsf" id="d1">
        <af:form id="f1">
            <af:panelGridLayout id="pgl1">
                <af:gridRow height="50px" id="gr1">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc1">
                        <!-- Header -->
                        <af:outputText value="ExecuteWithParams Test" id="ot1" inlineStyle="font-size:x-large;"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow height="50px" id="gr2">
                    <af:gridCell width="100%" halign="stretch" valign="stretch" id="gc2">
                        <!-- Content -->
                        <af:inputText label="City" id="it1" value="#{bindings.searchCityName1.inputValue}" autoSubmit="true"/>
                    </af:gridCell>
                </af:gridRow>
                <af:gridRow id="gr3">
                    <af:gridCell id="gc3">
                        <af:region value="#{bindings.showlocatiobycitybtf1.regionModel}" id="r1"/>
                    </af:gridCell>
                </af:gridRow>
            </af:panelGridLayout>
        </af:form>
    </af:document>
</f:view>

This concludes the first implementation and we can run the application

The sample application can be downloaded form ADFEMG Sample Project. It contains a second page (Start2) which uses the other view object (LocationsWithParamsView) inside the region. It’s build like the first version. The difference is that the default activity nor is the executeWithParams from the VOs operations instead the self implemented method from the VO. You spare writing the method and exposing the method in the client interface this way.
Be aware that the sample uses the HR DB schema and you have to change the connection information to point to your DB.

Book Review: Oracle ADF Faces Cookbook by Amr Gawish

During my short vacation I spend some time reading books. One of them was ‘Oracle ADF Faces Cookbook’ by Amr Gawish (http://bit.ly/PhWTlR).

Oracle ADF Faces Cookbook

Oracle ADF Faces Cookbook


The book is written as a cookbook, as the name says, for ADF Faces in the version 12c (12.1.2.0.0 to be accurate). The book offers 10 chapters:

  1. Building Your ADF Faces Environment From the Ground Up
  2. Here you get information about how to install JDK 1.7, Oracle XE Database, JDeveloper and how to tune the environment to work best together. Finally you can download the code used throughout the book from a GIT repository.Make sure to download the code and have it ready while reading the book!

  3. Getting Started with ADF Faces and JDeveloper
  4. In this chapter you get the very basics about development of Fusion Web Applications, building an workspace, connection to the DB, creating a business service, defining page flows and pages and how to run an ADF Faces application from within JDev.

  5. Presenting Data Using ADF Faces
  6. Here we start with use cases used for presenting data. You get info on how to present single records and different master – detail use cases, using different techniques. Finally a first glance on internationalization.

  7. Using Common ADF Faces Components
  8. Here you get a walk through to the normal adf faces components for input, output, selection, loading of data as well as pop-up, code editor, menu model, links, trains and dynamic components.

  9. Beautifying the Application Layout for Great User Experience
  10. Is about page templates, skins, responsive design and flat design techniques.

  11. Enriching User Experience with Visualization Components
  12. This chapter handles maps, graphs, gauges, Gantt charts, hierarchy, sunburst and timeline components.

  13. Handling Events and Partial Page Rendering
  14. Here events (client and server), partial triggers and behavior components are introduced. Polling, Drag & Drop, Active Data Service and WebSocket are described too in this chapter.

  15. Validating and Converting Inputs
  16. Is about conversation of data, custom converters, validation (client and server) and faces messaging.

  17. Building Your Application for Reuse
  18. Picking up from chapter 2 we learn about page templates, exception handling task flow parameters, contextual events, ADFLibraries and finally Meta Data Service (MDS).

  19. Scaling your ADF Faces Application
  20. The final chapter is about performance and recommendations as well as some advances options like Content Delivery Network (CDN) and ADF caching of resources.

The cookbook uses its own db schema which looks kind of familiar, as it’s build from the well known HR schema.

While reading the book I found some recipes hard to understand for novice developers, without reading the provided code in parallel. This is because the book is all about ADF Faces but spares most about the ADF Business Components Model layer. Here the reader has to know how things are working and how to set things up.
For experienced developers some of the covered topics are nothing new, we work with them every day. The uses cases covered are mostly basic in nature and you often see links to the original documentation. Here you find the missing information which is not covered in the cookbook itself. I would have liked some more deep going information on how properties work together for some often used components like af:table or af:panelGridLayout.

I personally found it hard to understand why the author used task flows build from pages for some of the use cases just because ‘it’s easier’. One of the main advantages are task flows build of fragments. They allow reuse and help divide a task into smaller pieces. Tools like the ADF-EMG Task Flow Tester are not even mentioned. Using this tool makes it easy to run task-flows build on fragments without the need to build a JSF Page to host the region.
There are no use cases which handle region specific questions like when regions are refreshed or how regions are restarted. Transaction management and data control sharing are not covers too.

The real interesting part of the book starts with chapter 5, where page templates, skins and at least some layout techniques are introduced. Very interesting is the use case about using SAAS (Syntactically Awesome Style Sheets) and Compass to build more dynamic css files which can be used in ADF. The chapter covers Responsive Web Design and Flat Design Techniques (which are trying to minimize the design) too.

Also interesting are the samples which are build in chapter 6 for graphs, gauges, maps, pivot table, Gantt charts (calendar is missing), timeline and sunburst. However I personally find them hard to digest as they have many ingredients (steps in this case) which you have to follow. Again the model layer is pre-build for you, which means you have to find out yourself how and if there are something special in the model to cook the dish. Nevertheless, samples in this area are hard to find, and these samples do work!

The chapter about event handling and ppr is a very nice summary of how things work in ADF. Easy to understand samples are making it easy to follow the concepts. Here you find samples for ADS (Active Data Service) and WebSocket too. Nice!
Contextual events are handled in a later chapter (‘Building Your Application for Reuse’) but they are not described in great detail. It’s enough to get a first impression.

The final chapter is a nice summary of performance increasing methods and techniques which is worth reading. After reading the techniques it make sense to test them on your system!

Summary:
Novice developers learn basic ADF Faces techniques and components by building samples. Some recipes are too complicated to understand by just reading them. As the book uses its own DB schema you should have this ready, as well as the code, to work through them.