Using Grails with Oracle AQ as Jms provider

In a quest to modernize a legacy system, we have decided that we want to try to move the new codebase to Grails.

In essence what we need the new system to do under the hood is to receive messages on several Oracle AQ queues, persist some legacy objects in the same schema as the AQ tables and finally persist our new domain model objects in a MongoDB.

I ended up making a sample application that combines a Grails application with a modified version of the routing-jms plugin and the MongoDB GORM plugin.

My first challenge was to extend the routing-jms plugin with an oracleQueue component. I forked the routing-jms plugin on GitHub (my forked version is available at here)

Now the reason I haven’t made a pull-request back to the upstream project is an unlucky dependency on Oracles’s aqapi.jar which is not publically available via Maven (as far as I can determine).

First, I added the oracleQueue component in RoutingJmsGrailsPlugin.groovy:

def doWithSpring = {
        def config = application.config.grails.plugin.routing.jms

        // Transaction policy needed for transacting the Camel Routes - can be used in your XxxRoute.groovy class
        propagationRequired(org.apache.camel.spring.spi.SpringTransactionPolicy) {
            transactionManager = ref('transactionManager')
        }

        /*
         * In your project that includes this plugin add the following in your Config.groovy file for each environment as appropriate:
         * grails.plugin.routing.jms.aq.database.url = "jdbc:oracle:thin:@{database-host}:{port}:{database.instance}"
         * grails.plugin.routing.jms.aq.database.username = "{username}"
         * grails.plugin.routing.jms.aq.database.password = "{password}"
         *
         * REMEMBER to include the appropriate Oracle driver jar either via a dependency or by placing them in your lib folder of the project.
         * i.e. ojdbc16-11.2.0.3.0.jar
         */
        QueueConnectionFactory connectionFactoryOracleAQQueue = AQjmsFactory.getQueueConnectionFactory(config.aq.database.url, null)

        aqQueueConnectionFactory(org.springframework.jms.connection.UserCredentialsConnectionFactoryAdapter) {
            targetConnectionFactory = connectionFactoryOracleAQQueue
            username = config.aq.database.username
            password= config.aq.database.password
        }

        oracleQueue(org.apache.camel.component.jms.JmsComponent) {
            connectionFactory = aqQueueConnectionFactory
            transacted = 'true'
        }

        // original component
        activemq(org.apache.activemq.camel.component.ActiveMQComponent) {
            brokerURL = config.brokerURL ?: 'vm://LocalBroker'
        }
}

I then needed to add the aqapi.jar dependency to the BuildConfig.groovy file:

repositories {
    // All the original repositories here ...

    // Replace with the maven repository where aqapi.jar is uploaded and uncomment!
    mavenRepo "http://artifactory:8081/artifactory/repo"
}

dependencies {
    // All the original dependencies here ...

    runtime('oracle:aqapi:1.0.0')
}    

To distribute the changes to the plugin, I ran grails package-plugin, and then I manually uploaded the resulting zip file to our Artifactory repository.

When I created the sample grails app, I added the following line in BuildConfig.groovy under repositories:


grails.project.repos.localReleases.url =  "http://artifactory:8081/artifactory/repo"

and then I ran grails install-plugin routing-jms --repository=localReleases so that it retrieved the plugin from our Artifactory repository.

In the created application there is still a couple configuration issues. One needs an Oracle driver jar – again one is not available through a public maven repository, so you can either drop a copy of the jar into the apps lib folder or manually configure a mavenRepo that contains a copy in BuildConfig.groovy and add a dependency instead.

The following dependencies are needed for creating your Camel route, using Jaxb, and the MongoDB plugin respectively:


runtime 'org.apache.camel:camel-jms:2.9.0'
runtime 'org.apache.camel:camel-jaxb:2.9.0'
runtime 'org.mongodb:mongo-java-driver:2.7.1'

In the Config.groovy file you need to remember to wire in the values for the following values for each environment:


grails.plugin.routing.jms.aq.database.url = "jdbc:oracle:thin:@{database-host}:{port}:{database.instance}"
grails.plugin.routing.jms.aq.database.username = "{username}"
grails.plugin.routing.jms.aq.database.password = "{password}"

It is also a pre-requisite that an Oracle AQ queue table and queue are defined and started in the Oracle schema. I have created a queue table and queue called CAMEL_TEST in our database. Our queue is defined with 2 redelivery attempts and running the sample application shows the transactional part working fine – attempting 2 redeliveries and then placing message on the error queue when the built in error traps are executed.

The DataSource.groovy also needs to be configured for access to the same Oracle schema

And away we go! (Sample application is available here)

  grails create-route AqMessage

Edit config method in the created Route

@Override
void configure() {
    def config = grailsApplication?.config
    DataFormat jaxb = new JaxbDataFormat("com.acme.order")
    from('oracleQueue:queue:CAMEL_TEST?jmsMessageType=Text').transacted('propagationRequired').unmarshal(jaxb).to('bean:persistAqMessageService?method=persistAqMessage')
}

I have created two domain classes. The first one Order which uses the Hibernate plugin to persist to the Oracle schema and OrderMash which uses the MongoDB plugin to persist to a MongoDB which is also configured in the file DataSource.groovy. The way to get a domain object persisted with the Mongo plugin instead of Hibernate is to add the following to the domain class:


    static mapWith = "mongo"

I auto-generated the Order Controller and Views and modified the save() action to call a method on my PersistAqMessageService which basically places an xml string on the oracle Aq Queue via the injected method sendMessage:

The Service is straight forward – I have a constraint on the Order object amount(range:1..10), so it will throw an exception if out of range. Likewise, if the name is set to “FAILME” I create an OrderMash object which violates a not nullable constraint on that object.

class PersistAqMessageService {
    static transactional = true

    def persistAqMessage(PurchaseOrder purchaseOrder) {
        def myOrder = new Order(name: purchaseOrder.name, price: purchaseOrder.price, amount: purchaseOrder.amount)
        if (!myOrder.save(flush: true)) {
            throw new RuntimeException("Yada-yada")
        } else {
            log.info "Order object saved!"
        }

        def orderMash
        if (myOrder.name.equals("FAILME")) {
            orderMash = new OrderMash(name: myOrder.name, price: myOrder.price, amount: myOrder.amount)
        } else {
            orderMash = new OrderMash(pieceId: myOrder.id, name: myOrder.name, price: myOrder.price, amount: myOrder.amount)
        }
        if (!orderMash.save(flush: true)) {
            throw new RuntimeException("Roll-me back back")
        } else {
            log.info "OrderMash object persisted to Mongo"
        }
    }

    def putMessageOnQueue(Order newOrder) {
        sendMessage('oracleQueue:queue:CAMEL_TEST?jmsMessageType=Text', "<purchaseOrder name=\"${newOrder.name}\" price=\"${newOrder.price}\" amount=\"${newOrder.amount}\" />")
    }
}

Et voila – run the application and create Orders via the Order form and check that an Order object is persisted in the Oracle schema and an OrderMash object is persisted in the orderMash Collection in database foo in your MongoDB.