2012. december 19., szerda

XA Transactions with IBM MQ Resource Adapter on JBoss AS7

Introduction and Problems

This post shows how to configure the IBM MQ resource adapter and the extended transactional client (for XA transactions) on JBoss AS7 as I have not yet found a proper description about this. If you are trying to make things works based on your experience with AS6, you will possibly try to just copy the com.ibm.mqetclient.jar and wmq.jmsra.rar files into some deployment folder like JBOSS_HOME/standalone/deployments/ and test your application.

Then you might see the following warning on server startup:

MQJCA4005: Distributed transactions are not available in client mode.
The extended transactional client JAR file, com.ibm.mqetclient.jar, was not be found in the class path.
If the extended transactional client is not required, no action is necessary.

(Note the poetic "was not be found" part of the log message. ;))

...or end up with the following exception when you eventually try to use the IBM MQ JMS client (during application deployment, MDB onMessage() call etc.):

com.ibm.mq.connector.DetailedResourceException: MQJCA1004: Distributed transactions are unavailable., error code: MQJCA1004 An attempt was made to use distributed transactions in an environment where they are not available. Make sure that the WebSphere MQ extended transactional client is installed if required, or use a bindings connection.

There is an article in the JBoss wiki about WebSphere MQ integration but in AS7 things are a bit different. Basically what you have to do is create a module for the extended client (com.ibm.mqetclient.jar and some other jar files) and explicitly declare a dependency to it in the resource adapter (wmq.jmsra.rar).

The resource adapter can be just copied into the deployments directory, but the client library will need a few additional jar files from MQ so it's easier to manage and reference them together as a JBoss server module.

You can find the detailed steps below for JBoss standalone mode.

Creating a JBoss Server Module for the IBM WSMQ Extended Client Library

If you create the module only referring the com.ibm.mqetclient.jar file, you might end up with the MQJCA4005 ("Distributed transactions are not available in client mode") or MQJCA1004 ("Distributed transactions are unavailable") exceptions again. So you might try adding more JAR files from the MQ distribution's Java library directory, or just add all, or start googling. Funnily, the most useful source I've found on this matter can be found on Oracle's website. It's the "Installing Third-Party JAR Files for the WebSphere MQ Adapter" section of the Java CAPS Documentation, and it goes into details about which JAR files are needed for the resource adapter to work. This mostly applies to our situation with JBoss AS7 as well.

So, create the JBOSS_HOME/modules/com/ibm/mqetclient/main/ directory and JBOSS_HOME/modules/com/ibm/mqetclient/main/module.xml file as:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="com.ibm.mqetclient">

    <resources>
        <resource-root path="com.ibm.mq.jar"/>
        <resource-root path="com.ibm.mqjms.jar"/>
        <resource-root path="com.ibm.mq.jmqi.jar"/>
        <resource-root path="connector.jar"/>
        <resource-root path="dhbcore.jar"/>
        <resource-root path="com.ibm.mqetclient.jar"/>
    </resources>

    <dependencies>
        <module name="javax.api"/>
        <module name="javax.jms.api"/>
    </dependencies>

</module>

Then copy the referenced JAR files from your IBM MQ installation's Java library folder (most probably /opt/mqm/java/lib/ under Linux) into JBOSS_HOME/modules/com/ibm/mqetclient/main/.

After some trial and error to minimize the amount of JAR files I've found that the com.ibm.mq.headers.jar and com.ibm.mq.commonservices.jar files are not needed, that's why they are not listed in the module above. (Note that this is a difference compared to what's in the documentation at Oracle).


Configuring the WebSphere MQ Resource Adapter


Declaring the Dependency to the IBM WSMQ Extended Client Library Module

You have to modify wmq.jmsra.rar/META-INF/MANIFEST.MF by adding the following line:

Dependencies: com.ibm.mqetclient

You could also do this by creating a wmq.jmsra.rar/META-INF/jboss-deployment-structure.xml file as follows, but in this case it has no advantage over MANIFEST.MF as we are not using any additional features of this descriptor. Just for reference:

<jboss-deployment-structure>
    <dependencies>
        <module name="com.ibm.mqetclient" />
    </dependencies>
</jboss-deployment-structure>

Configuring the Adapter in the JBoss Server Instance

Copy the modified wmq.jmsra.rar into JBOSS_HOME/standalone/deployments/.

Define the resource adapter and configure the concection, queues etc. in JBOSS_HOME/standalone/configuration/standalone.xml, it's gonna be something like this:

<subsystem xmlns="urn:jboss:domain:resource-adapters:1.0">
    <resource-adapters>
        <resource-adapter>
            <archive>
                wmq.jmsra.rar
            </archive>
            <transaction-support>XATransaction</transaction-support>
            <connection-definitions>
                <connection-definition class-name="com.ibm.mq.connector.outbound.ManagedConnectionFactoryImpl" jndi-name="java:/jms/MyConnectionFactory" enabled="true" use-java-context="false" pool-name="MyConnectionFactory">
                    <config-property name="port">
                        1414
                    </config-property>
                    <config-property name="hostName">
                        localhost
                    </config-property>
                    <config-property name="channel">
                        MY.CHANNEL
                    </config-property>
                    <config-property name="transportType">
                        CLIENT
                    </config-property>
                    <config-property name="queueManager">
                        MY.QM
                    </config-property>
                </connection-definition>
            </connection-definitions>
            <admin-objects>
                <admin-object class-name="com.ibm.mq.connector.outbound.MQQueueProxy" jndi-name="java:/jms/MyQueue1" enabled="true" use-java-context="false" pool-name="MyQueue1Pool">
                    <config-property name="baseQueueName">
                        MY.QUEUE.1
                    </config-property>
                    <config-property name="baseQueueManagerName">
                        MY.QM
                    </config-property>
                </admin-object>
                <admin-object class-name="com.ibm.mq.connector.outbound.MQQueueProxy" jndi-name="java:/jms/MyQueue2" enabled="true" use-java-context="false" pool-name="MyQueue2Pool">
                    <config-property name="baseQueueName">
                        MY.QUEUE.2
                    </config-property>
                    <config-property name="baseQueueManagerName">
                        MY.QM
                    </config-property>
                </admin-object>
            </admin-objects>
        </resource-adapter>
    </resource-adapters>
</subsystem>

Take care here, as it seems that the pool names (pool-name attributes of the admin-object elements) should be unique. (The "5.2.1. Resource adapter descriptor" section of the documentation is not totally clear about what is the exact meaning of the pool-name property. See JBoss forum "Websphere MQ integration" for details and related possible exceptions.)

Also add the adapter reference to the EJB subsystem:
<subsystem xmlns="urn:jboss:domain:ejb3:1.3">
    <mdb>
        <resource-adapter-ref resource-adapter-name="wmq.jmsra.rar" />
        <bean-instance-pool-ref pool-name="mdb-strict-max-pool" />
    </mdb>
    ...
</subsystem>

Application Configuration

That's all. Now you should be able to use the resource adapter in your application. To do this, you have to reference the resource adapter either using annotations or deployment descriptors. Here you can see an example jboss.xml on how to configure an MDB to use the adapter:

<?xml version="1.0" encoding="UTF-8"?>
<jboss>
    <security-domain>mydomain</security-domain>
    <enterprise-beans>
        <message-driven>
            <ejb-name>MyVerySmartMDB</ejb-name>
            <resource-adapter-name>wmq.jmsra.rar</resource-adapter-name>
            <configuration-name>Standard Message Driven Bean</configuration-name>

            <resource-ref>
                <description>My WSMQ ConnectionFactory</description>
                <res-ref-name>jms/ConnectionFactory</res-ref-name>
                <jndi-name>java:/jms/ConnectionFactory</jndi-name>
                <res-type>javax.jms.ConnectionFactory</res-type>
                <res-auth>Container</res-auth>
            </resource-ref>
        </message-driven>
    </enterprise-beans>
</jboss>

And that's all. Really. ;)

Cheers.

Infinispan Cache Naming: Is "config" a Reserved Word?

Assume you have some EJBs, e.g. an EJB 3.1 singleton, and some Infinispan caches defined in you JBoss AS7 instance.
@Resource(mappedName = "java:jboss/infinispan/cache/mycontainer/mycache")
protected Cache<String, String> mycache;
The @Resource annotation here is referring to an Infinispan cache defined in JBoss AS7 as:
<cache-container name="mycontainer" default-cache="mycache">
    <transport lock-timeout="60000"/>
    <replicated-cache name="mycache" mode="SYNC" batching="true">
        <locking isolation="READ_COMMITTED" />
    </replicated-cache>
</cache-container>
Now if you try to add another cache named "config" as follows:
    <replicated-cache name="config" mode="SYNC" batching="true">
        <locking isolation="READ_COMMITTED" />
    </replicated-cache>
...and try to inject it into your bean:
@Resource(mappedName = "java:jboss/infinispan/cache/mycontainer/config")
protected Cache<String, String> config;
...you might encounter the following deployment error:
JBAS015870: Deploy of deployment "MyApplication" was rolled back with failure message {
...
"jboss.naming.context.java.module.\"MyApplication\".\"MyApplication\".env.\"com.example.MyBean\".config Missing[jboss.naming.context.java.jboss.infinispan.cache.mycontainer.config]",
...
}

Moreover you have no stack traces in the server log, so it's a trial and error process.

But... If you rename the cache to "myconfig", everything works as expected.

So is "config" a reserved word in Infinispan? If so, where is this documented? Anyways...

2012. november 16., péntek

JBoss AS7 JNDI and Migration from AS6

When you migrate an application from JBoss AS6 to AS7, probably you will encounter a lot of exceptions if you do not read the AS7 migration guide beforehand. :) This is also the case when naming your objects in JNDI, so if you use JNDI lookups, @Resource annotations, ejb-jar.xml or web.xml resource references etc., then make sure to read the AS7 JNDI Reference first. Also please note that in files like web.xml and ejb-jar.xml, the jndi-name element in the resource-ref seems to be mandatory, contrary to AS6 or other application servers.

    My JMS Connection Factory
    jms/MyConnectionFactory
    java:/jms/MyConnectionFactory
    javax.jms.ConnectionFactory
    Container

Keeping things like this in mind can drastically reduce the time spent on investigating deployment errors like the one below:
Unknown error

Unexpected HTTP response: 500

Request
{
    "address" => [("deployment" => "MyApp.war")],
    "operation" => "deploy"
}

Response

Internal Server Error
{
    "outcome" => "failed",
    "failure-description" => {"JBAS014771: Services with missing/unavailable dependencies" => [
        "jboss.deployment.unit.\"MyApp.war\".component.\"javax.faces.webapp.FacesServlet\".START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".jboss.security.jacc Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.MySessionBean1.START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.\"javax.servlet.jsp.jstl.tlv.ScriptFreeTLV\".START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.\"org.apache.catalina.servlets.DefaultServlet\".START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".jndiDependencyService Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.MySessionBean2.START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.\"org.apache.jasper.servlet.JspServlet\".START Missing[JBAS014861: ]",
        "jboss.naming.context.java.module.\"MyApp\".\"MyApp\".env.jms.MyConnectionFactory Missing[jboss.naming.context.java.jboss.resources.jms.MyConnectionFactory]",
        "jboss.web.deployment.default-host./myapp Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.\"org.jboss.as.weld.webtier.jsp.JspInitializationListener\".START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.MySessionBean3.START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.\"javax.servlet.jsp.jstl.tlv.PermittedTaglibsTLV\".START Missing[JBAS014861: ]",
        "jboss.deployment.unit.\"MyApp.war\".component.MySessionBean4.START Missing[JBAS014861: ]"
    ]},
    "rolled-back" => true
}
In this example, JBoss complains about every possible component of the web application, so it's kind of difficult to spot the real (and only) error in the deployment:
...
"jboss.naming.context.java.module.\"MyApp\".\"MyApp\".env.jms.MyConnectionFactory Missing[jboss.naming.context.java.jboss.resources.jms.MyConnectionFactory]",
        "jboss.web.deployment.default-host./myapp Missing[JBAS014861: ]",
...
The error was caused by the previously missing jndi-name element which was not needed when deploying the same application to AS6:
...
    java:/jms/MyConnectionFactory
...
After adding the JNDI name to the configuration, the deployment worked fine on AS7.
Note taken.

Java Alternatives on Ubuntu

This post shows how to install alternatives for all your JDK binaries in Ubuntu. Assume you have JDK 1.6.0_37 installed which contains the following binaries:
$ find /usr/lib/jvm/jdk1.6.0_37/bin -type f -executable | sort
/usr/lib/jvm/jdk1.6.0_37/bin/appletviewer
/usr/lib/jvm/jdk1.6.0_37/bin/apt
/usr/lib/jvm/jdk1.6.0_37/bin/ControlPanel
/usr/lib/jvm/jdk1.6.0_37/bin/extcheck
/usr/lib/jvm/jdk1.6.0_37/bin/HtmlConverter
/usr/lib/jvm/jdk1.6.0_37/bin/idlj
/usr/lib/jvm/jdk1.6.0_37/bin/jar
/usr/lib/jvm/jdk1.6.0_37/bin/jarsigner
/usr/lib/jvm/jdk1.6.0_37/bin/java
/usr/lib/jvm/jdk1.6.0_37/bin/javac
/usr/lib/jvm/jdk1.6.0_37/bin/javadoc
/usr/lib/jvm/jdk1.6.0_37/bin/javah
/usr/lib/jvm/jdk1.6.0_37/bin/javap
/usr/lib/jvm/jdk1.6.0_37/bin/javaws
/usr/lib/jvm/jdk1.6.0_37/bin/jconsole
/usr/lib/jvm/jdk1.6.0_37/bin/jcontrol
/usr/lib/jvm/jdk1.6.0_37/bin/jdb
/usr/lib/jvm/jdk1.6.0_37/bin/jhat
/usr/lib/jvm/jdk1.6.0_37/bin/jinfo
/usr/lib/jvm/jdk1.6.0_37/bin/jmap
/usr/lib/jvm/jdk1.6.0_37/bin/jps
/usr/lib/jvm/jdk1.6.0_37/bin/jrunscript
/usr/lib/jvm/jdk1.6.0_37/bin/jsadebugd
/usr/lib/jvm/jdk1.6.0_37/bin/jstack
/usr/lib/jvm/jdk1.6.0_37/bin/jstat
/usr/lib/jvm/jdk1.6.0_37/bin/jstatd
/usr/lib/jvm/jdk1.6.0_37/bin/jvisualvm
/usr/lib/jvm/jdk1.6.0_37/bin/keytool
/usr/lib/jvm/jdk1.6.0_37/bin/native2ascii
/usr/lib/jvm/jdk1.6.0_37/bin/orbd
/usr/lib/jvm/jdk1.6.0_37/bin/pack200
/usr/lib/jvm/jdk1.6.0_37/bin/policytool
/usr/lib/jvm/jdk1.6.0_37/bin/rmic
/usr/lib/jvm/jdk1.6.0_37/bin/rmid
/usr/lib/jvm/jdk1.6.0_37/bin/rmiregistry
/usr/lib/jvm/jdk1.6.0_37/bin/schemagen
/usr/lib/jvm/jdk1.6.0_37/bin/serialver
/usr/lib/jvm/jdk1.6.0_37/bin/servertool
/usr/lib/jvm/jdk1.6.0_37/bin/tnameserv
/usr/lib/jvm/jdk1.6.0_37/bin/unpack200
/usr/lib/jvm/jdk1.6.0_37/bin/wsgen
/usr/lib/jvm/jdk1.6.0_37/bin/wsimport
/usr/lib/jvm/jdk1.6.0_37/bin/xjc
You can do it manually for some of the important executables like java and javac:
sudo su
update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.6.0_37/bin/java" 3000
update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.6.0_37/bin/javac" 3000
...
...but there are a lot of necessary binaries, so you can just use the command below to create alternatives for all of the executables in one step:
for f in `find /usr/lib/jvm/jdk1.6.0_37/bin/ -executable -type f | sort`; do update-alternatives --install "/usr/bin/${f##*/}" "${f##*/}" "$f" 3000; done
Or if you just want to have the commands for copy-paste etc.:
for f in `find /usr/lib/jvm/jdk1.6.0_37/bin/ -executable -type f | sort`; do echo "update-alternatives --install \"/usr/bin/${f##*/}\" \"${f##*/}\" \"$f\" 3000"; done
Regards.

2012. november 12., hétfő

Ubuntu 12.10 on Dell XPS 15z

Situation: Ubuntu 12.10 (including the live CD installer) hangs on startup on a Dell XPS 15z laptop. This may be the case for some earlier versions, and some other distros and laptops as well.

Finally I've found the kernel parameter that is needed specifically for Ubuntu 12.10 and the Dell XPS 15z laptop.

In the vast ocean of posts covering this and similar topics, the ones below were useful in the end. Thanks.

So the parameter is not acpi=noirq (still hangs) or acpi=off (overkill), but:

pci=noacpi

IBM MQ object authorization for JMS clients

Assume you have a queue manager named QMA, a queue named QUEUE1, and an appropriate channel defined in your IBM WebSphere MQ instance with proper authorization records for group "mqclient", e.g. you did something like:

setmqaut -m QMA -t qmgr -g mqclient +connect
setmqaut -m QMA -n QUEUE1 -t q -g mqclient +get +put +browse

Now, if you can successfully connect to the queue manager and the queue from a non-JMS IBM MQ client, e.g. the sample get client application:

$ /opt/mqm/samp/bin/amqsgetc QUEUE1 QMA
Sample AMQSGET0 start
no more messages
Sample AMQSGET0 end
$

...but not from a Java JMS client using the WebSphere MQ client libraries, because you get a "MQRC_NOT_AUTHORIZED (2035)" error, then you might end up wondering what's wrong with your client. The answer is in the "MQRC_NOT_AUTHORIZED (2035) for Java client accessing server" technote at IBM:

Cause
The WebSphere MQ Java classes inquire on some of the queue manager attributes while connecting.
Resolving the problem
Give the user +inquire authority to the queue manager object.
Example:
setmqaut -m QMGR -t qmgr -p user1 +connect
So in our case:
setmqaut -m QMA -t qmgr -g mqclient +connect +inq
setmqaut -m QMA -n QUEUE1 -t q -g mqclient +get +put +browse +inq

Bottom line: if you plan to access your MQ objects from JMS clients, give also +inquire authority to your clients. And yes, as the second line shows, this is needed not only for queue managers but for queues as well, independent of whether you are using QueueBrowsers or MessageConsumers.

Note: Don't forget to REFRESH SECURITY in MQSC or restart your queue manager QMA after this.

Problems with update() Operation in Human Task Data Controls

Suppose you have an ADF 11.1.1.5.0 UI implementing a BPM human task, i.e. a bounded task flow using the generated human task data controls (based on the task definition), and your specific BTF implementation with your pages. To update attributes in the BPM payload, you can use the update() operation of the HT data control. This is working fine on the auto-generated task form and on any other JSF page.

In ADF task flows, any kind of activity can have bindings/pageDefs defined, which means that method call or data control operation activities in your BTF can be used to access your custom BPM payload or any other BPM metadata coming from the getTaskDetails() operation in the data control.

The sad part comes as soon as you try to call the update() operation directly from the BTF, and not from a JSF view (e.g. in a button action). For example, you can do that in a method call activity which has an associated binding container that contains all the necessary bindings for getTaskDetails(), its results, and the update() operation inside it. But then you realize that it is not working, and throws the following NullPointerException:

<Apr 11, 2012 11:41:48 AM CEST> <Error> <oracle.adf.model.adapter>
<BEA-000000> <Exception invoking method from XML data control.
Cause:java.lang.NullPointerException:
java.lang.NullPointerException
       at oracle.bpel.services.workflow.datacontrol.WorkflowService.saveHistory(WorkflowService.java:831)
       at oracle.bpel.services.workflow.datacontrol.WorkflowService.invokeWorkflowDCMethods(WorkflowService.java:236)
       at oracle.bpel.services.datacontrol.XSDDataControl.invokeWorkflowOperation(XSDDataControl.java:373)
       at oracle.bpel.services.datacontrol.XSDDataControl.invokeOperation(XSDDataControl.java:467)
       at oracle.adf.model.bean.DCBeanDataControl.invokeMethod(DCBeanDataControl.java:430)
       at oracle.adf.model.binding.DCInvokeMethod.callMethod(DCInvokeMethod.java:261)
       at oracle.jbo.uicli.binding.JUCtrlActionBinding.doIt(JUCtrlActionBinding.java:1635)
       at oracle.adf.model.binding.DCDataControl.invokeOperation(DCDataControl.java:2149)
       at oracle.adf.model.bean.DCBeanDataControl.invokeOperation(DCBeanDataControl.java:467)
       at oracle.adf.model.adapter.AdapterDCService.invokeOperation(AdapterDCService.java:307)
       at oracle.jbo.uicli.binding.JUCtrlActionBinding.invoke(JUCtrlActionBinding.java:740)
       at oracle.adf.controller.v2.lifecycle.PageLifecycleImpl.executeEvent(PageLifecycleImpl.java:402)
       at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding._execute(FacesCtrlActionBinding.java:252)
       at oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding.execute(FacesCtrlActionBinding.java:210)
       at mypackage.MyBean.doSomethingAndUpdate(MyBean.java:666)
       ...

After hours of investigation and trial and error of no avail, and without having the sources of the XSDDataControl and WorkflowService classes (which can be found in bpm-workflow-datacontrol.jar), one can only guess why this is happening. (Which, to be honest, is mostly the case when developing human task interfaces, thanks to the poor documentation on human task form development.)

Again, it works when called from a view, but fails with a NPE when called from a method call activity in an ADF task flow, i.e. outside a view, where you do have a FacesContext, but not a UIViewRoot as you have not yet entered any view activity. Is it possible that the WorkflowService.saveHistory() method tries to access the JSF view root? Let's give it a try, and create a fake view before calling update():

// in the method call activity
String someFakeViewId = ...;
FacesContext fctx = FacesContext.getCurrentInstance();
Application application = fctx.getApplication();
ViewHandler viewHandler = application.getViewHandler();
UIViewRoot viewRoot = viewHandler.createView(fctx, someFakeViewId);
fctx.setViewRoot(viewRoot);
// update() after this

If you try this, you can see that it's working now. So we can (vaguely) guess that some call is being made on FacesContext.getViewRoot() in WorkflowService.saveHistory(), which of course does not makes much sense (i.e. calling back to the view layer from a data control).

Until this gets fixed, you can use this as a workaround. Or just avoid using method call activities for BPM payload updates... ;)

2012. május 13., vasárnap

Problems with Bindings Defined on Task Flow Call Activities

In an ADF task flow you can define bindings (create a pageDef) on any kind of activity: method call, task flow call, view etc., but there is a problem when trying to access the binding container upon returning from a task flow call activity and trying to copy the called task flow's return value into the bindings, e.g. into the input value of an attribute binding.

For example, consider the following unbounded task flow (adfc-config):


The CallerPage.jspx page shows buttons for the possible control flow cases. In each of them, we call the test-btf bounded task flow, which is defined as follows:


The HelloPage.jspx page has just an input field for #{pageFlowScope.name} and a button to call #{myBean.hello_action}. When clicking the button, the "hello_action" method is called, which calls the "sayHello" method in our POJO and sets its result into #{pageFlowScope.resultGreeting}. E.g. if you enter "John Doe" and click the button, then you will have "Hello, John Doe!" in this parameter. Then the task flow returns to the parent, with a return parameter named "greeting" containing this result. The "myBean" backing bean looks like this:

package pvarga.test.taskflow.returnvalue.view;

import oracle.adf.model.AttributeBinding;
import oracle.adf.model.BindingContext;
import oracle.adf.share.ADFContext;

import oracle.binding.OperationBinding;


/**
 * Testing backing bean.
 * 
 * @author Patrik Varga
 */
public class MyBackingBean {

    public MyBackingBean() {
        super();
    }

    /**
     * Action for "Say hello" the button.
     * 
     * @return "SAY_HELLO"
     */
    public String hello_action() {
        OperationBinding sayHello = BindingContext.getCurrent().getCurrentBindingsEntry().getOperationBinding("sayHello");
        String greeting = (String)sayHello.execute();
        System.out.println("Greeting is: " + greeting);
        ADFContext.getCurrent().getPageFlowScope().put("resultGreeting", greeting);
        return "SAY_HELLO";
    }

    /**
     * Copy #{pageFlowScope.tempReturnedGreeting} intoto #{bindings.storedGreeting.inputValue}.
     */
    public void copyOutputParameter() {
        AttributeBinding storedGreeting = (AttributeBinding)BindingContext.getCurrent().getCurrentBindingsEntry().getControlBinding("storedGreeting");
        String greeting = (String)ADFContext.getCurrent().getPageFlowScope().get("tempReturnedGreeting");
        storedGreeting.setInputValue(greeting);
        System.out.println("Copied value into storedGreeting AttributeBinding: " + greeting);
    }

}

We have the following POJO which we access through ADF data controls and bindings:

package pvarga.test.taskflow.returnvalue.view;

/**
 * Testing POJO, used through POJO DC.
 * 
 * @author Patrik Varga
 */
public class HelloPojo {

    private String storedName;
    private String storedGreeting;
    
    public String sayHello(String name) {
        return "Hello, " + name + "!";
    }

    public void setStoredGreeting(String storedGreeting) {
        this.storedGreeting = storedGreeting;
    }

    public String getStoredGreeting() {
        return storedGreeting;
    }

    public void setStoredName(String storedName) {
        this.storedName = storedName;
    }

    public String getStoredName() {
        return storedName;
    }

}

Now, the three different control flow cases and task flow call activities (test-btf1, test-btf2, test-btf3) are defined as follows:

The CALL_HELLO_BTF_PAGEFLOW case calls the test-btf task flow in the test-btf2 activity. This is just for testing the BTF, it is not using bindings but only pageFlowScope parameters.

The CALL_HELLO_BTF_BINDINGS case calls the test-btf task flow in the test-btf1 activity. This activity has an associated page definition so that we can access the binding container when passing input parameters and receiving output parameters. The input parameter is coming from the bindings (#{bindings.storedName.inputValue}), and the activity is configured to set the "greeting" return value into #{bindings.storedGreeting.inputValue}. Unfortunately, this fails at runtime with the following exception:

javax.el.PropertyNotFoundException: Target Unreachable, identifier 'bindings' resolved to null
        at com.sun.el.parser.AstValue.getTarget(Unknown Source)
        at com.sun.el.parser.AstValue.setValue(Unknown Source)
        at com.sun.el.ValueExpressionImpl.setValue(Unknown Source)
        at oracle.adf.controller.internal.util.ELInterfaceImpl.setExpression(ELInterfaceImpl.java:122)
        at oracle.adfinternal.controller.activity.TaskFlowReturnActivityLogic.storeReturnValues(TaskFlowReturnActivityLogic.java:511)
        at oracle.adfinternal.controller.activity.TaskFlowReturnActivityLogic.execute(TaskFlowReturnActivityLogic.java:200)
        at oracle.adfinternal.controller.engine.ControlFlowEngine.executeActivity(ControlFlowEngine.java:993)
        at oracle.adfinternal.controller.engine.ControlFlowEngine.doRouting(ControlFlowEngine.java:879)
        at oracle.adfinternal.controller.engine.ControlFlowEngine.doRouting(ControlFlowEngine.java:778)
        at oracle.adfinternal.controller.engine.ControlFlowEngine.routeFromActivity(ControlFlowEngine.java:552)
        at oracle.adfinternal.controller.engine.ControlFlowEngine.performControlFlow(ControlFlowEngine.java:148)
        at oracle.adfinternal.controller.application.NavigationHandlerImpl.handleAdfcNavigation(NavigationHandlerImpl.java:109)
        at oracle.adfinternal.controller.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:78)
        at org.apache.myfaces.trinidadinternal.application.NavigationHandlerImpl.handleNavigation(NavigationHandlerImpl.java:43)
        at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:130)
        at org.apache.myfaces.trinidad.component.UIXCommand.broadcast(UIXCommand.java:190)
        at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:475)
        at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:756)
        at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl._invokeApplication(LifecycleImpl.java:788)
        at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl._executePhase(LifecycleImpl.java:306)
        at oracle.adfinternal.view.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:186)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
        at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
        at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:26)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at oracle.adf.model.servlet.ADFBindingFilter.doFilter(ADFBindingFilter.java:205)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at oracle.adfinternal.view.faces.webapp.rich.RegistrationFilter.doFilter(RegistrationFilter.java:106)
        at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl$FilterListChain.doFilter(TrinidadFilterImpl.java:446)
        at oracle.adfinternal.view.faces.activedata.AdsFilter.doFilter(AdsFilter.java:60)
        at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl$FilterListChain.doFilter(TrinidadFilterImpl.java:446)
        at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl._doFilterImpl(TrinidadFilterImpl.java:271)
        at org.apache.myfaces.trinidadinternal.webapp.TrinidadFilterImpl.doFilter(TrinidadFilterImpl.java:177)
        at org.apache.myfaces.trinidad.webapp.TrinidadFilter.doFilter(TrinidadFilter.java:92)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at oracle.security.jps.ee.http.JpsAbsFilter$1.run(JpsAbsFilter.java:111)
        at java.security.AccessController.doPrivileged(Native Method)
        at oracle.security.jps.util.JpsSubject.doAsPrivileged(JpsSubject.java:313)
        at oracle.security.jps.ee.util.JpsPlatformUtil.runJaasMode(JpsPlatformUtil.java:413)
        at oracle.security.jps.ee.http.JpsAbsFilter.runJaasMode(JpsAbsFilter.java:94)
        at oracle.security.jps.ee.http.JpsAbsFilter.doFilter(JpsAbsFilter.java:161)
        at oracle.security.jps.ee.http.JpsFilter.doFilter(JpsFilter.java:71)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at oracle.dms.servlet.DMSServletFilter.doFilter(DMSServletFilter.java:136)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at weblogic.servlet.internal.RequestEventsFilter.doFilter(RequestEventsFilter.java:27)
        at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:56)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3715)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3681)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
        at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2277)
        at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2183)
        at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1454)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:178)

I cannot see the reason why this should not work, especially that for task flow input parameters you can use ELs referring the binding container, e.g. to pass the value of an attribute binding as an input parameter, as seen above. It seems that the binding container is released too soon when using task flow call activities.

Moreover, setting the values of attribute bindings works as expected in a method call activity having a binding container, so this can be used as a workaround.

This is what I did in the CALL_HELLO_BTF_BINDINGS_WORKAROUND control flow case of the task flow: instead of immediately copying the task flow call activity's return parameter into the bindings (test-btf3), just copy it to a pageFlowScope parameter (#{pageFlowScope.tempReturnedGreeting}), and in the next method call activity (copyOutputParameter), copy this parameter into the preferred attribute binding (see the MyBackingBean.copyOutputParameter() method).

So, until this problem gets fixed or at least explained, you can use this as a workaround.
Cheers. :)

2012. április 15., vasárnap

Tweetanic - Tweet like a Sir!

Hello,

check out project Tweetanic, a concept of my dear friend Mate Nagy for the 100th anniversary of the sinking of the Titanic. The designer was Adam Erdesz, and I contributed with some web development (server and client side).




Have fun! Or:
•••• •- •••- •|••-• ••- -• -•-•--
:)

2012. március 13., kedd

Applying Generics to ADF BC View Objects

Oh well. This is my first post intended for the audience, and not just as a note for myself.
Instead of long introductions, let's get into it immediately. ;)

So this post is about why and how to apply Java generics to ADF BC, more specifically to the view objects, in order to work with them programmatically in a safer and more convenient way.

The problem

If you are working with view objects and view rows programmatically when using ADF BC, e.g. working with rows which are retrieved from a view object, then you may have a lot of code similar to the following snippets.

DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    // Note that you have to cast the row even though the VO is a DepartmentsViewImpl
    // which always returns DepartmentsViewRow instances. 
    DepartmentsViewRow dept = (DepartmentsViewRow)deptVO.next();
    // Do something with dept, e.g. you want to use the generated
    // type-safe methods on the row.
    Number deptID = dept.getId();
    ...
}

Key key = ...;
// You cannot even do this, because the returned object will always be an array of Rows.
DepartmentsViewRow[] foundDepts = (DepartmentsViewRow[])deptVO.findByKey(key, 100);
// Instead you have to cast each Row separately...
Row[] foundDepts = deptVO.findByKey(key, 100);
for (Row row : foundDepts) {
    DepartmentsViewRow dept = (DepartmentsViewRow)row;
    Number deptID = dept.getId();
    ...
}

Here basically you don't have too much compile-time type-safety, and it is error-prone in the sense that you always have to take care to do the proper casts. Moreover, after a while it just feels inconvenient and cumbersome, and can get really annoying. At some point you will have to cast the Row instances to the actual row types if you want to code to the interfaces.

Wouldn't it be nice to write just something like this?

DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    DepartmentsViewRow dept = deptVO.next();
    // Do something with dept
    Number deptID = dept.getId();
    ...
}

Key key = ...;
DepartmentsViewRow[] foundDepts = deptVO.findByKey(key, 100);
for (DepartmentsViewRow dept : foundDepts) {
    // Do something with dept
    Number deptID = dept.getId();
    ...
}


So you might wonder:
  • Why do I always have to cast oracle.jbo.Row to my row class when iterating through row sets, finding rows etc.? Is there no way to avoid this cumbersome code?
  • Why can't I have more compile-time safety when using specific types of view objects? Why is it the developer and not the framework or the design-time tools that need to take care of proper casts?
  • Why is there no (compile-time) relation between the VO and Row types, even if in most cases they have a one-to-one relation?
  • And here we arrive to the question in general: Why is there no Java generics applied to ADF BC at all? Even though the view objects seem a very straightworward candidate to use generics. (Well, most of the row related classes seem good candidates, e.g. Row, ViewObject, RowIterator, RowSet, RowSetIterator. But for other than the ViewObject, I cannot see an easy solution like the one desribed below.)

The idea

Well, until generifying the ADF BC code eventually happens (if ever), we can use a simple workaround to have generic methods in our view objects.
The idea is to have a generic ViewObject class with the row type as type parameter, and to override the VO methods to return instances of our parameterized view row type. Thanks to covariant return types introduced in Java 5, we can actually do this.

Here are the main steps to achieve this:
  1. Create a generic ViewObject interface extension with overridden methods (using covariant return types)
  2. Extend the framework-provided ViewObjectImpl to implement our generic interface (and implement the overridden methods appropriately)
  3. In the actual VO classes (e.g. DepartmentsViewImpl) extend this base class using the appropriate (row) type parameter

Note that using the interface in the first step is optional; and doing the implementation in a separate base class helps us avoid coding it in every actual VO class.

Details of the solution

The steps are detailed below including code snippets.

Create our custom generic interface called GenericViewObject extending the ViewObject interface like this:
public interface GenericViewObject<T extends Row> extends ViewObject {

    @Override
    public T getCurrentRow();

    @Override
    public T next();

    @Override
    public T[] findByKey(Key key, int i);

    @Override
    public T[] getAllRowsInRange();

    // similar overridden signatures for all the methods having return types of Row or Row[]
    ...

}

In the corresponding implementation class (the ViewObjectImpl extension called GenericViewObjectImpl) we can safely cast the Row instances to the actual row type (T) as we know that the VO implementation will return instances of that specific row type.

However, we have a problem when returning arrays of Rows: we cannot cast them to T[], as no matter what the type of the contained Rows is, this will always be just an array of Rows, i.e. a Row[] instance. So we have to find a way to convert the Row[] instance to a T[] instance. The problem with this is that we have to create a new array using reflection (Array.newInstance()), because you cannot instantiate a generic array. In order to do that, we have to know row class, but (in almost all cases) there is no way to get the actual type argument of a generic class at runtime. However, there is one exception to this rule: we can get the actual type arguments of a generic superclass from a subclass. And fortunately this is exactly the case when extending our generic VO base class.

See the code below on how to do that, and store the class token in order to use it later when instantiating new arrays of our type parameter.

public abstract class GenericViewObjectImpl<T extends Row> extends ViewObjectImpl implements GenericViewObject<T> {

    private Class<T> rowClass;
    
    public GenericViewObjectImpl(String string, ViewDefImpl viewDefImpl) {
        super(string, viewDefImpl);
        storeClassToken();
    }

    public GenericViewObjectImpl() {
        super();
        storeClassToken();
    }
    
    /**
     * Get the class token of the actual type argument and store it for later use.
     */
    private final void storeClassToken() {
        // When called from a subclass, this will be GenericViewObjectImpl
        // containing the type parameter.
        ParameterizedType superClass = (ParameterizedType)this.getClass().getGenericSuperclass();
        // The first and only type parameter is the row class.
        this.rowClass = (Class<T>)superClass.getActualTypeArguments()[0];
    }

    /**
     * Convert an array of Rows to an array of the generic type of this class.
     * 
     * @param rows array of rows
     * @return generic array of rows
     */
    private final T[] convertToGenericArray(Row[] rows) {
        if (rows==null) {
            // khm... array-valued method returns null,
            // but it's what super does if we have null here...
            return null;
        }
        T[] genericRows = (T[])Array.newInstance(this.rowClass, rows.length);
        for (int i = 0; i < rows.length; i++) {
            genericRows[i] = (T)rows[i];
        }
        return genericRows;
    }

    @Override
    public T getCurrentRow() {
        return (T)super.getCurrentRow();
    }

    @Override
    public T next() {
        return (T)super.next();
    }

    @Override
    public T[] findByKey(Key key, int i) {
        return convertToGenericArray(super.findByKey(key, i));
    }

    @Override
    public T[] getAllRowsInRange() {
        return convertToGenericArray(super.getAllRowsInRange());
    }

    // similarly for all the other methods
    ...

}

That's all.

Now all you have to do is replace the extends clauses in your view objects, e.g. instead of
public class DepartmentsViewImpl extends ViewObjectImpl {}
...you can write:
public class DepartmentsViewImpl extends GenericViewObjectImpl<DepartmentsViewRow> {}

After this you can start accessing your view rows from your view objects as described above, e.g.:
DepartmentsViewImpl deptVO = appModule.getDepartmentsView1();
while (deptVO.hasNext) {
    DepartmentsViewRow dept = deptVO.next();
    // Do something with dept
    Number deptID = dept.getId();
    ...
}

I hope this will be useful for some of you, or at least to start some discussion about Java generics regarding ADF BC. Your comments are much appreciated.

Cheers. :)

2012. január 22., vasárnap

OpenWebBeans classpath under Tomcat 7

This is just for future reference as I always miss some JARs.
The necessary JAR files in the tomcat/lib directory for OpenWebBeans to work in a JSF application under Tomcat 7 are the followings:
  • from openwebbeans-distribution-1.1.3-binary.zip at http://openwebbeans.apache.org/
    • geronimo-atinject_1.0_spec-1.0.jar
    • geronimo-jcdi_1.0_spec-1.0.jar
    • openwebbeans-impl-1.1.3.jar
    • openwebbeans-resource-1.1.3.jar
    • openwebbeans-spi-1.1.3.jar
    • openwebbeans-tomcat7-1.1.3.jar
    • openwebbeans-web-1.1.3.jar
  • from javassist-3.15.0-GA.zip at http://www.javassist.org
    • javassist.jar
  • from scannotation-1.0.2.zip at http://sourceforge.net/projects/scannotation/
    • scannotation-1.0.2.jar
  • the javax.interceptor API from e.g. the Geronimo distibution:
    • geronimo-interceptor_1.1_spec-1.0.jar