Wednesday 28 March 2012

Oracle Service Bus - Using WLST to secure proxy services

UPDATE: 11g users please see the end of this post!

 One of the requirements for automating deployment of all resources related to Oracle Service Bus 11g was to set group security policy for public facing proxy services. This would involve WLST/MBeans but the default deployment process makes setting security policy impossible. After some 'research' I determined that it was possible with the inclusion of some undocumented apis. In the servicebus implementation there is a Security Configuration MBean which one might 'guess' is used internally to manage access controls to proxy services. For some reason BEA/Oracle does not want us using this... but we have other ideas. When starting WLST make sure to include sb-kernel-impl.jar in the classpath as this holds the Security Configuration MBean. You will also need sb-security.jar. Once you've created an OSB session you can grab the the relevant MBean with the following code:
serviceSecurityConfigurationMBean = findService(String("ServiceSecurityConfiguration.").concat(sessionName), "com.bea.wli.sb.security.management.configuration.ServiceSecurityConfigurationMBean")
You also need to grab the authorizer you want to use. The following code can grab this for you in a variable (I'm guessing there is a more elegant solution to getting the MBean...):
working_directory = pwd()
serverConfig()
xacmlAuthorizer = cd("/SecurityConfiguration/DevelopmentDomain/Realms/myrealm/AuthenticationProviders/DefaultAuthenticator")
cd(working_directory)
The following method will set the provided proxyReference with the policy either for the Web-Service as a whole or a particular operation:
def applyWSSecurityPolicy(serviceSecurityConfigurationMBean, authorizer, proxyReference, policy, operation = None):
 policyHolder = serviceSecurityConfigurationMBean.newAccessControlPolicyHolderInstance(authorizer)
 policyHolder.setPolicyExpression(policy)
 
 if (not operation):
  policyScope = serviceSecurityConfigurationMBean.newDefaultMessagePolicyScope(proxyReference)  
 else:
  policyScope = serviceSecurityConfigurationMBean.newOperationMessagePolicyScope(proxyReference,operation)
    
 serviceSecurityConfigurationMBean.setAccessControlPolicy(policyScope,policyHolder)
An example:
applyWSSecurityPolicy(serviceSecurityConfigurationMBean, xacmlAuthorizer, ref, "Grp(Administrators)", "deleteAllUsers")
And the following method will do the same at the transport level:
def applyTransportSecurityPolicy(serviceSecurityConfigurationMBean, authorizer, proxyReference, policy):
 policyHolder = serviceSecurityConfigurationMBean.newAccessControlPolicyHolderInstance(authorizer)
 policyHolder.setPolicyExpression(policy)
 
 policyScope = serviceSecurityConfigurationMBean.newTransportPolicyScope(proxyReference)
 
 serviceSecurityConfigurationMBean.setAccessControlPolicy(policyScope,policyHolder)
An example:
applyTransportSecurityPolicy(serviceSecurityConfigurationMBean, xacmlAuthorizer, ref, "Grp(Administrators)")
Remember to activate the session when you're done using the sessionMBean.acxtivateSession method. 

UPDATE: I've just started our migration to 11g and fixed the issue. For some reason Oracle made the ServiceSecurityConfigurationMBean very difficult to find. You can 'obtain' it again with the following WLST:
from java.lang.reflect import Proxy
custom()
cd("com/bea")
security_mbean_ih = MBeanServerInvocationHandler(mbs,ObjectName("com.bea:Name=%s.%s,Type=%s" % (ServiceSecurityConfigurationMBean.NAME,session_name,ServiceSecurityConfigurationMBean.TYPE)))
serviceSecurityConfigurationMBean = Proxy.newProxyInstance(ServiceSecurityConfigurationMBean.getClassLoader(),jarray.array([ServiceSecurityConfigurationMBean],java.lang.Class),security_mbean_ih)  
You can then use serviceSecurityConfigurationMBean as normal.

Tuesday 20 December 2011

Weblogic Server JMS reconnection pain

For many months we have had trouble with JMS clients to Weblogic Server (v9.2) repeatedly throwing the following error when sending messages to the JMS server after it had gone down:
org.omg.CORBA.COMM_FAILURE: vmcid: SUN minor code: 203 completed: No
at com.sun.corba.se.impl.logging.ORBUtilSystemException.writeErrorSend(ORBUtilSystemException.java:2259)
at com.sun.corba.se.impl.logging.ORBUtilSystemException.writeErrorSend(ORBUtilSystemException.java:2281)
at com.sun.corba.se.impl.transport.SocketOrChannelConnectionImpl.writeLock(SocketOrChannelConnectionImpl.java:957)
at com.sun.corba.se.impl.encoding.BufferManagerWriteStream.sendFragment(BufferManagerWriteStream.java:86)
at com.sun.corba.se.impl.encoding.BufferManagerWriteStream.sendMessage(BufferManagerWriteStream.java:104)
at com.sun.corba.se.impl.encoding.CDROutputObject.finishSendingMessage(CDROutputObject.java:144)
at com.sun.corba.se.impl.protocol.CorbaMessageMediatorImpl.finishSendingRequest(CorbaMessageMediatorImpl.java:247)
at com.sun.corba.se.impl.protocol.CorbaClientRequestDispatcherImpl.marshalingComplete1(CorbaClientRequestDispatcherImpl.java:355)
at com.sun.corba.se.impl.protocol.CorbaClientRequestDispatcherImpl.marshalingComplete(CorbaClientRequestDispatcherImpl.java:336)
at com.sun.corba.se.impl.protocol.CorbaClientDelegateImpl.invoke(CorbaClientDelegateImpl.java:129)
at com.sun.corba.se.impl.protocol.CorbaClientDelegateImpl.is_a(CorbaClientDelegateImpl.java:213)
at org.omg.CORBA.portable.ObjectImpl._is_a(ObjectImpl.java:112)
at weblogic.corba.j2ee.naming.Utils.narrowContext(Utils.java:126)
at weblogic.corba.j2ee.naming.InitialContextFactoryImpl.getInitialContext(InitialContextFactoryImpl.java:94)
at weblogic.corba.j2ee.naming.InitialContextFactoryImpl.getInitialContext(InitialContextFactoryImpl.java:31)
at weblogic.jndi.WLInitialContextFactory.getInitialContext(WLInitialContextFactory.java:41)
at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
at javax.naming.InitialContext.init(InitialContext.java:223)
at javax.naming.InitialContext.(InitialContext.java:197)

My initial attempt was to see if it was an issue with the CORBA code - this resulted in finding an unrelated race condition in the JDK CORBA implementation that created a similar exception. A bug report to Sun can be found here: Bug ID: 7056731 - Race condition in CORBA code causes re-use of ABORTed connections. Unfortunately this wasn't on the right track - it seems that the default implementation of the RMI/CORBA/IIOP connection to Weblogic Server as part of the 'wljmsclient.jar' wasn't able to recover broken connections (!) - since some kind of singleton seemed to be used so we were forced to restart the application each time the Weblogic Server instance went down. This is a Bad Thing.

After some research I found that the full BEA/Oracle t3 protocol would be able to gracefully recover closed connections: Overview of Stand-alone Clients. To use the proper t3 protocol we had to generate 'wlfullclient.jar' which was ~55mb (!... again); unfortunately this didn't seem to be a feature of Weblogic Server 9.2, so I had to improvise. Furthermore, since the 'wlfullclient.jar' bundled up many weblogic-unrelated classes we ran into JAR hell.

My solution was to take two jar files from the WLS server/lib directory: weblogic.jar, webserviceclient+ssl.jar and in order to strip out unnecessary classes I used my test Java JMS test code with the the java parameter: '-verbose:class' to see what classes were used when sending a JMS message. The test code is shown in a previous post. After some experimentation I was able to strip out what classes I found to be unnecessary... note that I was quite conservative in what classes I stripped out (especially the weblogic and com/bea packages) as there might be strange corner cases in practice that invoke classes not seen as part of 'normal' execution.

Once this was done I created my own JAR file 'wlfullclient-stripped.jar' that was made up of WLS v9.2 classes. I also took the MANIFEST.MF from 'wlclient.jar' just in case something relied on this for the client. A list of all classes that I ended up using from these packages can be found here.

Once this JAR file was tested I needed to upload it to our Maven Repository for easy use within our projects:
mvn deploy:deploy-file -DrepositoryId=repo -DgroupId=com.bea.weblogic -DartifactId=wlfullclient -Dversion=9.2.2.0 -Dpackaging=jar -Dfile=wlfullclient-stripped-9.2.jar -Durl=http://repo.com/repository/external -Dclassifier=stripped
Since I stripped out the javax JMS/JTA classes out of the JAR these had to be added to our Maven repository as artifacts (these are not part of the 'standard' Maven repos):
mvn deploy:deploy-file -DrepositoryId=repo -DgroupId=javax.transaction -DartifactId=jta -Dversion=1.1 -Dpackaging=jar -Dfile=jta-1.1.jar -Durl=http://repo.com/repository/external
mvn deploy:deploy-file -DrepositoryId=repo -DgroupId=javax.jms -DartifactId=jms -Dversion=1.1 -Dpackaging=jar -Dfile=jms-1.1.jar -Durl=http://repo.com/repository/external

The resulting wlfullclient-stripped.jar is ~22mb... certainly not small but, without further testing as to the exact set of classes used as part of sending a JMS message, is unavoidable. To use the JMS client in a Maven repo the following dependencies are added:
<dependency>
      <groupId>javax.jms</groupId>
      <artifactId>jms</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>javax.transaction</groupId>
      <artifactId>jta</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>com.bea.weblogic</groupId>
      <artifactId>wlfullclient</artifactId>
      <version>9.2.2.0</version>
      <classifier>stripped</classifier>
    </dependency>

Running it is still not without problems. The following error was encountered when trying to send a message to the WLS JMS Server:
javax.naming.CommunicationException [Root exception is java.rmi.UnmarshalException: failed to unmarshal class weblogic.security.acl.internal.AuthenticatedUser; nested exception is: 
 java.io.StreamCorruptedException: invalid type code: 31]
 at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:85)
 at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:41)
 at weblogic.jndi.WLInitialContextFactoryDelegate.toNamingException(WLInitialContextFactoryDelegate.java:773)
 at weblogic.jndi.WLInitialContextFactoryDelegate.pushSubject(WLInitialContextFactoryDelegate.java:673)
 at weblogic.jndi.WLInitialContextFactoryDelegate.newContext(WLInitialContextFactoryDelegate.java:466)
 at weblogic.jndi.WLInitialContextFactoryDelegate.getInitialContext(WLInitialContextFactoryDelegate.java:373)
 at weblogic.jndi.Environment.getContext(Environment.java:307)
 at weblogic.jndi.Environment.getContext(Environment.java:277)
 at weblogic.jndi.WLInitialContextFactory.getInitialContext(WLInitialContextFactory.java:117)
 at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
 at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
 at javax.naming.InitialContext.init(InitialContext.java:223)
 at javax.naming.InitialContext.(InitialContext.java:197)
...

This was resolved (after some searching) by adding the following system parameter: -Dsun.lang.ClassLoader.allowArraySyntax=true. Note that you will have to add it to MAVEN_OPTS if using the exec plugin. One final issue was that if using t3s (i.e. SSL) then the BEA WLS license is required; or the following error would appear:
java.lang.RuntimeException: Exception occurred while reading the license file.
        at weblogic.security.utils.SSLSetup.getLicenseLevel(SSLSetup.java:143)
        at weblogic.security.utils.SSLSetup.getSSLDelegateInstance(SSLSetup.java:196)
        at weblogic.security.utils.SSLContextWrapper.(SSLContextWrapper.java:27)
        at weblogic.security.utils.SSLContextWrapper.getInstance(SSLContextWrapper.java:22)
        at weblogic.security.utils.SSLSetup.getSSLContext(SSLSetup.java:380)
        at weblogic.security.SSL.SSLClientInfo.getSSLSocketFactory(SSLClientInfo.java:56)
        at weblogic.socket.ChannelSSLSocketFactory.getSocketFactory(ChannelSSLSocketFactory.java:125)
        at weblogic.socket.ChannelSSLSocketFactory.createSocket(ChannelSSLSocketFactory.java:75)
        at weblogic.socket.AbstractMuxableSocket.createSocket(AbstractMuxableSocket.java:207)
        at weblogic.rjvm.t3.MuxableSocketT3.newSocketWithRetry(MuxableSocketT3.java:252)
        at weblogic.rjvm.t3.MuxableSocketT3.connect(MuxableSocketT3.java:421)
        at weblogic.rjvm.t3.MuxableSocketT3S.createConnection(MuxableSocketT3S.java:79)
        at weblogic.rjvm.ConnectionManager.createConnection(ConnectionManager.java:1749)
        at weblogic.rjvm.ConnectionManager.findOrCreateConnection(ConnectionManager.java:1410)
        at weblogic.rjvm.ConnectionManager.bootstrap(ConnectionManager.java:448)
        at weblogic.rjvm.ConnectionManager.bootstrap(ConnectionManager.java:326)
        at weblogic.rjvm.RJVMManager.findOrCreateRemoteInternal(RJVMManager.java:261)
        at weblogic.rjvm.RJVMManager.findOrCreate(RJVMManager.java:204)
        at weblogic.rjvm.RJVMFinder.findOrCreateRemoteServer(RJVMFinder.java:226)
        at weblogic.rjvm.RJVMFinder.findOrCreate(RJVMFinder.java:189)
        at weblogic.rjvm.ServerURL.findOrCreateRJVM(ServerURL.java:154)
        at weblogic.jndi.WLInitialContextFactoryDelegate$1.run(WLInitialContextFactoryDelegate.java:342)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:363)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:147)
        at weblogic.jndi.WLInitialContextFactoryDelegate.getInitialContext(WLInitialContextFactoryDelegate.java:337)
        at weblogic.jndi.Environment.getContext(Environment.java:307)
        at weblogic.jndi.Environment.getContext(Environment.java:277)
        at weblogic.jndi.WLInitialContextFactory.getInitialContext(WLInitialContextFactory.java:117)
        at javax.naming.spi.NamingManager.getInitialContext(Unknown Source)
        at javax.naming.InitialContext.getDefaultInitCtx(Unknown Source)
        at javax.naming.InitialContext.init(Unknown Source)
        at javax.naming.InitialContext.(Unknown Source)
...
This could be resolved by grabbing the BEA license for WLS from the Oracle site. It then needed to be placed somewhere that the code could find it, I found that setting -Dbea.home=/etc/some/dir and placing license.bea under /etc/some/dir (i.e. /etc/some/dir/license.bea) worked as expected.

One final unresolved issue is that sometimes the Weblogic code would throw the following exception the first time it tried to retrieve the JNDI InitialContext:
javax.naming.CommunicationException [Root exception is java.net.ConnectException: t3s://wli.dev.auckland.ac.nz:8001: Destination unreachable; nested exception is: 
 java.io.IOException: Stream closed.; No available router to destination]
 at weblogic.jndi.internal.ExceptionTranslator.toNamingException(ExceptionTranslator.java:49)
 at weblogic.jndi.WLInitialContextFactoryDelegate.toNamingException(WLInitialContextFactoryDelegate.java:773)
 at weblogic.jndi.WLInitialContextFactoryDelegate.getInitialContext(WLInitialContextFactoryDelegate.java:363)
 at weblogic.jndi.Environment.getContext(Environment.java:307)
 at weblogic.jndi.Environment.getContext(Environment.java:277)
 at weblogic.jndi.WLInitialContextFactory.getInitialContext(WLInitialContextFactory.java:117)
 at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:667)
 at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:288)
 at javax.naming.InitialContext.init(InitialContext.java:223)
 at javax.naming.InitialContext.(InitialContext.java:197)
Strangely enough this would only happen once, further requests for the InitialContext were successful... I haven't had time to investigate it further because it's not a show stopper but I suspect it's either because of a clustering or timeout issue... Eitherway, it's something to be aware of.

Sunday 30 October 2011

Apache Camel: Plain Java deployment, Spring, CXF WS client and WS-Security

I needed to create an application that polled one non-standard web-service for changes and sent the results through to another web-service, an ideal use-case for Apache Camel. However I found many configuration issues that made the process a little tricky, I'll track my steps here for future reference.

I had the following requirements for my Camel deployment:

  • externalised Spring properties configuration 
  • a custom component to poll a non-standard web service (I will document this process later) 
  • using CXF to call a web service with WS-Security (username/password to start with) 
  • an plain-java uber-jar for deployment 


I use the Spring properties format to specify properties in the camel-context.xml file (rather than the Camel properties), however I did not want to specify the properties file in the camel-context.xml file (it changes on the deployment machine): this does not fit well with the camel Main method that expects to load everything using the setApplicationContextUri() method. This meant I needed to create a normal Spring application context first, load the properties, refresh the context, assign it to Camel and then set the Camel Main method to start:
public class ChangePublisherMain {
    public static final String[] CAMEL_CONTEXT_PATHS = {"META-INF/spring/camel-context.xml"};

    public static void main(String[] args) throws Exception {

        String propertiesFileLocation = args[0]

        //the Camel start for a Java JAR
        Main main = new Main();

        //create a spring context which we will populate with properties file
        AbstractApplicationContext context = new ClassPathXmlApplicationContext(CAMEL_CONTEXT_PATHS,false);
        PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
        cfg.setLocation(new FileSystemResource(propertiesFileLocation));
        context.addBeanFactoryPostProcessor(cfg);
        context.refresh();

        main.setApplicationContext(context);

        //we want to control exactly when routes are started (after we have properly configuring everything!)
        SpringCamelContext camel = (SpringCamelContext)context.getBean("camelContext");
        camel.start();
        for (RouteDefinition route : camel.getRouteDefinitions()) {
            camel.startRoute(route);
        }

        //block until the application is stopped
        main.enableHangupSupport();
        main.run();

    }
}
(note that the properties is a file system reference passed in as an argument).

I found that the Spring camelContext autoStart was giving me trouble when doing this so I had to disable to autostart in the camel-context file:
    

    
        integration.routes
        
    
This shows where the above camelContext is started. Furthermore, the routes wouldn't start automatically so the main method iterates over each route to start it within the context.

Rather than use POJO's with CXF and Camel I wanted to pass an XML Document directly, using the payload data-type that CXF should use to call the external web service. The external web service I was calling also required WS-Security with a simple username/password token. In the camel-context.xml file I created the following cxf endpoint:
    
        
            
            
                
                    
                        
                        
                        
                        
                    
                
            
        
    
This required a password callback handler, so I created something very simple in Java:
public class WSPasswordCallbackHandler implements CallbackHandler {

    private String username;
    private String password;

    public WSPasswordCallbackHandler(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public WSPasswordCallbackHandler() {
        //string constructor
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPassword() {
        return password;
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

       WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

       if (pc.getIdentifier().equals(username)) {
          pc.setPassword(password);
       }
    }

}
I then created an instance of this callback handler in camel-context.xml - note that it was included as a additional WSS4JOutInterceptor in the CXF end point.
    
        
        
    
I was then able to use the end point in my Java route (notice the PAYLOAD data format):
                    to("cxf:bean:wsPublishEndpoint?" +
                        "defaultOperationName=wsSendOperation&" +
                        "defaultOperationNamespace=http://www.tempuri.co.nz/wsService&" +
                        "wrappedStyle=true&" +
                        "loggingFeatureEnabled=true&" +
                        "dataFormat=PAYLOAD");

Now the final issue was creating an 'uber-jar' for simple deployment - CXF made this very difficult as many of the spring configurations would get overwritten. Using the Maven Shade plugin with carefully selected transformers allowed the conflicting files to be aggregated:
            
                org.apache.maven.plugins
                maven-shade-plugin
                1.4
                
                    
                        package
                        
                            shade
                        
                        
                            
                                
                                    ChangePublisherMain
                                
                                
                                    META-INF/services/org/apache/camel/TypeConverter
                                
                                
                                    META-INF/spring.handlers
                                
                                
                                    META-INF/spring.schemas
                                
                                
                                    META-INF/cxf/cxf.extension
                                
                                
                                    META-INF/extensions.xml
                                
                                
                                    META-INF/cxf/extensions.xml
                                
                                
                                    META-INF/cxf/bus-extensions.txt
                                
                                
                                    META-INF/cxf/bus-extensions.xml
                                
                                
                                    META-INF/wsdl.plugin.xml
                                
                                
                                    META-INF/tools.service.validator.xml
                                
                                
                                    META-INF/cxf/java2wsbeans.xml
                                
                                
                            
                            executable
                            true
                        
                    
                
                
                    
                        org.apache.cxf
                        cxf-buildtools
                        2.2.12
                        jar
                        compile
                    
                
            
I suspect there might be other Camel Spring resources that conflict besides the TypeConverter but I haven't found any yet. This meant I could run my java 'uber-jar' straight off the command line using java -jar ChangePublisher-1.0-executable.jar

And finally, all of my Maven dependencies:
    
        
            org.apache.camel
            camel-core
            2.8.2
        
        
            org.slf4j
            slf4j-log4j12
            1.6.1
        
        
            log4j
            log4j
            1.2.16
        
        
            org.apache.camel
            camel-cxf
            2.8.2
        
        
            org.apache.camel
            camel-spring
            2.8.2
        
        
            org.apache.cxf
            cxf-rt-ws-security
            2.4.3
        
    
Hopefully I've managed to clamber up the Apache Camel learning curve now!

Tuesday 20 September 2011

Possible Oracle Service Bus/Weblogic Server SSL bug

We have encountered 'Connection reset' exceptions in production environments when the Oracle Service Bus sends web requests to IIS/WCF-based web services over an SSL connection. By capturing packets and decrypting the SSL stream we were able to establish the following chain of events:

  • The OSB opens an SSL connection with the IIS/WCF web service
  • The OSB sends many HTTP/SOAP requests over this connection
  • Eventually the OSB sends a HTTP/SOAP request for which the IIS/WCF web service fails to reply - it appears to be a bug in IIS/WCF (or the application) that occurs after n number of requests are sent over the same SSL connection
  • IIS times out the TCP connection by sending a TCP RST packet to the OSB
  • The OSB re-establishes an SSL connection to the IIS/WCF web service, it then proceeds to send the HTTP header to the web service but fails to send the HTTP payload. Since the IIS/WCF webservice is expecting more data everything halts waiting for more data. After some time IIS closes the connection by sending a TCP RST packet
  • The OSB continues to re-establish the SSL connection and only send the HTTP header, this occurs 5 times in total
  • On the last TCP SSL connection tear down the OSB throws a 'Connection reset' exception
  • Now the OSB re-establishes a SSL connection and continues to send the other requests in the queue - this works as expected until we reach n number of requests for the same SSL connection where the same thing happens again
The following exception is encountered after 5 SSL connection tear downs.
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:168)
at weblogic.utils.io.ChunkedInputStream.read(ChunkedInputStream.java:159)
at java.io.InputStream.read(InputStream.java:85)
at com.certicom.tls.record.ReadHandler.readFragment(Unknown Source)
at com.certicom.tls.record.ReadHandler.readRecord(Unknown Source)
at com.certicom.tls.record.ReadHandler.read(Unknown Source)
at com.certicom.io.InputSSLIOStreamWrapper.read(Unknown Source)
at java.io.BufferedInputStream.fill(BufferedInputStream.java:218)
at java.io.BufferedInputStream.read(BufferedInputStream.java:237)
at weblogic.net.http.MessageHeader.isHTTP(MessageHeader.java:220)
at weblogic.net.http.MessageHeader.parseHeader(MessageHeader.java:143)
at weblogic.net.http.HttpClient.parseHTTP(HttpClient.java:431)
at weblogic.net.http.HttpURLConnection.getInputStream(HttpURLConnection.java:359)
at weblogic.net.http.SOAPHttpsURLConnection.getInputStream(SOAPHttpsURLConnection.java:37)
at weblogic.net.http.HttpURLConnection.getResponseCode(HttpURLConnection.java:947)
at com.bea.wli.sb.transports.http.HttpOutboundMessageContext.getResponse(HttpOutboundMessageContext.java:574)
at com.bea.wli.sb.transports.http.HttpOutboundMessageContext.access$000(HttpOutboundMessageContext.java:76)
at com.bea.wli.sb.transports.http.HttpOutboundMessageContext$RetrieveHttpResponseWork.handleResponse(HttpOutboundMessageContext.ja\
va:898)
at weblogic.net.http.AsyncResponseHandler$MuxableSocketHTTPAsyncResponse$RunnableCallback.run(AsyncResponseHandler.java:531)
at weblogic.work.ContextWrap.run(ContextWrap.java:41)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:516)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:201)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:173
Our workaround was to set a timeout of 30 seconds in the OSB business service, and a retry count of 1. Since the IIS time out was approximately two minutes this meant that when it failed to respond, the OSB would tear down the connection and try again once more. This meant we never arrived in the state where the OSB was sending the header alone.

Friday 16 September 2011

Groovy Logging Injection - AST Transformation

Following on from the theme of AST transformations, I created a global AST transform that would iterate through all classes and effectively inject the following field:
//SLF4J Logger/LoggerFactory
Logger logger = LoggerFactory.getLogger(this.class) 
Like with Grails, this means that a class can simply call 'logger' without worrying about creating it (or it's implementation for that matter). The following classes achieve this:
package annotations

import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.FieldNode
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.ClassExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.PropertyExpression
import org.codehaus.groovy.ast.expr.VariableExpression
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention

//Injects the following into each non-static, non-enum class
// Logger logger = LoggerFactory.getLogger(this.class)

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class LoggerInjectionASTTransformation implements ASTTransformation {
    public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

        List classes = sourceUnit.getAST()?.getClasses()
        classes?.each { ClassNode classNode ->
            if (classNode.isEnum() || classNode.isStaticClass() || classNode.isInterface() || classNode.isAnnotationDefinition()) return //skip to next class

            //ignore these transformations!
            if (classNode.declaresInterface(new ClassNode(org.codehaus.groovy.transform.ASTTransformation.class))) return

            //don't add another logger field (allows for overriding)
            if (classNode.getField("logger")) return

            //exclude classes decorated with the ExcludeLoggerInjection class
            if (classNode.getAnnotations(new ClassNode(ExcludeLoggerInjection.class))) return

            Expression objectExpression = new ClassExpression(new ClassNode(org.slf4j.LoggerFactory.class))
            PropertyExpression classReference = new PropertyExpression(new VariableExpression("this"),new ConstantExpression("class"))
            ArgumentListExpression arguments = new ArgumentListExpression(classReference)
            Expression initialValueExpression = new MethodCallExpression(objectExpression,"getLogger", arguments)

            FieldNode loggingField = new FieldNode("logger",2,new ClassNode(org.slf4j.Logger.class),classNode,initialValueExpression)

            classNode.addField(loggingField)

        }
    }
}

@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
public @interface ExcludeLoggerInjection {
}
Note that it only injects the logger field when there isn't already such a field (allowing the developer to override the logger implementation at compile-time if they desire), and it only adds a logger field to 'standard' classes, excluding the likes of interfaces and enums. Furthermore, an annotation 'ExcludeLoggerInjection' was created that, when decorated on a class, would stop the AST transform from injecting the logging class: this is especially useful when Java reflection is used to instantiate a class. One final step is required for invoking the global transform during class compilation. The classpath needs to contain a META-INF/services directory containing a file called 'org.codehaus.groovy.transform.ASTTransformation'. The contents of the file should contain all of the global annotation classed used during Groovy compilation. In our case the file simply contains:
nz.ac.auckland.mediaservices.digitizer.annotations.LoggerInjectionASTTransformation
If you're using Maven to do building this is pretty simple as resources are placed onto the classpath before compilation (we also need to build the annotation first before the remaining classes, see a previous post for how I used an additional ant groovyc task to force early compilation of these annotations).

Monday 12 September 2011

Groovy AST Transform to inject Spring Security Check for Java Caller

This post follows on with the trend of using AST transforms to inject compile time behaviour into Groovy code so that Java code can exploit these benefits. In this case we have a piece of Java code (Spring RMIExporter) that calls methods on a 'service' class. Since it is straight Java making the call to the code we cannot use the Groovy metaclass approach of changing the way methods are called. The following AST transform was created that simply iterates over every public non-static class method and calls a security method provided as part of the annotation:
package annotations

import org.codehaus.groovy.transform.GroovyASTTransformationClass
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.transform.ASTTransformation

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class SpringSecurityInvocationTransformation implements ASTTransformation {
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {

        def instanceNode = nodes[1] //specified in documentation
        def annotationNode = nodes[0]

        String securityMethod = annotationNode.getMember("securityMethod").getValue()

        def interfaceMethods = instanceNode.getMethods()
        interfaceMethods.findAll { MethodNode methodNode ->
            methodNode.isPublic() && !methodNode.isStatic() && methodNode.declaringClass == instanceNode.getPlainNodeReference()
        }.each { MethodNode methodNode ->
            List currentStatements = methodNode.getCode().getStatements()
            String methodCallingCode = "this." + securityMethod + "()"

            def methodCallNodes = new AstBuilder().buildFromString(CompilePhase.SEMANTIC_ANALYSIS,true, methodCallingCode)
            currentStatements.add(0, methodCallNodes[0].statements[0])
        }
    }
}


@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass(["annotations.SpringSecurityInvocationTransformation"])
public @interface SpringSecurityInvocation {
    String securityMethod();
}
It might have made more sense to do it over each method from the interface that the class implemented (seeing as it was an implemention of a RMI-exported interface). We can then invoke this using a class definition like:
import annotations.SpringSecurityInvocation

//this annotation injects the checkSecurity method at the start of
//every public non-static method for this class
@SpringSecurityInvocation(securityMethod="checkSecurity")
public class SampleRMIInterfaceImplementation implements SampleRMIInterfaceImplementation {
    private void checkSecurity() {
        //do security stuff
        //throw exception if invalid security
    }

    public void someRMIOperation() {
        //do stuff
    }
}
In this case someRMIOperation will first call the checkSecurity() method.

Friday 9 September 2011

Groovy injecting JAX-WS annotations to interfaces for CXF web services

So having done some Spring development using CXF web services I found that interface method parameters were named arg0, arg1 etc in the WSDL. This is ugly but it seems that there isn't an elegant way of specifying parameter names using the basic CXF front end module (JAX-WS annotations do not work). So I switched to using JaxWsServerFactoryBean (with the security interceptors I discussed in a previous post). But this meant annotating the interface methods and parameters with JAX-WS annotations which was tedious and gross. So Groovy to the rescue with an AST transform that injects the annotations so that when the Java CXF code reads the interface it sees annotations. So the local AST transform iterates through every method in the interface and decorates the method with a javax.jws.WebMethod annotation containing the name of the method. It then iterates through the method's parameters and adds a javax.jws.WebParam annotation with the name of the parameter (this fixes the arg0, arg1 issue).
package annotations

import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Retention
import java.lang.annotation.ElementType
import java.lang.annotation.Target
import org.codehaus.groovy.transform.GroovyASTTransformationClass
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.control.CompilePhase
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.expr.ConstantExpression

@GroovyASTTransformation(phase=CompilePhase.SEMANTIC_ANALYSIS)
class WSAnnotationTransformation implements ASTTransformation {
    public void visit(ASTNode[] nodes, SourceUnit sourceUnit) {
        def interfaceNode = nodes[1] //specified in documentation
        def interfaceMethods = interfaceNode.getMethods()
        interfaceMethods.each { MethodNode methodNode ->

            //add an annotation for the method web operation
            ClassNode webMethod = new ClassNode(javax.jws.WebMethod)
            AnnotationNode methodAnnotation = new AnnotationNode(webMethod)
            methodAnnotation.addMember("operationName",new ConstantExpression(methodNode.getName()))
            methodNode.addAnnotation(methodAnnotation)

            //add the annotation for the parameter names
            methodNode.parameters.each { parameter ->
                ClassNode webParam = new ClassNode(javax.jws.WebParam)
                AnnotationNode parameterAnnotation = new AnnotationNode(webParam)
                parameterAnnotation.addMember("name",new ConstantExpression(parameter.getName()))
                parameter.addAnnotation(parameterAnnotation)
            }
        }
    }
}

@Retention(RetentionPolicy.SOURCE)
@Target([ElementType.TYPE])
@GroovyASTTransformationClass(["annotations.WSAnnotationTransformation"])
public @interface ParameterAnnotationInjection {
}
We can then decorate the interface with the annotation: @ParameterAnnotationInjection e.g.
package webservices

import annotations.ParameterAnnotationInjection

@ParameterAnnotationInjection
public interface DigitizerService {
     void doSomething(String a, String b, String c);
     void somethingElse(String a, String b, String c);
}
Which returns a XSD schema like:

    
        
        
        
    


    
        
        
        
    

Also, the Eclipse-Groovy Maven plugin had trouble with the annotation and compilation order, so I added an ant task to force the compile of the annotation before calling the Eclipse-Groovy Maven plugin to do the heavy lifting:
            
                maven-antrun-plugin
                
                    
                        process-sources
                        process-sources
                        
                            
                                
                                
                                         
                                
                                
                                
                                    
                                
                            
                        
                        
                            run