Friday, September 17, 2010

ADF Region Interaction Functional Pattern - OTN Article

There is an excellent article in OTN on 'ADF Region Interaction'.
Please check it out - ADF Region Interaction Functional Pattern

Thursday, September 16, 2010

Building a custom event dispatcher for Contextual Events

ADF lets you to create custom event dispatcher to override the default one to provide custom behaviors. In this post, I'm sharing a simple example built using a custom event dispatcher. Please refer this topic 28.7.6 How to Register a Custom Event Dispatcher in Fusion Developer's Guide to learn more about this topic.

A glance at the implementation

1. You can create your own event dispatcher (from scratch) by implementing oracle.adf.model.events.EventDispatcher. Alternatively, if you want to override specific behavior alone then consider sub classing oracle.adf.model.binding.events.EventDispatcherImpl or its base class - AbstractEventDispatcher.

2. Next step is to register the custom event dispatcher in the Databindings.cpx file
<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://xmlns.oracle.com/adfm/application"
  version="11.1.1.56.60" id="DataBindings" SeparateXMLFiles="false"
  Package="view" ClientType="Generic"
  EventDispatcher="view.extension.CustomEventDispatcherImpl">

Cool...The basic infrastructure to use your custom event dispatcher is in place now. enjoy !

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

How to run this sample?

This sample is configured to use view.extension.CustomEventDispatcherImpl class as event dispatcher. The CustomEventDispatcherImpl class check for the event payload type and if payload is 'instance of' view.extension.OriginAwarePayLoad, then decorates the payload by adding the producer name. Please note that, this is just a simple example to illustrate the custom event dispatcher. You can have more smart implementations (based on your use cases) and do it in your own way!

1. Run the main.jspx
2. Click on the Command Button which generates contextual event. Here the event payload is defined as a custom object, sub classed from a view.extension.OriginAwarePayLoad.
3. Control comes to the custom event dispatcher - CustomEventDispatcherImpl . This class overrides invokeEvent(...) to add 'event producer id' to the pay load.
4. Finally, event handler method (defined for the subscriber) receives this decorated payload and the same is used for further execution.

Wednesday, September 15, 2010

Disabling dispatch of Contextual Events

You can turn off the event dispatch to regions (and child regions) having eventMap with 'producer region' as 'Any'(*) by setting DynamicEventSubscriptions="false" in the pageDefinition file
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
                version="11.1.1.56.60" id="somePageDef"
                Package="view.pageDefs" DynamicEventSubscriptions="false">

Thursday, September 9, 2010

Using bind variable for the SQL statements with IN clause

Bind variable improves the performance of query execution by avoiding the repeated parsing of the SQLs (prepare once and execute multiple times). In this post, I'm discussing about the possibility of using bind variable for a ViewObject whose WHERE clause is formed using IN clause.

Obviously, you cannot directly bind a single value to an IN clause and expect it to be treated as many values. A common solution is to have a DB function which takes comma separated String as parameter and let this return a user defined object type. This post is also based on same 'age old' idea. The query generated using the custom db function(in_list_char) to support bind variable for IN clause may look like as shown below,
 SELECT Departments.DEPARTMENT_ID, Departments.DEPARTMENT_NAME  FROM DEPARTMENTS Departments WHERE 
   ( ( Departments.DEPARTMENT_NAME IN (select * from TABLE (cast (in_list_char ( :CommaDelimitedDeptNames ) as ChartableType))A)) )

Generate custom where clause fragment for the ViewCriteriaItem

If you want to enable multi-value search for a specific ViewCriteriaItem in a ViewCriteria, then you can provide the custom interpretation for ViewCriteriaItem by overriding ViewObjectImpl::getCriteriaItemClause(ViewCriteriaItem vci) as shown below.
@Override
public String getCriteriaItemClause(ViewCriteriaItem vci) {
 if (vci.getAttributeDef().getName().equals("DepartmentName") &&
    vci.getViewCriteria().getName().contains("DeptSampleVC")) {
    if (vci.getViewCriteria().getRootViewCriteria().isCriteriaForQuery()) {
    return getINClauseForDatabaseUse(vci);
    } else {
    return getINClauseForCache(vci);
    }
 } else {
    return super.getCriteriaItemClause(vci);
 }

}

protected String getINClauseForDatabaseUse(ViewCriteriaItem vci) {

 String bindVarValue = getCommaDelimitedDeptNames();
 String bindVarName = "CommaDelimitedDeptNames";
 String whereCluase = "1=1";
 if (bindVarValue != null && bindVarValue.trim().length() != 0) {
    whereCluase =
        this.getEntityDef(0).getAliasName() + ".DEPARTMENT_NAME IN (select /*+ CARDINALITY(A, 50) */ * from TABLE (cast (in_list_char ( :" +
        bindVarName + " ) as ChartableType))A)";
 }
 return whereCluase;
}

protected String getINClauseForCache(ViewCriteriaItem vci) {
 String whereCluase = "1=1"; 
 return whereCluase;
}

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

How to run this sample?

1. Unzip the source to your local drive.
2. Setup the required DB objects in your local schema(HR) by running the <BindParamEnabledINClauseSample>\script\select_in_list.sql
3. Run the test.jspx. This page displays query panel and a result table.
4. This sample has enabled multi-value(comma separated values) search for 'DepartmentName' ViewCriteriaItem.
You can try searching the comma separated values for 'DepartmentName' field in the search panel e.g: Finance,Sales,Executive . Have fun!

Learn More ...

There are a lot more points like this. If  you are curious to learn the internals of the ADF Business Components and ADF Binding Layer,  the following book is for you - Oracle ADF Real World Developer’s Guide.
More details about this book can be found in this post- http://jobinesh.blogspot.in/2012/10/oracle-adf-real-world-developers-guide.html
 

Excluding the class files from the war file

Sharing a tip about 'deployment' profile configuration I learned recently. JDeveloper lets you to exclude the class files from the war file generated using the 'deploy' option.

How to do this?

1) Right-click on the web project, select 'Project Properties'
2) Select the 'Deployment' option on the left hand side of the dialog. Choose the deployment profile you are interested, click Edit.
3) Under 'File Groups -> WEB-INF/classes -> Filters' select the 'Files' tab. You can deselect the class you don't want to include. Alternatively you can select the 'Patters' tab to specify the pattern for inclusion.

That's it, job is done :)

Wednesday, September 8, 2010

Undoing changes using Middle Tier Savepoints

While building applications, sometimes you may need to enable undo/redo functionality for your pages. If you use ADF BC for implementing the business services, then one possibility is to leverage the 'Middle-Tier Savepoints' to rollback to a certain point within a transaction instead of rolling back the
entire transaction. Below shown APIs are your friends here.
ApplicationModuleImpl::passivateStateForUndo(java.lang.String id, byte[] clientData, int flags)
ApplicationModuleImpl::activateStateForUndo(java.lang.String id,int flags)

More details can be found in Fusion Developer's Guide - 40.9 Using State Management for Middle-Tier Savepoints. You may need to note down a couple of point here.
1. This approach saves only ADF Model state(in other words , this wouldn't help you to save navigation state/manged bean/memory scope scope variable)
2. Saved state(snapshot) do not survive past duration of transaction

Adding Save Points to a Task Flow

Please note that Oracle ADF provides a declarative approach to working with savepoints, described in Section 18.7, Adding Save Points to a Task Flow. This is different from the approach discussed in this post. In this approach, save point is managed by the ADF Controller. Worth noting the following points associated with 'ADF Controller Save Points' functionality.
1. Declarative
2. Saves UI state, ManagedBean state,Navigation State and ADF Model state
3. Can be restored at later stage, sate is persisted in databases.
4. This is useful, if you need to enable this functionality(restoring save point) for a bounded taskflow


You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

How to run this sample?

Please try running sample-task-flow-definition . Default activity for this taskflow creates the save point by calling passivateStateForUndo(...). The 'Cancel' button in editDept.jspx is mapped to activateStateForUndo method in the ApplicationModule. This method restores the previously saved state.

Friday, September 3, 2010

<af:popup> inside <af:table>

Recently, I noticed a couple of issues reported saying that popup dialog is not getting displayed when used inside a table. There are some points you may need to take care while using <af:popup> inside <af:table>

  • Please keep the definition of <af:popup> outside of the <af:table> .This would ensure table refresh( in other words DOM replacement for table during PPR) doesn't cause any harm to your popup component
  • Please don't leave the table rows in an inconsistent state while you iterate through the table rows from the managed bean. Please see this post for more details.

Thursday, September 2, 2010

New ADF Book - Quick Start Guide to Oracle Fusion Development

A new book is available on ADF - Quick Start Guide to Fusion Development, authored by Grant Ronald [ Senior Group Manager Product Management - ADF ]
The goal of the book is to give developers/sales/support/pre-sales/managers/anyone! an accelerated hit of essential ADF knowledge without having to spend months going through thousands of pages of developer guides and on-line help.

Please check out this blog post from Grant to learn more about this book.

Annotate your ApplicationModule to persist complex objects during passivation

The ApplicationModule component is smart enough to automate the state management for your application. While ApplicationModule takes care of the state management for the ViewObjects and EntityObjects (pending transaction state) during the activation-passivation cycle, it doesn't really care about the custom business objects that you might have created during the service invocations. There may be some rare scenarios where you may need to passivate these variables to avoid the expensive instantiation each time. This blog post discusses a customized solution for the above scenario - a solution for passivating your custom business objects.

A couple of weeks back, Kavin Kumar - a colleague of mine - shared a solution (which has been used in their application) for persisting complex objects during the passivation of ApplicationModule. This post is inspired by this idea. So the credit goes to Kavin :)

Use Case Requirement

A specific business service methods defined in the ApplicationModule requires a heavy 'business object' to call some other external services, and the creation of this objective is a pretty expensive task. The requirement is to make this variable passivation safe.

Solution

Obviously, you may need to override ApplicationModuleImpl::activateState(Element parent) and ApplicationModuleImpl::passivateState(Document doc,Element parent) to get hold of the points to hook your custom code.

The method ApplicationModuleImpl::passivateState(...) allows subclasses to store custom data as XML-nodes under the given parent element, in the given document. Whereas method ApplicationModuleImpl::activateState(...) allows subclasses to retrieve custom data from an XML-node under the given parent element.

Now the real fun starts. As the above two methods deals with xml for storing custom data,how do we store binary objects there? Cool...read on...

A very generic solution is to use Base-64 encoding for the binary data. Please refer this article to learn more about this topic. Getting back to our topic of discussion, on a high level, you may need to perform following steps to make the member variables of AM passivation safe.

When the ApplicationModule passivates...

  • Get the member variable , convert this to byte array and encode the byte array to Base64
  • Stores the Base64 encoded string as a XML node

When the ApplicationModule activates...

  • Decode (using Base-64) the the 'stringified' binary object (which is stored in the XML during passivation), and convert back to byte array.
  • Recreate the object using the byte array
@Override
protected void passivateState(Document doc, Element parent) {
  super.passivateState(doc, parent);
  String stringifiedValue=getBase64EncodedObject( objectTobePassivated )
 // Add them to the XML
 Node nodeUserData = doc.createElement("USERDATA");
 Element elem = doc.createElement("AM_OBJECT");
 elem.setAttribute("KEY", key);
 elem.setAttribute("VALUE",
       (String)base64EncodedProperties.get(key));
 elem.setAttribute("TYPE", "java.lang.Object");
 nodeUserData.appendChild(elem);
 parent.appendChild(nodeUserData);
 //...........Your code goes here.........
}
  
/**
* Converts object to Base64 format
* @param propertyValue
* @return
*/
private String getBase64EncodedObject(Object propertyValue) {
  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  FastByteArrayOutputStream fbos = null;
  try {
      fbos = new FastByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(fbos);
      out.writeObject(propertyValue);
      out.flush();
      out.close();

  } catch (IOException e) {
      e.printStackTrace();
  }
  return Base64.byteArrayToBase64(fbos.getByteArray());

}
/********************************************************/
@Override
protected void activateState(Element parent) {

 if (parent != null) {
    NodeList nl = parent.getElementsByTagName("USERDATA");
    if (nl.getLength() > 0) {

    Node n = nl.item(0);
    NodeList nl2 = n.getChildNodes();

    for (int i = 0; i < nl2.getLength(); i++) {

        Element e = (Element)nl2.item(i);
        String key = e.getAttribute("KEY");
        String value = e.getAttribute("VALUE");
        try {
        byte[] obj = Base64.base64ToByteArray(value);
        ObjectInputStream in =
            new ObjectInputStream(new FastByteArrayInputStream(obj));
        setValue(key, in.readObject());
        in.close();

        } catch (Exception ex) {
        ex.printStackTrace();
        }
    }

    }
 }
 super.activateState(parent);
}


A generic solution

Now, let us try to generalize the above solution so that same can be enabled for any ApplicationModules with zero/less effort. One possibility is to use Java Annotations to enable the dynamism for ApplicationModule's member variables. Please refer this article to learn more about Java Annotations. Idea is to define some marker annotation and annotate the ApplicationModule variable to mark it as passivation safe. In this sample I opted to use a custom annotation 'Persistable', to annotate member variables of the ApplicationModule. With this approach, the 'passivation safe' member variables in your ApplicationModule may look like as shown below.

public class AppModuleImpl extends SerializableApplicationModule implements AppModule {
/**
* This is a complex object, so avoid the 
* destruction/recreation of the same during 
* passivation/activation of AM
*/
@Persistable
ComplexBusinessServiceObject complexBusinessServiceObject;

ApplicationModule's passivateState method would be looking for fields with this annotation and provide a special treatment to make them passivation safe.

To improve the re-usability, you can move the activation/passivation logic to a generic 'ApplicationModule' implementation class which may act as the base class for other ApplicationModule classes, on need basis.

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

A glance at the implementation

This sample is making use of a SerializableApplicationModule class (sub-classed from ApplicationModuleImpl) which holds the logic for passivating member variables. This act as a base class for my ApplicationModule. Other relevant classes are located under 'sample.common.extension' package.

How to run this sample?

Run the test.jspx. Click on the Save button. This action is bound to AppModuleImpl::saveChanges(), which requires instance of ComplexBusinessServiceObject class to service the request. Please note that, this instance is getting created only once for the user session. The custom passivation logic passivates this instance along with ApplicationModule to serve the next request.