Pages

Monday, December 29, 2008

Coherence with ADF BC ( BC4J)

Inspired by an article of Clemens I decided to make my own very fast and easy ADF BC Coherence example project ( I use in this project the HR schema tables ). This Jdeveloper 11g project is a bit different then that of Clemens. I will use not a local cache and I will use a JPA provider to fill the Coherence cache. Clemens uses an entity to fill the cache. In my case the JPA Toplink ( eclipselink) provider fills the cache. The ADF BC viewobjects reads this cache and the transactions are handled by the ADF BC entities ( my own EntitiyImpl updates or add the cache entries).
It is now very easy to use coherence with ADF BC, you don't need to program java or know much about coherence. There are only five steps to make it work.
Step 1, create an EJB entity
Step 2, change the coherence configuration xml where we will add the new entity and start Coherence on 1 or more servers
Step 3, Create an ADF BC entity ( same attributes and java types as the EJB entity ) and override the row entity class.
Step 4, Create on the just created entity a new default view and override the viewobject object.
Step 5, Fill the cache and start the ADF BC Web Application
That's all.
Here is an picture of an ADF page where I use the coherence viewobjects and I also support ADF BC master detail relation ( viewlinks)

We start by adding a new EJB entity.


Create a new persistence unit, This name must match with the coherence configuration xml

Select only one table a time, else the foreign key attributes will be replaced by relation classes

We need to change the persistence xml and add some extra jdbc properties. Coherence won't use the datasource so we need to add eclipselink.jdbc properties. Change my values with your own database values.
<?xml version="1.0" encoding="windows-1252" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
         version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="Model">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>java:/app/jdbc/jdbc/hrDS</jta-data-source>
<class>nl.whitehorses.coherence.model.Departments</class>
<class>nl.whitehorses.coherence.model.Employees</class>
<properties>
  <property name="eclipselink.target-server" value="WebLogic_10"/>
  <property name="javax.persistence.jtaDataSource"
            value="java:/app/jdbc/jdbc/hrDS"/>
  <property name="eclipselink.jdbc.driver"
            value="oracle.jdbc.OracleDriver"/>
  <property name="eclipselink.jdbc.url"
            value="jdbc:oracle:thin:@localhost:1521:ORCL"/>
  <property name="eclipselink.jdbc.user" value="hr"/>
  <property name="eclipselink.jdbc.password" value="hr"/>
</properties>
</persistence-unit>
</persistence>

Download coherence from Oracle and put my start script and my coherence configuration file in the bin folder of coherence home
here is my configuration xml, Where I add for every ejb entity a new cache-mapping. If I add the ejb entities under the same package name and use the same persistence unit I can use the same distributed-scheme.
<?xml version="1.0" encoding="windows-1252" ?>
<cache-config>
<caching-scheme-mapping>
<cache-mapping>
<cache-name>Employees</cache-name>
<scheme-name>jpa-distributed</scheme-name>
</cache-mapping>
<cache-mapping>
<cache-name>Departments</cache-name>
<scheme-name>jpa-distributed</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<distributed-scheme>
<scheme-name>jpa-distributed</scheme-name>
<service-name>JpaDistributedCache</service-name>
<backing-map-scheme>
  <read-write-backing-map-scheme>
    <internal-cache-scheme>
      <local-scheme/>
    </internal-cache-scheme>
    <cachestore-scheme>
      <class-scheme>
        <class-name>com.tangosol.coherence.jpa.JpaCacheStore</class-name>
        <init-params>
          <init-param>
            <param-type>java.lang.String</param-type>
            <param-value>{cache-name}</param-value>
          </init-param>
          <init-param>
            <param-type>java.lang.String</param-type>
            <param-value>nl.whitehorses.coherence.model.{cache-name}</param-value>
          </init-param>
          <init-param>
            <param-type>java.lang.String</param-type>
            <param-value>Model</param-value>
          </init-param>
        </init-params>
      </class-scheme>
    </cachestore-scheme>
  </read-write-backing-map-scheme>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
</caching-schemes>
</cache-config>

Add these parameters to run options of the model and viewcontroller project.
-Dtangosol.coherence.distributed.localstorage=false -Dtangosol.coherence.log.level=3 -Dtangosol.coherence.cacheconfig=d:\oracle\coherence\bin\jpa-cache-config-web.xml
Now JDeveloper knows where to find the coherence the cache.

Create an new entity with the same name as the ejb entity and don't select a schema object. We will add our own attributes to this ADF BC entity.


Add at least all the mandatory attributes to this entity. These attributes needs to have the same name and java type as the ejb entity.

When we are finished we can create a new default view



I created a new EntityImpl ( Inspired by the great work of Steve and Clemens). This EntityImpl has it's own doDML. In this method I use reflection to dynamically update or add entries to the Coherence Cache.
package nl.whitehorses.adfbc.model.base;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;

import java.lang.reflect.Method;

import oracle.jbo.AttributeDef;
import oracle.jbo.server.EntityImpl;
import oracle.jbo.server.TransactionEvent;

public class CoherenceEntityImpl extends EntityImpl {


    protected void doSelect(boolean lock) {
    }

    protected void doDML(int operation, TransactionEvent e) {

        NamedCache cache =
            CacheFactory.getCache(this.getEntityDef().getName());

        if (operation == DML_INSERT || operation == DML_UPDATE) {

            try {
                Class clazz =
                    Class.forName("nl.whitehorses.coherence.model." + this.getEntityDef().getName());
                Object clazzInst = clazz.newInstance();
                AttributeDef[] allDefs =
                    this.getEntityDef().getAttributeDefs();
                for (int iAtts = 0; iAtts < allDefs.length; iAtts++) {
                    AttributeDef single = allDefs[iAtts];
                    Method m =
                        clazz.getMethod("set" + single.getName(), new Class[] { single.getJavaType() });
                    m.invoke(clazzInst,
                             new Object[] { getAttribute(single.getName()) });
                }
                cache.put(getAttribute(0), clazzInst);
            } catch (Exception ee) {
                ee.printStackTrace();
            }
        } else if (operation == DML_DELETE) {
            cache.remove(getAttribute(0));
        }
    }

}

Override the just create ADF BC Entity with this EntityImpl

Here is the ViewObjectImpl I use to override the Viewobjects
package nl.whitehorses.adfbc.model.base;

import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import com.tangosol.util.ConverterCollections;
import com.tangosol.util.Filter;
import com.tangosol.util.filter.AllFilter;
import com.tangosol.util.filter.EqualsFilter;

import com.tangosol.util.filter.IsNotNullFilter;

import java.lang.reflect.Method;

import java.sql.ResultSet;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import oracle.jbo.AttributeDef;
import oracle.jbo.Row;
import oracle.jbo.ViewCriteria;
import oracle.jbo.common.Diagnostic;
import oracle.jbo.server.AttributeDefImpl;
import oracle.jbo.server.ViewObjectImpl;
import oracle.jbo.server.ViewRowImpl;
import oracle.jbo.server.ViewRowSetImpl;

public class CoherenceViewObjectImpl extends ViewObjectImpl {


    private NamedCache cache;
    private Iterator foundRowsIterator;

    /**
     * executeQueryForCollection - overridden for custom java data source support.
     */
    protected void executeQueryForCollection(Object qc, Object[] params,
                                             int noUserParams) {

        cache = CacheFactory.getCache(this.getViewDef().getName());

        List filterList = new ArrayList();
        Set foundRows = null;
        
        // get the currently set view criteria
        ViewCriteria vc = getViewCriteria();
        if (vc != null) {
            Row vcr = vc.first();
            // get all attributes and check which ones are filled
            for (AttributeDef attr : getAttributeDefs()) {
                Object s = vcr.getAttribute(attr.getName());
                if (s != null && s != "") {
                    // construct an EqualsFilter
                    EqualsFilter filter =
                        new EqualsFilter("get" + attr.getName(), s);
                    // add it to the list
                    filterList.add(filter);
                }
            }
        } else if  (params != null && params.length > 0) {
            for ( int i = 0 ; i < params.length ; i++  ) {
                Object[] s = (Object[])params[i];
                // construct an EqualsFilter
                String attribute = s[0].toString();
                attribute = attribute.substring(5);
                EqualsFilter filter = new EqualsFilter("get" + attribute, s[1]);
                // add it to the list
                filterList.add(filter);
                   
            }
        }
        if (filterList.size() > 0) {
          // create the final filter set, with
          // enclosing the single filters wit an AllFilter
          Filter finalEqualsFilterArray[] =
                    (EqualsFilter[])filterList.toArray(new EqualsFilter[] { });
          AllFilter filter = new AllFilter(finalEqualsFilterArray);
          foundRows = cache.entrySet(filter);
        } else {
              IsNotNullFilter filter =  new IsNotNullFilter("get"+this.getAttributeDef(0).getName());
              foundRows = cache.entrySet(filter);
        }
        setUserDataForCollection(qc, foundRows);
        
        ConverterCollections.ConverterEntrySet resultEntrySet =
            (ConverterCollections.ConverterEntrySet)getUserDataForCollection(qc);
        foundRowsIterator = resultEntrySet.iterator();

        super.executeQueryForCollection(qc, params, noUserParams);
    }

    protected boolean hasNextForCollection(Object qc) {

        boolean retVal = foundRowsIterator.hasNext();
        if (retVal == false) {
            setFetchCompleteForCollection(qc, true);
        }
        return retVal;
    }


    /**
     * createRowFromResultSet - overridden for custom java data source support.
     */
    protected ViewRowImpl createRowFromResultSet(Object qc,
                                                 ResultSet resultSet) {

        ViewRowImpl r = createNewRowForCollection(qc);
        AttributeDefImpl[] allDefs = (AttributeDefImpl[])getAttributeDefs();
        Map.Entry entry = (Map.Entry)foundRowsIterator.next();
        // loop through all attrs and fill them from the righ entity usage
        Object dynamicPojoFromCache = entry.getValue();
        Class dynamicPojoClass = dynamicPojoFromCache.getClass();
        for (int iAtts = 0; iAtts < allDefs.length; iAtts++) {
            AttributeDefImpl single = allDefs[iAtts];
            try {
                Method m =
                    dynamicPojoClass.getMethod("get" + single.getName(), new Class[] { });
                Object result = result = m.invoke(dynamicPojoFromCache, null);
                // populate the attributes
                super.populateAttributeForRow(r, iAtts, result);
            } catch (Exception e) {
               e.printStackTrace(); 
            }
        }
        return r;
    }


    public long getQueryHitCount(ViewRowSetImpl viewRowSet) {
       if (viewRowSet.isFetchComplete()) {
          return viewRowSet.getFetchedRowCount();
       }
       Long result;
       if ( cache != null) {
           result = Long.valueOf(cache.size());
       } else {
           cache = CacheFactory.getCache(this.getViewDef().getName());
           result = Long.valueOf(cache.size());
       }
       return result;
    }


}

Override the viewobject with this ViewObjectImpl

That's all.
Here is the JDeveloper 11gR2 example on github
https://github.com/biemond/jdev11gR2_examples/tree/master/Coherence_ADF_BC

Important,

download coherence at otn and put the jars in the coherence\lib folder.
Rename the internal coherence folder of jdeveloper else you can't connect to the cache.

Update the project options with your own coherence settings, also the weblogic run options of the application.

And Start Coherence and fill the coherence cache before running.


Wednesday, December 24, 2008

Reuse remote Task Flows

In JDeveloper 11g is possible reuse pages in different applications. For example you can make one registration process which you can use in your all applications. If you breakup your application in reusable parts you can use these parts in all your applications, this will reduce maintenance and lower the complexity of your application because you now have small applications which has only one purpose. These parts must be bounded Task Flows and must contain at least one view and a Task Flow return component. We can deploy this TF to an application server and call this TF from your application. You will be automatically redirected to this TF and when we are finished we will return to your application.
Here is an example where I can press the remote button, this will open the Remote TF. The url of my application starts with http://127.0.0.1:7101/remoteTF-ViewController...

The remote TF is deployed to a WebLogic Application server, you can now see that the url of the TF is http://localhost:7001/remote/ . When we press back we will return to calling application


And we are back.
To do it yourself follow these steps. First create a new Application or ADF viewcontroller project. In the ViewController project we will create a bound task flow ( de-select the page fragments option). In this TF we add a view and a TF return component. Between these components we will add control flow case called return.

Select the task flow return component and provide a value to the name of Outcome. We need this value for the return control flow case of our calling application.

To make it more interesting let's add an input and a return parameter.

Deploy this TF to an application server. See this blog entry how to this. If you get a deployment error then it can help to remove the listener in the weblogic-application.xml. See the Oracle forums for more details.
The next step is to add an ADF library deployment profile to your TF and deploy your TF code to a jar.


Now go to your calling application and add this jar as a library to theviewcontroller project.
Open your unbounded Task Flow ( adfc-config.xml ). We can see the just created TF in the Component Palette and drag this TF to the unbounded TF


Let's add two control flow cases. The first called remote will open the remote TF and the second called back ( this need to have the same name as the outcome name of the task flow return component )
Because the remote TF is running on a other application server we need to provide the url. In my case http://localhost:7001/remote/faces/adf.task-flow? . This url must end with faces/adf.task-flow?.
This remote TF has an input and a return parameter so let's add a value to these parameters.

That's all. Here is the example workspace

Tuesday, December 23, 2008

Google Maps Task Flow

I made a stand alone google maps Task Flow which you can use as region in your jsf pages. This Task Flow is based on GMaps4JSF and you only have to fill the input parameters and the Task Flow will display you the location and popup the description of this location.
Here you see some examples where I use the department table of the HR schema as input.

This Task Flow has the following four input parameters.
1) inputParameterTitle: Display the Title
2) inputParameterLocation: The google maps location like London, UK
3) inputParameterDescription: Popup description of the location
4) inputParameterZoomLevel

Here the steps to integrate this Google maps Task Flow in your own Application.
1) Add the Task Flow jar to your own viewcontroller project. The jar is located in the deploy folder GmapsTaskFlow project.
2) Drag the task flow from the component palette to your page. This will add the region component to the jsf page and a task flow binding in the Page definition.

3) We need to add your own google maps api key to your jsf page. We need to do this in the metacontainer of af:document

<af:document>
<f:facet name="metaContainer">
<f:verbatim>
<![CDATA[
<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAM7FSGSscPTbXiOt1No2LPRSLP72-OZgzlwHUle6cA--KWDlXYxSMtxkbiwjRJ9xjiVAYHIVo1d0VkA">
</script>
]]>
</f:verbatim>
</f:facet>

4) Fill the input parameters of this Task Flow and change the refresh condition to ifNeeded
Open the PageDef of your page and add some values to the inputparameters.


that's all. Here is the google maps workspace with the Task Flow and a test project where I use this TF. ( It works on the HR schema ).

Here some code how to fill the location parameter in an ADF rich table by using your own selection listener.

public static Object invokeMethod(String expr, Class[] paramTypes,
Object[] params) {
FacesContext fc = FacesContext.getCurrentInstance();
ELContext elc = fc.getELContext();
ExpressionFactory ef = fc.getApplication().getExpressionFactory();
MethodExpression me =
ef.createMethodExpression(elc, expr, Object.class, paramTypes);
return me.invoke(elc, params);
}

public static Object invokeMethod(String expr, Class paramType,
Object param) {
return invokeMethod(expr, new Class[] { paramType },
new Object[] { param });
}

public void departmentSelectionListener(SelectionEvent selectionEvent) {
// Add event code here...
invokeMethod("#{bindings.DepartmentsView1.collectionModel.makeCurrent}",
SelectionEvent.class, selectionEvent);

DCBindingContainer bc = (DCBindingContainer) BindingContext.getCurrent().getCurrentBindingsEntry();
DCIteratorBinding iter = bc.findIteratorBinding("DepartmentsView1Iterator");
Row rw = iter.getCurrentRow();
location = rw.getAttribute("City").toString()+","+
rw.getAttribute("CountryId").toString();
if ( rw.getAttribute("StateProvince") != null ) {
location = location +","+rw.getAttribute("StateProvince").toString();
}
description = rw.getAttribute("DepartmentName").toString()+","+
rw.getAttribute("StreetAddress").toString();
}