Pages

Tuesday, November 30, 2010

ADF Faces Rich Client Development

In this blogpost I will try to give you a jumpstart in javascript development. For ADF Faces RC development you need to have JDeveloper 11g together with Mozilla FireFox & the FireBug plugin.
This blogpost is inspired by the great work of Frank Nimphius ( Lot's of demos and a very good chapter in his book ) and a very nice blogpost of Chris how you can use hotkeys, to switch Tabs in UIShell.

Before you can start we need to add some context parameters to the web.xml
<context-param>
    <param-name>oracle.adf.view.rich.profiler.ENABLED</param-name>
    <param-value>true</param-value>
  </context-param>
  <context-param>
    <param-name>oracle.adf.view.rich.LOGGER_LEVEL</param-name>
    <param-value>ERROR</param-value>
  </context-param>
  <context-param>
    <param-name>oracle.adf.view.rich.LOG_WRITERS</param-name>
    <param-value>AdfConsoleLogWriter</param-value>
  </context-param>
  <context-param>
    <param-name>org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT</param-name>
    <param-value>true</param-value>
  </context-param>
I set the oracle.adf.view.rich.LOGGER_LEVEL to ERROR because the RC frameworks generates a lot of INFO messages, so you won't see your own log messages.
The oracle.adf.view.rich.LOG_WRITERS parameter enables the Javascript Logger popup window, when the firebug console is enabled then this popup won't launch and the messages will be seen in the firebug console window.
The org.apache.myfaces.trinidad.DEBUG_JAVASCRIPT will allow to debug a javascript file in the firebug debug console.

The next step is to create a javascript file and put this in a new folder in the Web Content folder of your web application.

To load the javascript files in your ADF Web Application you can use af:resource
<af:resource type="javascript" source="js/keys.js"/>
When you do this in the JSPX page then every page / region or fragment can call these javascript methods.
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view beforePhase="#{viewScope.HandleKeys.registerKeyboardMapping}">
    <af:document id="document" title="Client Side">
      <af:resource type="javascript" source="js/keys.js"/>
      <af:resource type="javascript" source="js/utils.js"/>
      <af:serverListener type="keyboardToServerNotify"
                         method="#{viewScope.HandleKeys.handleKeyboardEvent}"/>
      <af:form id="f1">
        <af:pageTemplate viewId="/oracle/ui/pattern/dynamicShell/dynamicTabShell.jspx"
                         value="#{bindings.pageTemplateBinding}" id="pt1">

To call a javascript method from your JSF page you can use af:clientListener where you need to provide the method name and how it is activated.
<af:clientListener method="numbersOnly" type="keyDown"/>

Here an example of a javascript method
function numbersOnly(evt){
     var _keyCode = evt.getKeyCode();
     var _filterField = evt.getCurrentTarget();
     var _oldValue = _filterField.getValue();
     
     AdfLogger.LOGGER.logMessage(AdfLogger.ERROR, "key: "+_keyCode);
     //check for characters
     if (_keyCode > 64 && _keyCode < 91){  
       _filterField.setValue(_oldValue);
       evt.cancel(); 
     }
  }
In this code the AdfLogger.LOGGER.logMessage will log a message in the firebug console or logging popup.

ADF 11g has a very large Javascript API which you can use in your own Web Application, to see the API, click here.

You can also pass on data to the javascript method. You can use the af:clientAttribute for this with as value a text or an EL expression.
<af:panelHeader text="Tab Order" id="ph4">
     <af:panelFormLayout id="pform" clientComponent="true">
      <af:inputText id="it3" label="Tab 1">
        <af:clientAttribute name="tabindex" value="1"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it4" label="Tab 3">
        <af:clientAttribute name="tabindex" value="3"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it5" label="Tab 4">
        <af:clientAttribute name="tabindex" value="4"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
      <af:inputText id="it6" label="Tab 2">
        <af:clientAttribute name="tabindex" value="2"/>
        <af:clientListener method="setTabIndex('1','4')" type="keyPress"/>
      </af:inputText>
     </af:panelFormLayout>
    </af:panelHeader>
To get the clientAttribute value in javscript you can use this in javascript , component.getProperty("tabindex") Or you can do a Javascript callback
function setTabIndex( min, max) {
     return function (evt) {
       var minTabIndex = min;
       var maxTabIndex = max;
       
       var keyCodePressed = evt.getKeyCode();
       var keyModifiers = evt.getKeyModifiers();
       var component = evt.getSource();
       var panelForm = component.getParent();

       if ( keyCodePressed == AdfKeyStroke.TAB_KEY ) {
         evt.cancel();
         var tabIndex = parseInt(component.getProperty("tabindex"));
       }
    }
  }

Next question will be, how can you invoke a method in a managed bean (serverside) from the clientside. For this we can use af:serverListener, but you can not invoke this managed bean method directly. You need to fire a client ADF Event from the clientListener method. like this AdfCustomEvent.queue( source, "clickOnRow", {}, false);
<af:table value="#{bindings.allDepartments.collectionModel}" var="row"
               rows="#{bindings.allDepartments.rangeSize}"
               emptyText="#{bindings.allDepartments.viewable ? 'No data to display.' : 'Access Denied.'}"
               fetchSize="#{bindings.allDepartments.rangeSize}"
               rowBandingInterval="0" rowSelection="single" id="departmentTable"
               width="400"
               selectedRowKeys="#{bindings.allDepartments.collectionModel.selectedRow}">
      <af:clientListener method="clickRow" type="click"/>
      <af:serverListener type="clickOnRow" method="#{Client.goCurrentRow}"/>
      <af:column sortProperty="departmentId" sortable="false"
                 headerText="#{bindings.allDepartments.hints.departmentId.label}"
                 id="c1">
       <af:outputText value="#{row.departmentId}" id="ot3">
        <af:clientAttribute name="departmentId" value="#{row.departmentId}"/>
        <af:clientListener method="showPopup('::deptPopup','dialog1','departmentId')"
                           type="click"/>
       </af:outputText>
      </af:column>
      <af:column sortProperty="departmentName" sortable="false"
                 headerText="#{bindings.allDepartments.hints.departmentName.label}"
                 id="c2" width="200">
       <af:outputText value="#{row.departmentName}" id="ot1"/>
      </af:column>
     </af:table>
Here is the javascript for the ADF Client Event.
function clickRow(evt) {
     var source = evt.getSource();
     AdfLogger.LOGGER.logMessage(AdfLogger.ERROR, "clickOnRow");
     AdfCustomEvent.queue( source, "clickOnRow", {}, false); 
  } 
And the managed bean method.
public void goCurrentRow(ClientEvent clientEvent) {
       // Determine which row is currently selected in the RichTable
       RichTable deptTable = (RichTable)clientEvent.getComponent();
       Iterator keys = deptTable.getSelectedRowKeys().iterator();
       while (keys.hasNext()) {
           Key key = (Key)((List)keys.next()).get(0);
           System.out.println(key.getKeyValues()[0]);
       }
   }

The only thing still to do is to debug the javascript methods. Start you Web application in FireFox and open the firebug application.  Click on the script Tab.

 Select the javascript file which you want to debug.

Set the breakpoint and debug through your application.

Here is my demo application with the following client examples ( thanks to Frank ).

  • Hotkeys for Tab switching
  • Tab Order
  • Clear validation messages
  • Type only numbers in a inputtext.
  • Click on a record and invoke a managed bean method
  • Click on a inputtext and display the value in a popup


Here you can download the demo workspace

Thursday, November 25, 2010

Start or Stop your SOA Suite 11g Composite in Java

In this short blogpost I will show you how can execute operations on a SOA Suite 11g Composite in java. The operations are start, stop, activate, retire, set the default version and list all the deployed composites.

I use for this the oracle.fabric.management.deployedcomposites.CompositeManager class ( located in the fabric runtime ) with its static methods.
The SOA ANT scripts also uses this class or you can do it yourself by looking up the right Composite MBean and invoke the same operations.

Before PS2 you could lookup the composite and call the start/ stop methods, but this not working anymore so now you need to use this ( or use the EM or the MBeans )
package nl.whitehorses.soa.client;

import oracle.fabric.management.deployedcomposites.CompositeManager;

public class ActivateComposite {

    public static void main(String[] args) {
      try {
           CompositeManager.initConnection("localhost", "8001", "weblogic", "weblogic1");

           CompositeManager.assignDefaultComposite(CompositeManager.getCompositeLifeCycleMBean(), "default/Helloworld!1.0");

           CompositeManager.stopComposite(CompositeManager.getCompositeLifeCycleMBean(), "default/Helloworld!1.0");

           CompositeManager.startComposite(CompositeManager.getCompositeLifeCycleMBean(), "default/Helloworld!1.0");

           CompositeManager.activateComposite(CompositeManager.getCompositeLifeCycleMBean(), "default/Helloworld!1.0");

           CompositeManager.retireComposite(CompositeManager.getCompositeLifeCycleMBean(), "default/Helloworld!1.0");

           System.out.println(CompositeManager.listDeployedComposites(CompositeManager.getCompositeLifeCycleMBean()));

           CompositeManager.closeConnection(); 

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

With this as result.
Connecting to: service:jmx:t3://localhost:8001/jndi/weblogic.management.mbeanservers.runtime

Following 5 composites are currently deployed to the platform:

1. HttpBinding[1.0], mode=active, state=on, isDefault=true, deployedTime=2010-08-20T22:42:58.888+02:00
2. SoaSpring[1.0], mode=active, state=on, isDefault=true, deployedTime=2010-07-10T15:41:07.640+02:00
3. bpm[1.0], mode=active, state=on, isDefault=true, deployedTime=2010-07-10T15:58:13.853+02:00
4. Helloworld[1.0], mode=retired, state=on, isDefault=true, deployedTime=2010-09-22T02:37:50.924+02:00
5. BPELCorrelation[1.0], mode=active, state=on, isDefault=true, deployedTime=2010-09-01T22:14:59.374+02:00

Saturday, November 13, 2010

Global Transactions and Quality of Service in OSB

Last week I had a great OSB BlackBelt training of Samrat Ray and organized by the SOA Community  In this training we talked a lot about Global Transactions ( XA), QoS ( Quality of Service )  in combination with Transports and JCA Adapters. Because this is so important ( you dont want to lose a Message ) I decide to make a blog about this subject and I will explain the different scenarios options you have in the Oracle Service Bus.

Before I can start I need to create and configure some Queues. In this blogpost I will use JMS for the Proxy and the Business service but you can also use EJB, MQ or the Direct Binding ( SOA Suite 11g ) for the Business Service, they all support XA, HTTP transport does not support XA ).

First create the RequestQ Queue combined with an ErrorQ Queue ( Now failed messages will be redirected to this Queue and won't be lost in Space), a ReponseQ Queue and a DestinationQ Queue ( used in the Business service )

Here is a picture how you can define an Error Queue on the RequestQ.  Go to the Queue, select the Delivery Failure Tab in the Configuration Tab. Now you can set the Redirect Expiration Policy and select the Error destinations.


I start with a simple scenario.  Add a message in the RequestQ and we will expect the message in the ResponseQ.

Add a Proxy Service ( any XML) and select JMS as protocol. OSB generates a default url with an internal XA connection factory and add the Queue name to this URL.

Add the destination Queue in the Proxy Service by enabling the Is Response Required option. Use text as Response Message Type ( else it will be hard to read the message ) and provide the Response URI.

Leave the Message Flow empty and a message in the RequestQ ( Go to the Queue, monitoring tab and a create a new message).  Always change the Redelivery Limit  ( Don't use the value -1 )
You will this message in the ResponseQ.

So far so good, now let's raise an Error on the Response Channel and take a look at the results. In the message flow I added a PipelinePair with a stage and an Error activity.
What you will see the message is removed from the RequestQ and the ResponseQ only gets a message with a SERVER_ERROR header and the content is gone.
We can solve this by making a change in the Proxy Service. Enable the Same Transaction For Response option, the response Thread will take over the transaction so when now something happens the transaction will be roll backed.



Add a message to the RequestQ, ( set the Redelivery to 2 else you will get a loop ) what you will see that the message now is added to the ErrorQ and not to the ResponseQ. This is good behaviour. Now you can re-process this message again by moving it to the RequestQ or let an ErrorHospital handle it.

The next step is adding a business service ( also use JMS and a XA Connection Factory )  and make this part of the Global Transaction. Add a routing to this Business Service in the message flow of the Proxy Service. This will also add the message to the DestinationQ. Make sure  that the Message Type = text ( for this test only )
When you now put a message on the RequestQ, the message is also roll backed on the DestinationQ and only exists in the Error Queue.
When you disable the option Same Transaction for Response ( Default behavior ) then this message will be removed even when there is an error in the Response Channel of the Proxy Service.

The last part of this blog is taking a look at Quality of Service ( QoS ). QoS requires a transaction ( The Proxy Service need to start it ) and you can control, if the Business Service will take part on the Global Transaction. You need to add a Routing options activity to the request part of the Routing Node. First set QoS on Best Effort. Exactly Once is the default value when you use JMS and XA in the Proxy Service.

When you add a message on the RequestQ with a retry delivery of 5, You will see 6 messages in the DestinationQ. With Best Effort you will force the Business Service not to use the Global Transaction. Don't think you will like this,so let's change this to Exactly Once or remove this Routing Options action.



Conclusion: You need to think about your process and ask yourself is it a problem when your message is lost and what about when you deliver the same message again and again. Be aware one transaction for the request and response in the Proxy Service is not enabled by default and you need to configure it and test all the situations. Off course you need to use XA connection factories, Datasources and XA configured Resource Adapters.
HTTP Proxy or Business Services won't be part of XA. When you call a BPEL service you can use the Direct Binding transport for this.

Wednesday, November 3, 2010

Deploy your AQ , DB or JMS Resource Adapter to Weblogic and use it in OSB & SOA Suite

When you are familiar with SOA Suite 11g or Oracle Service Bus then you know, that sometimes you need to configure some Resource Adapters in WebLogic. This is necessary when you use the Database, AQ , JMS or Applications adapter in your Composite application or Proxy / Business Service. This task can't be scripted with WLST, The first time you need to generate a Deployment Plan and after that, you can add your EIS entries. In a Cluster environment you need to put this Deployment Plan on a shared storage or copy this to every server.
In this blogpost I will you show that you can deploy your own Resource Adapter with your EIS entries to the SOA Server or Cluster. This way you can separate EIS entries in their own Resource Adapter, so you don't have one big Deployment Plan. Reduce the human factor and just deploy it, no manual tasks after you SOA Composites deployments. And you can deploy it to a cluster and add these Resource Adapter files to your Subversion system.

Basically I do the following steps.
  • Extract the WebLogic Resource Adapters files to a local folder
  • Remove the jars, because these jars are already loaded in the original Resource Adapters
  • Edit the weblogic-ra.xml and remove the default entries and add your own
  • Pack the Resource Adapter
  • Undeploy the Resource Adapter on the WebLogic Server
  • Deploy the Resource Adapter
  • Set the deployment order after the original Resource Adapter
Here a picture of my JDeveloper project where I can do these tasks with the help of ANT and WLST.
 To make this work you need to add your WebLogic jar to the classpath of the ANT environment.


Change the weblogic-ra.xml with your own Entries.

And at last deploy all the Resource Adapters or just one

Here is the ANT properties file. With changing the EnvironmentName you can create a different set of Resource Adapters
FMW.home=C:/oracle/Soa11gPS2
WLS.home=${FMW.home}/wlserver_10.3
SOA.home=${FMW.home}/Oracle_SOA1
SOA.adapters.home=${SOA.home}/soa/connectors

WLS.fullclient=C:/java/wlfullclient.jar

AQ=AqAdapter
DB=DbAdapter
JMS=JmsAdapter

EnvironmentName=MyAdap

build.folder=build

serverURL=t3://laptopedwin:7001
user=weblogic
password=weblogic1

targets=soa_server1

The ANT Build file
<?xml version="1.0" encoding="UTF-8" ?>
<project default="init">

  <property file="build.properties"/>

  <taskdef name="wldeploy" classname="weblogic.ant.taskdefs.management.WLDeploy"/>
  <taskdef name="wlst"     classname="weblogic.ant.taskdefs.management.WLSTTask"/>

  <target name="initAdapters">
     <!-- make an environment folder   -->
     <delete dir="${EnvironmentName}" failonerror="true" />
     <mkdir  dir="${EnvironmentName}"/>
     <!-- unpack the adapters   -->
     <unjar src="${SOA.adapters.home}/${AQ}.rar" dest="${EnvironmentName}/${AQ}" />
     <unjar src="${SOA.adapters.home}/${DB}.rar" dest="${EnvironmentName}/${DB}" />
     <unjar src="${SOA.adapters.home}/${JMS}.rar" dest="${EnvironmentName}/${JMS}" />     
     <!-- remove the jars   -->
     <delete>
       <fileset dir="${EnvironmentName}" includes="**/*.jar"/>
     </delete>
  </target>

  <target name="packAdapters">
     <!-- make a build folder   -->
     <delete dir="${build.folder}"/>
     <mkdir dir="${build.folder}"/>
     <jar basedir="${EnvironmentName}/${AQ}" destfile="${build.folder}/${EnvironmentName}_AQAdapter.rar" />
     <jar basedir="${EnvironmentName}/${DB}" destfile="${build.folder}/${EnvironmentName}_DBAdapter.rar" />
     <jar basedir="${EnvironmentName}/${JMS}" destfile="${build.folder}/${EnvironmentName}_JMSAdapter.rar" />
  </target>

  <target name="deployAll" depends="packAdapters">
     <!-- deploy AQ   -->
     <antcall target="deployAQ"/>
     <antcall target="deployDB"/>
     <antcall target="deployJMS"/>
  </target>  

  <target name="deployAQ" depends="packAdapters">
     <!-- deploy AQ   -->
     <antcall target="undeploy" >
       <param name="deployment" value="${EnvironmentName}_AQAdapter"/>
     </antcall>
     <antcall target="deploy" >
       <param name="deployment" value="${EnvironmentName}_AQAdapter"/>
     </antcall>     
     <antcall target="changeDeploymentOrder" >
       <param name="deployment" value="${EnvironmentName}_AQAdapter"/>
     </antcall>     
  </target>  

  <target name="deployDB" depends="packAdapters">
     <!-- deploy AQ   -->
     <antcall target="undeploy" >
       <param name="deployment" value="${EnvironmentName}_DBAdapter"/>
     </antcall>
     <antcall target="deploy" >
       <param name="deployment" value="${EnvironmentName}_DBAdapter"/>
     </antcall>     
     <antcall target="changeDeploymentOrder" >
       <param name="deployment" value="${EnvironmentName}_DBAdapter"/>
     </antcall>     
  </target>  

  <target name="deployJMS" depends="packAdapters">
     <!-- deploy AQ   -->
     <antcall target="undeploy" >
       <param name="deployment" value="${EnvironmentName}_JMSAdapter"/>
     </antcall>
     <antcall target="deploy" >
       <param name="deployment" value="${EnvironmentName}_JMSAdapter"/>
     </antcall>     
     <antcall target="changeDeploymentOrder" >
       <param name="deployment" value="${EnvironmentName}_JMSAdapter"/>
     </antcall>  
  </target>  

  <target name="changeDeploymentOrder" >
    <wlst failonerror="true" debug="true" arguments="${user} ${password} ${serverURL} ${deployment}">
      <script>
          adminUser=sys.argv[0]
          adminPassword=sys.argv[1]
          adminUrl=sys.argv[2]
          deploymentAdapter='/AppDeployments/'+sys.argv[3]

          connect(adminUser,adminPassword,adminUrl)

          edit()
          startEdit()

          cd(deploymentAdapter)
          cmo.setDeploymentOrder(500)
         
          activate()
          disconnect()    
       </script>
    </wlst>
  
  </target>

  <target name="undeploy">
     <wldeploy action="undeploy" 
               name="${deployment}"
               user="${user}"
               password="${password}"
               adminurl="${serverURL}"
               targets="${targets}"
               failonerror="false">
     </wldeploy>
  </target>

  <target name="deploy">
     <wldeploy action="deploy" 
               name="${deployment}"
               source="${build.folder}/${deployment}.rar"
               user="${user}"
               password="${password}"
               adminurl="${serverURL}"
               targets="${targets}"
               upload="true"
               failonerror="true">
     </wldeploy>
  </target>



</project>

Here you can download my workspace.