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
                        
                    
                
            

Wednesday 7 September 2011

Weblogic JMS message receiver

Following on from an earlier post where I wrote a quick piece of java to put files onto an arbitrary JMS topic/queue, I also wanted something to read text documents off a topic/queue. I wrote the following code that takes a topic/queue and continues to read messages off of it, it also supports durable topic subscription IDs. Remember to include wlclient.jar, wljmsclient.jar in the classpath when building/running this (todo: mavenize all of these Weblogic libraries).
import javax.jms.*;
import javax.naming.*;
import java.util.Properties;
import java.io.*;

public class JMSConsumeMessage {

    public static void main(String[] args) throws Exception {
		if (args.length < 5 || args.length > 6) {
			System.out.println ("Usage: TOPIC|QUEUE ProviderURL UserName Password DestName [subscriptionName]");
			return;
		}

		String destType = args[0];
		String provider = args[1];
		String username = args[2];
		String password = args[3];
		String destName = args[4];
		String subscriptionName = null;
		if (args.length == 6) subscriptionName = args[5];

		if (!(destType.equals("QUEUE") || destType.equals("TOPIC"))) throw new Exception ("Must specify destination as TOPIC or QUEUE");

		Properties props = System.getProperties();
		props.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
		props.put(Context.PROVIDER_URL, provider);
		props.put(Context.SECURITY_PRINCIPAL, username);
		props.put(Context.SECURITY_CREDENTIALS, password);

		InitialContext ctx = new InitialContext(props);

		Session session = null;
		MessageConsumer consumer = null;
		Connection connection = null;

		try {
			if (destType.equals("TOPIC")) {
				TopicConnectionFactory tconfactory = (TopicConnectionFactory)ctx.lookup("javax.jms.TopicConnectionFactory");
				TopicConnection tcon = tconfactory.createTopicConnection();
				if (subscriptionName != null) tcon.setClientID(subscriptionName);
				TopicSession tsession = tcon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
				Topic topic = (Topic)ctx.lookup(destName);
				TopicSubscriber tsubscriber = (subscriptionName == null ? tsession.createSubscriber(topic) : tsession.createDurableSubscriber(topic,subscriptionName));
				session = (Session)tsession;
				consumer = (MessageConsumer)tsubscriber;
				connection = (Connection)tcon;
			} else if (destType.equals("QUEUE")) {
				QueueConnectionFactory qconfactory = (QueueConnectionFactory)ctx.lookup("javax.jms.QueueConnectionFactory");
				QueueConnection qcon = qconfactory.createQueueConnection();
				QueueSession qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
				Queue queue = (Queue)ctx.lookup(destName);
				QueueReceiver qreceiver = qsession.createReceiver(queue);
				session = (Session)qsession;
				consumer = (MessageConsumer)qreceiver;
				connection = (Connection)qcon;
			}

			connection.start();

			int currentIndex = 0;

			while (true) {
				Message m = consumer.receive();
				if (m instanceof TextMessage) {
					TextMessage txt = (TextMessage)m;
					BufferedWriter writer = new BufferedWriter(new FileWriter(destName + "-" + currentIndex + ".txt"));
					writer.write(txt.getText());
					writer.close();
					System.out.println("Read message to: " + destName + "-" + currentIndex + ".txt");
					currentIndex++;
				}
			}


		} finally {
		    if (consumer != null) consumer.close();
		    if (session != null) session.close();
		    if (connection != null) connection.close();
		}

    }

}

Saturday 3 September 2011

CXF, WS-Security and Spring Security

I wanted to find a way to integrate CXF and Spring Security so that CXF would take the WS-Security details and populate the SecurityContextHolder; leaving the service implementation to carry out authentication/authorisation processes using Spring Security features. I found a post on the Spring forums: http://forum.springsource.org/showthread.php?64492-WS-Security-integration that overrode the WSS4JInInterceptor and populated the SecurityContext here, but I wasn't totally happy with this solution: in theory it was possible that a different thread could handle a different interceptor in the chain further down the line possibly causing elevated privileges for a thread/caller that doesn't have the permissions. Furthermore the thread didn't remove the SpringContext details after execution which might cause security problems on outgoing interceptors if care wasn't taken. Inspired by the RMI ContextPropagatingRemoteInvocation offered by Spring I changed the CXF method invoker to set the SecurityContext for the calling thread, execute the method and then remove the SecurityContext. I made modifications to the request chain to make this happen; while all my code is in Groovy I've tried to make it as close to Java as possible, I suspect translation should be a trivial exercise.

I override the WSS4JInInterceptor to get the WS-Security details from the message, these are put into a Spring UsernamePasswordAuthenticationToken then placed into the messages's exchange under the key "WS_TOKEN". The CXF Architecture defines an exchange as holding "references to the in, out and fault messages for the current message exchange"; I wanted to include the value in the actual Message object but only the exchange was available to the method invocation (described later) - exchange would have to do. The following is the code for SpringAuthnInterceptor.groovy:
package ws

import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor
import org.apache.cxf.binding.soap.SoapMessage
import org.apache.cxf.interceptor.Fault
import org.apache.ws.security.handler.WSHandlerConstants
import org.apache.ws.security.handler.WSHandlerResult
import org.apache.ws.security.WSSecurityEngineResult
import org.apache.ws.security.WSConstants
import org.apache.ws.security.WSUsernameTokenPrincipal
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.apache.cxf.ws.security.SecurityConstants

public class SpringAuthnInterceptor extends WSS4JInInterceptor {

    public SpringAuthnInterceptor() {
        super()
    }

    public SpringAuthnInterceptor(Map properties) {
        super (properties)
    }

    //with help from: http://forum.springsource.org/showthread.php?64492-WS-Security-integration
    public void handleMessage(SoapMessage message) throws Fault {
        message.put(SecurityConstants.VALIDATE_TOKEN, Boolean.FALSE);
        super.handleMessage(message)
        message.getContextualProperty(WSHandlerConstants.RECV_RESULTS).each { WSHandlerResult result ->
            result.getResults().each { WSSecurityEngineResult securityResult ->
                if ((securityResult.get(WSSecurityEngineResult.TAG_ACTION) & WSConstants.UT) > 0) {
                    WSUsernameTokenPrincipal principal = (WSUsernameTokenPrincipal)securityResult.get(WSSecurityEngineResult.TAG_PRINCIPAL)
                    if (!principal.getPassword()) principal.setPassword("")
                    UsernamePasswordAuthenticationToken usernameToken = new UsernamePasswordAuthenticationToken(principal.getName(), principal.getPassword())
                    message.getExchange().put("WS_TOKEN", usernameToken)
                }
            }
        }
    }
}

Once the user details are populated we need to override BeanInvoker to change the way that the WS method is invoked to add, then remove the SecurityContext. The following is SpringSecurityWSInvoker.groovy which carries this out:
package ws

import org.apache.cxf.service.invoker.BeanInvoker
import org.apache.cxf.message.Exchange;
import java.lang.reflect.Method
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder

class SpringSecurityWSInvoker extends BeanInvoker {
    public SpringSecurityWSInvoker(Object proxy) {
        super(proxy)
    }

    protected Object performInvocation(Exchange exchange, final Object serviceObject, Method m,
        Object[] paramArray) throws Exception {
        Object invocationResult = null
        try {
            UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)exchange.get("WS_TOKEN")
            if (token) {
                SecurityContextHolder.getContext().setAuthentication(token)
            }
            invocationResult = super.performInvocation(exchange,serviceObject,m,paramArray)
        } finally {
            SecurityContextHolder.clearContext()
        }

        return invocationResult
    }
}

Now we can use Spring to set up the relevant web service with the additional interceptor and overridden method invoker. I've used the following with the Spring DSL (I'm sure it translates to Spring XML easily also):
wsServerFactory(org.apache.cxf.frontend.ServerFactoryBean) {
        serviceClass = wsInterface
        address = "${ws.protocol}://${ws.host}:${ws.port}/${ws.serviceName}"
        invoker = new SpringSecurityWSInvoker(wsInterfaceInstance)
        inInterceptors = [new SpringAuthnInterceptor(["action":"UsernameToken","passwordType":"PasswordText"])]
        serviceName = new QName(ws.namespace,ws.serviceName,"")
    }
Where wsInterface is the webservice interface code that forms the WSDL and wsInterfaceInstance is the instantce of the implementation of this interface. To start the server the following code could be used:
def serverFactory = appContext.getBean("wsServerFactory")
serverFactory.create()

Tuesday 26 July 2011

XQuery - finding changes in the trees

Given a set of node names I wanted to compare the data/text of these nodes from two structurally identical documents. This follows on from a previous post where we needed to translate the character sets for a given node's data/text; by comparing the before and after trees we can log what was actually changed before it was sent onto the next system.

The following XQuery takes a sequence of node names (as before), as well as the original and new documents; by comparing the relevant element's data it returns a string containing old and new values where they differ. This string is mainly useful for logging purposes.

declare namespace tmp = "http://www.tempuri.co.nz/xquery/";

declare function tmp:stringChangeList($nodeNames as xs:string*,$oldOutput as element(),$newOutput as element()) as xs:string* {
 for $curNodeName in $nodeNames
 let $oldValues := $oldOutput//*[name() = $curNodeName]
 for $oldValuePosition in (1 to count($oldValues))
 let $oldValueNode := $oldValues[$oldValuePosition]
 let $newValueNodeList := $newOutput//*[name() = $curNodeName]
 let $newValueNode := $newValueNodeList[$oldValuePosition]
 where data($oldValueNode) != data($newValueNode)
 return concat("(",$curNodeName," - Old: ",data($oldValueNode),", New: ",data($newValueNode),")")
};

Wednesday 20 July 2011

XQuery translations to Oracle Service Bus

So things are never as straight forward as they should be when Oracle Service Bus gets involved. It isn't possible to include modules in OSB XQuery... It is also difficult to include arbitrary documents without referring to an absolute path (deployment nightmare!); this was needed to refer to the 'translation table' XML file.

So I created a XQuery script compatible with Oracle Service Bus that does the following:
- Takes a node
- Takes a list of node names (including namespaces) that we want to convert
- A translation table as before
=> We return the provided node with the nodes in the node name list translated into the new values.

declare namespace tmp = "http://www.tempuri.co.nz/xquery/";

declare function tmp:convertString($inputString as xs:string, $translationSet as element()*) as xs:string {
        if (count($translationSet) = 0) then (: base case :)
           $inputString
        else (: recursion :)
           let $fromValue := data($translationSet[1]/translate_from)
           let $toValue := data($translationSet[1]/translate_to)
           let $updatedStr := fn:replace($inputString,$fromValue,$toValue)
           return tmp:convertString($updatedStr,remove($translationSet,1))
};

declare function tmp:replaceAll($element as element(), $updateNodes as xs:string*, $translationSet as element()*) as element() {
        element { fn:node-name($element) }
                     { $element/@*, for $child in $element/node()
                                    return if ($child instance of element())
                                           then tmp:replaceAll($child, $updateNodes,$translationSet)
                                           else if ($child instance of text() and count(index-of($updateNodes,fn:name($element))))
                                                then tmp:convertString($child,$translationSet)
                                                else $child
                     }
};

declare variable $element as element() external;
declare variable $updateNodes as xs:string* external;
declare variable $translationSet as element() external;

tmp:replaceAll($element, $updateNodes, $translationSet//translation)

Then in Oracle Service Bus I created a request/response pipeline at the very start with a replace node in each stage. The XPath is simply '.' in the variable body, with the XQuery created above. The external variables above are as follows:
translationSet:

        
          A
          BB
        
        
          C
          DD
        



updateNodes:
"firstname","lastname"

element:
$body

This will take the $body node, and find all nodes called "firstname" and "lastname", replacing all "A"'s and "C"'s with "BB" and "DD" respectively. Obviously strange characters will be more useful ;).

Tuesday 19 July 2011

XQuery to translate characters

We needed a way to translate characters from one character set to another depending on an external web service. I made the following xquery module uoa.xq:
module namespace uoa = "http://www.tempurl.co.nz/xquery";

declare function uoa:convertString($inputString as xs:string, $translationSet as element()*) as xs:string {
        if (count($translationSet) = 0) then (: base case :)
           $inputString
 else (: recursion :)
           let $fromValue := data($translationSet[1]/translate_from)
           let $toValue := data($translationSet[1]/translate_to)
           let $updatedStr := fn:replace($inputString,$fromValue,$toValue)
           return uoa:convertString($updatedStr,remove($translationSet,1))
};

Declaring a translation set can be done with an XML file like translation.xml:

 
          A
          BB
        
 
          C
          DD
        


Then invoking this can be achieved with the following xquery:
import module namespace uoa="http://www.tempurl.co.nz/xquery" at "uoa.xq";

let $inputString := "AABBCCDD"
let $translationSet := doc("translation.xml")/translation_set/translation
let $outputString := uoa:convertString($inputString,$translationSet)

return

        {$inputString}
        {$outputString}


This takes the input string and recursively iterates through the translation set (note that xquery is very functional and immutable variables mean that loops won't work!).

Monday 18 July 2011

Weblogic Server JMS message tester

I needed a way to send test messages through the CLI to Weblogic Server JMS modules (on server to test things were going as expected). I hacked together the following java code:
import javax.jms.*;
import javax.naming.*;
import java.util.Properties;
import java.io.*;

public class JMSSendMessage {

    public static void main(String[] args) throws Exception {
 if (args.length != 6) {
     System.out.println ("Usage: TOPIC|QUEUE ProviderURL UserName Password DestName FileName");
     return;
 }

 String destType = args[0];
 String provider = args[1];
 String username = args[2];
 String password = args[3];
 String destName = args[4];
 String filePath = args[5]; 

 if (!(destType.equals("QUEUE") || destType.equals("TOPIC"))) 
            throw new Exception ("Must specify destination as TOPIC or QUEUE");

 Properties props = System.getProperties();
 props.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
 props.put(Context.PROVIDER_URL, provider);
 props.put(Context.SECURITY_PRINCIPAL, username);
 props.put(Context.SECURITY_CREDENTIALS, password);

 InitialContext ctx = new InitialContext(props);

 Session session = null;
 MessageProducer producer = null;
 Connection connection = null;

 if (destType.equals("TOPIC")) {
     TopicConnectionFactory tconfactory = 
            (TopicConnectionFactory)ctx.lookup("javax.jms.TopicConnectionFactory");
     TopicConnection tcon = tconfactory.createTopicConnection();
     TopicSession tsession = tcon.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
     Topic topic = (Topic)ctx.lookup(destName);
     TopicPublisher tpublisher = tsession.createPublisher(topic);
     session = (Session)tsession;
     producer = (MessageProducer)tpublisher;
     connection = (Connection)tcon;
 } else if (destType.equals("QUEUE")) {
     QueueConnectionFactory qconfactory = 
            (QueueConnectionFactory)ctx.lookup("javax.jms.QueueConnectionFactory");
     QueueConnection qcon = qconfactory.createQueueConnection();
     QueueSession qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
     Queue queue = (Queue)ctx.lookup(destName);
     QueueSender qsender = qsession.createSender(queue);
     session = (Session)qsession;
     producer = (MessageProducer)qsender;
     connection = (Connection)qcon;
 }

 TextMessage msg = session.createTextMessage();

 connection.start();
 byte[] buffer = new byte[(int)new File(filePath).length()];
 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
 bis.read(buffer);
 bis.close();

 String textOutput = new String(buffer);

 System.out.println("Sending the following to: " + destName + 
             " (" + destType + ") on provider: " + provider);
 System.out.println(textOutput);

 msg.setText(textOutput);
 if (producer instanceof TopicPublisher) ((TopicPublisher)producer).publish(msg);
 else if (producer instanceof QueueSender) ((QueueSender)producer).send(msg);
 
 producer.close();
 session.close();
 connection.close();

 System.out.println("File sent successfully");

    }

}

You need two WLS files to compile it: wlclient.jar, wljmsclient.jar which you can 'borrow' from the WLS install directory.

Sunday 17 July 2011

LDAP membership search

A quick query to check the members of a particular LDAP group (or groups with * operator):
ldapsearch -x -H "ldaps://ldap-server:636" -D "cn=user,ou=ou,ou=ou,o=a"\
-w password -b "ou=group_list,dc=dc,dc=dc,dc=dc,dc=dc" "(cn=group_we_want)" member

Script to transfer SSH Keys

A simple script to send RSA public key to a remote SSH server so we can avoid typing a password.
#!/bin/bash                                 
cat ~/.ssh/id_rsa.pub | ssh $1 "mkdir .ssh; cat - >> ~/.ssh/authorized_keys; \
chmod a-w ~/.ssh; chmod og-rw ~/.ssh/authorized_keys"

Thursday 14 July 2011

Jenkins Apache Setup

After installing Jenkins with the MacOSX installer I wanted to put apache in the front end (in addition to RhodeCode). I had to modify the start up daemon script to listen to a different port as I use 8080 for grails dev. The following is my /Library/LaunchDaemons/org.jenkins-ci.plist:




 EnvironmentVariables
 
  JENKINS_HOME
  /Users/Shared/Jenkins/Home
 
 GroupName
 daemon
 KeepAlive
 
 Label
 org.jenkins-ci
 ProgramArguments
 
  /usr/bin/java
  -Xmx512m
  -Duser.home=/Users/Shared/Jenkins/Home
  -Djava.awt.headless=true
  -jar
  /Applications/Jenkins/jenkins.war
  --httpPort=8989
  --httpListenAddress=127.0.0.1
  --prefix=/jenkins
  --ajp13Port=-1
 
 RunAtLoad
 
 UserName
 daemon


Note that AJP couldn't be used due to an issue. Also note setting -Duser.home, this seems to be a problem where changing users causes Jenkins to put the .m2 Maven directory in the root's home directory which fails due to lack of permissions.

I then added the following to my SSL virtual host:

  ProxyPass http://127.0.0.1:8989/jenkins
  ProxyPassReverse http://127.0.0.1:8989/jenkins

Don't forget to update http://localhost:8989/jenkins/configure/ "Jenkins URL".

Restart Jenkins and Apache:
launchctl unload -w /Library/LaunchDaemons/org.jenkins-ci.plist
launchctl load -w /Library/LaunchDaemons/org.jenkins-ci.plist
sudo apachectl restart

RhodeCode Apache Proxy

I wanted to set up a proxy for SSL in front of my RhodeCode (and jenkins) setup using Apache in MacOSX. The following was added in production.ini under [app:main]:
filter-with = proxy-prefix

The following was added at the end:
[filter:proxy-prefix]
use = egg:PasteDeploy#prefix                                                                                                               
prefix = /rhodecode

Under /private/etc/apache2/httpd.conf:

             DocumentRoot "/Library/WebServer/Documents"
             ServerName host
             SSLEngine on
             SSLCertificateFile host.crt
             SSLCertificateKeyFile host.key
             SSLCertificateChainFile host.ca-bundle

             
                ProxyPass http://127.0.0.1:5000
                ProxyPassReverse http://127.0.0.1:5000
                RequestHeader set X-Url-Scheme https
             



Restart Apache with:
sudo apachectl restart

RhodeCode MacOSX Daemon/launchd

I wanted to make RhodeCode run as a launchd daemon process on MacOSX. This isn't as straight forward as it should be.
I made the following file in /Library/LaunchDaemons/rhodecode.plist




  EnvironmentVariables
  
    PYTHON_EGG_CACHE
    /opt/mercurial/.python-eggs
    CELERYD_PID_DIR
    /var/run
  

 GroupName
 daemon
 KeepAlive
 
 Label
 org.rhodelist
 ProgramArguments
 
  /usr/local/bin/paster
  serve
  /opt/mercurial/production.ini
 
 RunAtLoad
 
 UserName
 daemon
 


Restarting with the following commands:
launchctl unload -w /Library/LaunchDaemons/rhodecode.plist
launchctl load -w /Library/LaunchDaemons/rhodecode.plist

I also made a terrible hack to part of RhodeCode to force it to work under launchd (I may write a proper patch at some point in the future).
Line 40 of /Library/Python/2.6/site-packages/RhodeCode-1.1.8-py.2.6.egg/rhodecode/lib/pidlock.py
self.pidfile = "/var/run/" + self.pidfile

Yeah it's gross, but I can improve it later.