Tuesday, May 5, 2015

Unleash the power of Java API's on your WLST scripts!

Oracle SOA Suite and many other Oracle products have extensive Java API's to expose their functionality. WLST can often be used for relatively course grained actions.  WLST (the version supplied in Weblogic 12.1.3) uses Jython 2.2.1. Jython is the Python scripting language implemented on the Java Virtual Machine. Jython allows easy integration with Java. In this article I describe how you can unleash the power of these Java API's on your WLST scripts!


Considerations

Why WLST and not Java?

For system operators, WLST is easier to work with than Java code. For Java code you need to supply all dependencies in the classpath and updating code requires recompilation. Also Java code can be a bit verbose compared to WLST code and requires (for most developers) more time to write. With a WLST script you do not need to provide dependencies since they are already present in the classpath set by the wlst.sh (of wlst.cmd) command used to start WLST scripts and you can more easily update the scripts without need for recompilation.

Why use Java classes in WLST?

In this example I wanted to create a script which undeployed composites which where not the default revision (are not called by default). Also I wanted to look at the instances. I did not want to undeploy composites which had running instances (long running instances like BPM and ACM). WLST provides some nifty features to undeploy composites; https://docs.oracle.com/middleware/1213/soasuite/wlst-reference-soa/custom_soa.htm#SOACR2689 for example the sca_undeployComposite command. I did however not see WLST commands I could use to query instances.

Undeploying composites using Java

I started out with a piece of Java code shown below. In order to make the required classes available in your project, you need to import Weblogic Remote Client, JRF API and SOA Runtime (see http://javaoraclesoa.blogspot.nl/2015/01/oracle-soa-suite-12c-soa-instance.html for a more elaborate example of using the Java API). With the Locator class you can find your composites and instances. By calling the MBean oracle.soa.config:Application=soa-infra,j2eeType=CompositeLifecycleConfig,name=soa-infra method removeCompositeForLabel you can undeploy composites from Java. This is based on what I found at https://community.oracle.com/thread/1632905.

 package nl.amis.smeetsm.utils.soa;  
 import java.util.Hashtable;  
 import javax.management.MBeanServerConnection;  
 import javax.management.ObjectName;  
 import javax.management.openmbean.CompositeData;  
 import javax.management.remote.JMXConnector;  
 import javax.management.remote.JMXConnectorFactory;  
 import javax.management.remote.JMXServiceURL;  
 import javax.naming.Context;  
 import oracle.soa.management.facade.Composite;  
 import oracle.soa.management.facade.CompositeInstance;  
 import oracle.soa.management.facade.Locator;  
 import oracle.soa.management.facade.LocatorFactory;  
 import oracle.soa.management.util.CompositeFilter;  
 import oracle.soa.management.util.CompositeInstanceFilter;  
 public class UndeployComposites {  
   Locator myLocator;  
   MBeanServerConnection mbsc;  
   ObjectName mbean;  
   public UndeployComposites(String user, String pass, String host,  
                String port) throws Exception {  
     super();  
     String providerURL = "t3://" + host + ":" + port + "/soa-infra";  
     String mbeanRuntime = "weblogic.management.mbeanservers.runtime";  
     String jmxProtoProviderPackages = "weblogic.management.remote";  
     String mBeanName =  
       "oracle.soa.config:Application=soa-infra,j2eeType=CompositeLifecycleConfig,name=soa-infra";  
     Hashtable jndiProps = new Hashtable();  
     jndiProps.put(Context.PROVIDER_URL, providerURL);  
     jndiProps.put(Context.INITIAL_CONTEXT_FACTORY,  
            "weblogic.jndi.WLInitialContextFactory");  
     jndiProps.put(Context.SECURITY_PRINCIPAL, user);  
     jndiProps.put(Context.SECURITY_CREDENTIALS, pass);  
     myLocator = LocatorFactory.createLocator(jndiProps);  
     String jmxurl =  
       "service:jmx:t3://" + host + ":" + port + "/jndi/" + mbeanRuntime;  
     JMXServiceURL serviceURL = new JMXServiceURL(jmxurl);  
     Hashtable<String, String> ht = new Hashtable<String, String>();  
     ht.put("java.naming.security.principal", user);  
     ht.put("java.naming.security.credentials", pass);  
     ht.put("jmx.remote.protocol.provider.pkgs", jmxProtoProviderPackages);  
     JMXConnector jmxConnector =  
       JMXConnectorFactory.newJMXConnector(serviceURL, ht);  
     jmxConnector.connect();  
     mbsc = jmxConnector.getMBeanServerConnection();  
     mbean = new ObjectName(mBeanName);  
   }  
   private CompositeInstanceFilter getCompositeInstanceFilter() {  
     CompositeInstanceFilter myFilter = new CompositeInstanceFilter();  
     int[] instanceStates =  
     { CompositeInstance.STATE_UNKNOWN, CompositeInstance.STATE_RUNNING,  
      CompositeInstance.STATE_SUSPENDED };  
     myFilter.setStates(instanceStates);  
     return myFilter;  
   }  
   public void undeployComposites() throws Exception {  
     CompositeFilter filter = new CompositeFilter();  
     CompositeInstanceFilter instanceFilter = getCompositeInstanceFilter();  
     int instanceCount = 0;  
     String dnString;  
     Object compositeObjArray = mbsc.getAttribute(mbean, "DeployedComposites");  
     for (Composite myComposite : myLocator.getComposites(filter)) {  
       if (!myComposite.isDefaultRevision()) {  
         instanceCount =  
             myComposite.getInstances(instanceFilter).size();  
         if (instanceCount < 1) {  
           System.out.println("Undeploying: " + myComposite.getCompositeDN());  
           //Get all the CompositeData objects from MBean. They contain DNs  
           //Note- this DN and composite.getDN()/getCompositeDN() are not same. This DN is required for undeploying  
           CompositeData[] compositeData = (CompositeData[])compositeObjArray;  
           dnString = getDNToUndeploy(compositeData, myComposite.getCompositeDN().toString());  
           mbsc.invoke(mbean, "removeCompositeForLabel", new Object[]{dnString},new String[]{"java.lang.String"});  
         }  
       }  
     }  
   }  
   private String getDNToUndeploy(CompositeData[] compositeData,  
                   String compositeToBeUndeployed) throws Exception {  
     String dnString = null;  
     for (CompositeData tmpCData : compositeData) {  
       String tempDN = (String)tmpCData.get("DN");  
       if (tempDN.contains(compositeToBeUndeployed)) {  
         dnString = tempDN;  
         break;  
       }  
     }  
     return dnString;  
   }  
   public static void main(String[] args) throws Exception {  
     System.out.println("Initializing");  
     UndeployComposites me =  
       new UndeployComposites("weblogic", "Welcome01",  
                   "localhost", "7101");  
     System.out.println("Running");  
     me.undeployComposites();  
   }  
 }  

Rewriting the Java code to WLST

Below is the result of rewriting the Java code to WLST. This was suprisingly easy. I noticed though that entire books are written about Jython Java integration. Basically, with the below simple translation steps (which come quite naturally) it became easy to rewrite the Java code to WLST. The resulting example isn't a perfect one on one copy but it provides the same functionality. The first thing is to replace the {} with Python indentation to indicate nesting and to remove the ; from the line endings.

Method calls

The following Java line:

private String getDNToUndeploy(CompositeData[] compositeData, String compositeToBeUndeployed) throws Exception

Becomes in WLST

def getDNToUndeploy(compositeData,compositeToBeUndeployed):

I've not paid attention to the Java access modifiers. Didn't seem very relevant for my script. Because of the introspection properties of Jython, you don't need to specify which exception is thrown.

Types and constructors

There are some other differences between Java and WLST. WLST determines its types by introspection and does not require explicit declarations or casts. Calling a constructor for example looks in Java like:

Hashtable jndiProps = new Hashtable();

and in WLST like

jndiProps = Hashtable()

The effect of the line is exactly the same.

Imports

Although pretty straightforward, the following Java import:

import java.util.Hashtable;

Looks in WLST like

from java.util import Hashtable

Arrays

Converting Python arrays to Java arrays such as String[] and Object[] can be done with the jarray module array function. Be careful when also using a different function which is called array. You have to import one of the methods under a different name as I have done with the array from jarray which is imported as jarray_c below.

 import array  
 from jarray import array as jarray_c  
 from java.util import Hashtable  
 from javax.management import MBeanServerConnection  
 from javax.management import ObjectName  
 from javax.management.openmbean import CompositeData  
 from javax.management.remote import JMXConnector  
 from javax.management.remote import JMXConnectorFactory  
 from javax.management.remote import JMXServiceURL  
 from javax.naming import Context  
 from java.lang import String  
 from java.lang import Object  
 from oracle.soa.management.facade import Composite  
 from oracle.soa.management.facade import CompositeInstance  
 from oracle.soa.management.facade import Locator  
 from oracle.soa.management.facade import LocatorFactory  
 from oracle.soa.management.util import CompositeFilter  
 from oracle.soa.management.util import CompositeInstanceFilter  
 host='localhost'  
 port='7101'  
 username='weblogic'  
 password='Welcome01'  
 providerURL = "t3://" + host + ":" + port + "/soa-infra";  
 mbeanRuntime = "weblogic.management.mbeanservers.runtime";  
 jmxProtoProviderPackages = "weblogic.management.remote";  
 mBeanName = "oracle.soa.config:Application=soa-infra,j2eeType=CompositeLifecycleConfig,name=soa-infra";  
 jndiProps = Hashtable()  
 jndiProps.put(Context.PROVIDER_URL, providerURL)  
 jndiProps.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory")  
 jndiProps.put(Context.SECURITY_PRINCIPAL, username)  
 jndiProps.put(Context.SECURITY_CREDENTIALS, password)  
 myLocator = LocatorFactory.createLocator(jndiProps)  
 jmxurl = "service:jmx:t3://" + host + ":" + port + "/jndi/" + mbeanRuntime  
 serviceURL = JMXServiceURL(jmxurl)  
 ht = Hashtable()  
 ht.put("java.naming.security.principal", username)  
 ht.put("java.naming.security.credentials", password)  
 ht.put("jmx.remote.protocol.provider.pkgs", jmxProtoProviderPackages)  
 jmxConnector = JMXConnectorFactory.newJMXConnector(serviceURL, ht)  
 jmxConnector.connect()  
 mbsc = jmxConnector.getMBeanServerConnection()  
 mbean = ObjectName(mBeanName)  
 instanceFilter = CompositeInstanceFilter()  
 instanceStates = array.array('i', [CompositeInstance.STATE_UNKNOWN, CompositeInstance.STATE_RUNNING, CompositeInstance.STATE_SUSPENDED,CompositeInstance.STATE_RECOVERY_REQUIRED])  
 instanceFilter.setStates(instanceStates)  
 filter = CompositeFilter()  
 def getDNToUndeploy(compositeData,compositeToBeUndeployed):  
   #print compositeToBeUndeployed  
   dnString = '';  
   for tmpCData in compositeData:  
     tempDN = tmpCData.get("DN")  
     #print "tempDN: "+tempDN  
     if compositeToBeUndeployed in tempDN:  
       dnString = tempDN  
       break  
   return dnString;  
 instanceCount = 0;  
 compositeObjArray = mbsc.getAttribute(mbean, "DeployedComposites");  
 for myComposite in myLocator.getComposites(filter):  
   try:  
     if not myComposite.isDefaultRevision():  
       instanceCount = myComposite.getInstances(instanceFilter).size()  
       if instanceCount < 1:  
         #print "Undeploying: " + str(myComposite.getCompositeDN())  
         #Get all the CompositeData objects from MBean. They contain DNs  
         #Note- this DN and composite.getDN()/getCompositeDN() are not same. This DN is required for undeploying  
         dnString = getDNToUndeploy(compositeObjArray, myComposite.getCompositeDN().toString());  
         print "Undeploying "+dnString  
         strarray = ["java.lang.String"]  
         #print "Array made"  
         jarray=jarray_c(strarray,String)  
         objectarray=[dnString]  
         jobjectarray=jarray_c(objectarray,Object)  
         #print "Array converted"  
         mbsc.invoke(mbean, "removeCompositeForLabel", jobjectarray,jarray)  
   except:  
     print "Unexpected error: "+str(sys.exc_info()[0])+" "+str(myComposite.getCompositeDN())  

Conclusion

Rewriting Java to WLST was suprisingly easy. With this example you can now use the full power of the Oracle SOA Suite Java API in WLST scripts to make them even more powerful and versatile. You can of course easily simplify the above WLST code by using sca_undeployComposite for the undeploy action and remove everything related to calling the MBean.