Install this theme
Oracle Advanced Queuing with Spring and custom Oracle Types

There are already several blog articles on the web about this topic. In this post I want to demonstrate how you can process JMS messages that have been created based on the Oracle Custom Types.

This is also the solution to the common problem: JMS-137: Payload factory must be specified for destinations with ADT payloads.

In the run-up to the post please find below some links how to connect Spring via JMS to the Oracle Advanced Queing:

Assuming following custom oracle data type, queue table and the queue itself were defined in the database:

create or replace type aq_event_obj as object
(
  datetime date,
  id       varchar2(100),
  payload  varchar2(255)
);
sys.dbms_aqadm.create_queue_table(queue_table        => 'T_AQ_EVENT',
                                  queue_payload_type => 'AQ_EVENT_OBJ',
                                  sort_list          => 'PRIORITY, ENQUEUE_TIME',
                                  compatible         => '10.0.0',
                                  primary_instance   => 0,
                                  secondary_instance => 0);
sys.dbms_aqadm.create_queue(queue_name     => 'Q_AQ_EVENT',
                            queue_table    => 'T_AQ_EVENT',
                            queue_type     => sys.dbms_aqadm.normal_queue,
                            max_retries    => 5,
                            retry_delay    => 0,
                            retention_time => 0);

First step is a standard procedure creating JMS Connection Factory, Event Queue and JMS Template in Spring described in the posts above (see links). Just a brief overview for this here:

<bean id="jmsQueueConnectionFactory" class="oracle.jms.AQjmsFactory"
	factory-method="getQueueConnectionFactory">
	<constructor-arg index="0" ref="dataSource" />
</bean>
<bean id="aqEventQueue" class="net.javaforge.blog.oracle.aq.OracleAqQueueFactoryBean">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="oracleQueueName" value="Q_AQ_EVENT" />
</bean>
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="defaultDestination" ref="aqEventQueue" />
</bean>

And the corresponding java classes:

public abstract class AbstractOracleAqFactoryBean {

	private ConnectionFactory connectionFactory;
	private String oracleQueueName = null;
	private String oracleQueueUser = null;

	@Required
	public void setConnectionFactory(final ConnectionFactory connectionFactory) {
		this.connectionFactory = connectionFactory;
	}

	public String getOracleQueueName() {
		return oracleQueueName;
	}

	@Required
	public void setOracleQueueName(final String oracleQueueName) {
		this.oracleQueueName = oracleQueueName;
	}

	public String getOracleQueueUser() {
		return oracleQueueUser;
	}

	public void setOracleQueueUser(final String oracleQueueUser) {
		this.oracleQueueUser = oracleQueueUser;
	}

	public boolean isSingleton() {
		return false;
	}

	protected AQjmsSession getSession() throws JMSException {
		final AQjmsSession session = (AQjmsSession) connectionFactory
				.createConnection().createSession(true,
						Session.SESSION_TRANSACTED);
		return session;
	}

}

public class OracleAqQueueFactoryBean extends AbstractOracleAqFactoryBean
		implements FactoryBean {

	public Class getObjectType() {
		return javax.jms.Queue.class;
	}

	public Queue getObject() throws JMSException {
		final AQjmsSession session = getSession();
		return session.getQueue(getOracleQueueUser(), getOracleQueueName());
	}

}

Furthermore as soon as we want to process custom oracle data type as message payload, we have to implement the ORADataFactory interface and describe our aq_event_obj type in it.

This interface can be implemented by hand or you can just generate an appropriate implementation with Oracle’s JDeveloper IDE. To save time i have chosen the second option and generated following ugly but fully functional class:

image
public class AQCustomPayloadObject implements ORAData, ORADataFactory {
	
	public static final String _SQL_NAME = "AQ_EVENT_OBJ";
	public static final int _SQL_TYPECODE = OracleTypes.STRUCT;

	protected MutableStruct _struct;

	protected static int[] _sqlType = { 91, 12, 12 };
	protected static ORADataFactory[] _factory = new ORADataFactory[3];
	protected static final AQCustomPayloadObject _AqEventObjFactory = new AQCustomPayloadObject();

	public static ORADataFactory getORADataFactory() {
		return _AqEventObjFactory;
	}

	/* constructors */
	protected void _init_struct(boolean init) {
		if (init)
			_struct = new MutableStruct(new Object[3], _sqlType, _factory);
	}

	public AQCustomPayloadObject() {
		_init_struct(true);
	}

	public AQCustomPayloadObject(java.sql.Timestamp datetime, String id, String payload)
			throws SQLException {
		_init_struct(true);
		setDatetime(datetime);
		setId(id);
		setPayload(payload);
	}

	/* ORAData interface */
	public Datum toDatum(Connection c) throws SQLException {
		return _struct.toDatum(c, _SQL_NAME);
	}

	/* ORADataFactory interface */
	public ORAData create(Datum d, int sqlType) throws SQLException {
		return create(null, d, sqlType);
	}

	protected ORAData create(AQCustomPayloadObject o, Datum d, int sqlType)
			throws SQLException {
		if (d == null)
			return null;
		if (o == null)
			o = new AQCustomPayloadObject();
		o._struct = new MutableStruct((STRUCT) d, _sqlType, _factory);
		return o;
	}

	/* accessor methods */
	public java.sql.Timestamp getDatetime() throws SQLException {
		return (java.sql.Timestamp) _struct.getAttribute(0);
	}

	public void setDatetime(java.sql.Timestamp datetime) throws SQLException {
		_struct.setAttribute(0, datetime);
	}

	public String getId() throws SQLException {
		return (String) _struct.getAttribute(1);
	}

	public void setId(String id) throws SQLException {
		_struct.setAttribute(1, id);
	}

	public String getPayload() throws SQLException {
		return (String) _struct.getAttribute(2);
	}

	public void setPayload(String payload) throws SQLException {
		_struct.setAttribute(2, payload);
	}

}

Now it is time for the magic. We have to override Spring’s default DefaultMessageListenerContainer class by creating our special message consumer that can deal with the aq_event_obj type :

public class AQCustomPayloadMessageListenerContainer extends
		DefaultMessageListenerContainer {

	@Override
	protected MessageConsumer createConsumer(Session session,
			Destination destination) throws JMSException {

		return ((AQjmsSession) session).createConsumer(destination,
				getMessageSelector(),
				AQCustomPayloadObject.getORADataFactory(), null,
				isPubSubNoLocal());

	}
}

And the corresponding Spring’s XML configuration:

<bean id="jmsContainer"
	class="net.javaforge.blog.oracle.aq.AQCustomPayloadMessageListenerContainer">
	<property name="connectionFactory" ref="jmsQueueConnectionFactory" />
	<property name="destination" ref="aqEventQueue" />
	<property name="messageListener" ref="aq.receiver" />
	<property name="sessionTransacted" value="true" />
	<property name="transactionManager" ref="tx.manager" />
</bean>

The only task now is to implement the message receiver:

@Named("aq.receiver")
public class AQReceiver implements MessageListener {

	private static final Logger logger = LoggerFactory
			.getLogger(AQReceiver.class);

	@Override
	public void onMessage(Message message) {
		try {
			AQjmsAdtMessage msg = (AQjmsAdtMessage) message;
			AQCustomPayloadObject obj = (AQCustomPayloadObject) msg.getAdtPayload();

			logger.info("Id: {}", obj.getid());
			logger.info("Datetime: {}", obj.getDatetime());
			logger.info("Payload: {}", obj.getPayload());

		} catch (Exception e) {
			logger.error("Error receiving queue message!", e);
		}
	}
}
 
Blog comments powered by Disqus