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:
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); } } }