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.

Comments

  1. I read your post and I found this is amazing. Your thought process is wonderful. The way you express yourself is awesome.

    ReplyDelete

Post a Comment