/* * Copyright 2004,2005 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.axis2.transport.jms; import org.apache.axiom.mime.ContentType; import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.builder.Builder; import org.apache.axis2.builder.SOAPBuilder; import org.apache.axis2.context.MessageContext; import org.apache.axis2.util.MessageProcessorSelector; import org.apache.axis2.description.AxisService; import org.apache.axis2.format.DataSourceMessageBuilder; import org.apache.axis2.format.TextMessageBuilder; import org.apache.axis2.format.TextMessageBuilderAdapter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.axis2.transport.TransportUtils; import org.apache.axis2.transport.base.BaseUtils; import org.apache.axis2.transport.jms.iowrappers.BytesMessageDataSource; import org.apache.axis2.transport.jms.iowrappers.BytesMessageInputStream; import javax.jms.*; import javax.jms.Queue; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.NameNotFoundException; import java.lang.reflect.Method; import java.text.ParseException; import java.util.*; /** * Miscallaneous methods used for the JMS transport */ public class JMSUtils extends BaseUtils { private static final Log log = LogFactory.getLog(JMSUtils.class); private static final Class<?>[] NOARGS = new Class<?>[] {}; private static final Object[] NOPARMS = new Object[] {}; /** * Should this service be enabled over the JMS transport? * * @param service the Axis service * @return true if JMS should be enabled */ public static boolean isJMSService(AxisService service) { if (service.isEnableAllTransports()) { return true; } else { for (String transport : service.getExposedTransports()) { if (JMSListener.TRANSPORT_NAME.equals(transport)) { return true; } } } return false; } /** * Get a String property from the JMS message * * @param message JMS message * @param property property name * @return property value */ public static String getProperty(Message message, String property) { try { return message.getStringProperty(property); } catch (JMSException e) { return null; } } /** * Return the destination name from the given URL * * @param url the URL * @return the destination name */ public static String getDestination(String url) { String tempUrl = url.substring(JMSConstants.JMS_PREFIX.length()); int propPos = tempUrl.indexOf("?"); if (propPos == -1) { return tempUrl; } else { return tempUrl.substring(0, propPos); } } /** * Set the SOAPEnvelope to the Axis2 MessageContext, from the JMS Message passed in * @param message the JMS message read * @param msgContext the Axis2 MessageContext to be populated * @param contentType content type for the message * @throws AxisFault * @throws JMSException */ public static void setSOAPEnvelope(Message message, MessageContext msgContext, String contentType) throws AxisFault, JMSException { if (contentType == null) { if (message instanceof TextMessage) { contentType = "text/plain"; } else { contentType = "application/octet-stream"; } if (log.isDebugEnabled()) { log.debug("No content type specified; assuming " + contentType); } } int index = contentType.indexOf(';'); String type = index > 0 ? contentType.substring(0, index) : contentType; Builder builder = MessageProcessorSelector.getMessageBuilder(type, msgContext); if (builder == null) { if (log.isDebugEnabled()) { log.debug("No message builder found for type '" + type + "'. Falling back to SOAP."); } builder = new SOAPBuilder(); } OMElement documentElement; if (message instanceof BytesMessage) { // Extract the charset encoding from the content type and // set the CHARACTER_SET_ENCODING property as e.g. SOAPBuilder relies on this. String charSetEnc = null; try { if (contentType != null) { charSetEnc = new ContentType(contentType).getParameter("charset"); } } catch (ParseException ex) { // ignore } msgContext.setProperty(Constants.Configuration.CHARACTER_SET_ENCODING, charSetEnc); if (builder instanceof DataSourceMessageBuilder) { documentElement = ((DataSourceMessageBuilder)builder).processDocument( new BytesMessageDataSource((BytesMessage)message), contentType, msgContext); } else { documentElement = builder.processDocument( new BytesMessageInputStream((BytesMessage)message), contentType, msgContext); } } else if (message instanceof TextMessage) { TextMessageBuilder textMessageBuilder; if (builder instanceof TextMessageBuilder) { textMessageBuilder = (TextMessageBuilder)builder; } else { textMessageBuilder = new TextMessageBuilderAdapter(builder); } String content = ((TextMessage)message).getText(); documentElement = textMessageBuilder.processDocument(content, contentType, msgContext); } else { handleException("Unsupported JMS message type " + message.getClass().getName()); return; // Make compiler happy } msgContext.setEnvelope(TransportUtils.createSOAPEnvelope(documentElement)); } /** * Set the JMS ReplyTo for the message * * @param replyDestination the JMS Destination where the reply is expected * @param session the session to use to create a temp Queue if a response is expected * but a Destination has not been specified * @param message the JMS message where the final Destinatio would be set as the JMS ReplyTo * @return the JMS ReplyTo Destination for the message */ public static Destination setReplyDestination(Destination replyDestination, Session session, Message message) { if (replyDestination == null) { try { // create temporary queue to receive the reply replyDestination = createTemporaryDestination(session); } catch (JMSException e) { handleException("Error creating temporary queue for response", e); } } try { message.setJMSReplyTo(replyDestination); } catch (JMSException e) { log.warn("Error setting JMS ReplyTo destination to : " + replyDestination, e); } if (log.isDebugEnabled()) { try { assert replyDestination != null; log.debug("Expecting a response to JMS Destination : " + (replyDestination instanceof Queue ? ((Queue) replyDestination).getQueueName() : ((Topic) replyDestination).getTopicName())); } catch (JMSException ignore) {} } return replyDestination; } /** * Set transport headers from the axis message context, into the JMS message * * @param msgContext the axis message context * @param message the JMS Message * @throws JMSException on exception */ public static void setTransportHeaders(MessageContext msgContext, Message message) throws JMSException { Map<?,?> headerMap = (Map<?,?>) msgContext.getProperty(MessageContext.TRANSPORT_HEADERS); if (headerMap == null) { return; } for (Object headerName : headerMap.keySet()) { String name = (String) headerName; if (name.startsWith(JMSConstants.JMSX_PREFIX) && !(name.equals(JMSConstants.JMSX_GROUP_ID) || name.equals(JMSConstants.JMSX_GROUP_SEQ))) { continue; } if (JMSConstants.JMS_COORELATION_ID.equals(name)) { message.setJMSCorrelationID( (String) headerMap.get(JMSConstants.JMS_COORELATION_ID)); } else if (JMSConstants.JMS_DELIVERY_MODE.equals(name)) { Object o = headerMap.get(JMSConstants.JMS_DELIVERY_MODE); if (o instanceof Integer) { message.setJMSDeliveryMode((Integer) o); } else if (o instanceof String) { try { message.setJMSDeliveryMode(Integer.parseInt((String) o)); } catch (NumberFormatException nfe) { log.warn("Invalid delivery mode ignored : " + o, nfe); } } else { log.warn("Invalid delivery mode ignored : " + o); } } else if (JMSConstants.JMS_EXPIRATION.equals(name)) { message.setJMSExpiration( Long.parseLong((String) headerMap.get(JMSConstants.JMS_EXPIRATION))); } else if (JMSConstants.JMS_MESSAGE_ID.equals(name)) { message.setJMSMessageID((String) headerMap.get(JMSConstants.JMS_MESSAGE_ID)); } else if (JMSConstants.JMS_PRIORITY.equals(name)) { message.setJMSPriority( Integer.parseInt((String) headerMap.get(JMSConstants.JMS_PRIORITY))); } else if (JMSConstants.JMS_TIMESTAMP.equals(name)) { message.setJMSTimestamp( Long.parseLong((String) headerMap.get(JMSConstants.JMS_TIMESTAMP))); } else if (JMSConstants.JMS_MESSAGE_TYPE.equals(name)) { message.setJMSType((String) headerMap.get(JMSConstants.JMS_MESSAGE_TYPE)); } else { Object value = headerMap.get(name); if (value instanceof String) { message.setStringProperty(name, (String) value); } else if (value instanceof Boolean) { message.setBooleanProperty(name, (Boolean) value); } else if (value instanceof Integer) { message.setIntProperty(name, (Integer) value); } else if (value instanceof Long) { message.setLongProperty(name, (Long) value); } else if (value instanceof Double) { message.setDoubleProperty(name, (Double) value); } else if (value instanceof Float) { message.setFloatProperty(name, (Float) value); } } } } /** * Read the transport headers from the JMS Message and set them to the axis2 message context * * @param message the JMS Message received * @param responseMsgCtx the axis message context * @throws AxisFault on error */ public static void loadTransportHeaders(Message message, MessageContext responseMsgCtx) throws AxisFault { responseMsgCtx.setProperty(MessageContext.TRANSPORT_HEADERS, getTransportHeaders(message)); } /** * Extract transport level headers for JMS from the given message into a Map * * @param message the JMS message * @return a Map of the transport headers */ public static Map<String, Object> getTransportHeaders(Message message) { // create a Map to hold transport headers Map<String, Object> map = new HashMap<String, Object>(); // correlation ID try { if (message.getJMSCorrelationID() != null) { map.put(JMSConstants.JMS_COORELATION_ID, message.getJMSCorrelationID()); } } catch (JMSException ignore) {} // set the delivery mode as persistent or not try { map.put(JMSConstants.JMS_DELIVERY_MODE, Integer.toString(message.getJMSDeliveryMode())); } catch (JMSException ignore) {} // destination name try { if (message.getJMSDestination() != null) { Destination dest = message.getJMSDestination(); map.put(JMSConstants.JMS_DESTINATION, dest instanceof Queue ? ((Queue) dest).getQueueName() : ((Topic) dest).getTopicName()); } } catch (JMSException ignore) {} // expiration try { map.put(JMSConstants.JMS_EXPIRATION, Long.toString(message.getJMSExpiration())); } catch (JMSException ignore) {} // if a JMS message ID is found try { if (message.getJMSMessageID() != null) { map.put(JMSConstants.JMS_MESSAGE_ID, message.getJMSMessageID()); } } catch (JMSException ignore) {} // priority try { map.put(JMSConstants.JMS_PRIORITY, Long.toString(message.getJMSPriority())); } catch (JMSException ignore) {} // redelivered try { map.put(JMSConstants.JMS_REDELIVERED, Boolean.toString(message.getJMSRedelivered())); } catch (JMSException ignore) {} // replyto destination name try { if (message.getJMSReplyTo() != null) { Destination dest = message.getJMSReplyTo(); map.put(JMSConstants.JMS_REPLY_TO, dest instanceof Queue ? ((Queue) dest).getQueueName() : ((Topic) dest).getTopicName()); } } catch (JMSException ignore) {} // priority try { map.put(JMSConstants.JMS_TIMESTAMP, Long.toString(message.getJMSTimestamp())); } catch (JMSException ignore) {} // message type try { if (message.getJMSType() != null) { map.put(JMSConstants.JMS_TYPE, message.getJMSType()); } } catch (JMSException ignore) {} // any other transport properties / headers Enumeration<?> e = null; try { e = message.getPropertyNames(); } catch (JMSException ignore) {} if (e != null) { while (e.hasMoreElements()) { String headerName = (String) e.nextElement(); try { map.put(headerName, message.getStringProperty(headerName)); continue; } catch (JMSException ignore) {} try { map.put(headerName, message.getBooleanProperty(headerName)); continue; } catch (JMSException ignore) {} try { map.put(headerName, message.getIntProperty(headerName)); continue; } catch (JMSException ignore) {} try { map.put(headerName, message.getLongProperty(headerName)); continue; } catch (JMSException ignore) {} try { map.put(headerName, message.getDoubleProperty(headerName)); continue; } catch (JMSException ignore) {} try { map.put(headerName, message.getFloatProperty(headerName)); } catch (JMSException ignore) {} } } return map; } /** * Create a MessageConsumer for the given Destination * @param session JMS Session to use * @param dest Destination for which the Consumer is to be created * @param messageSelector the message selector to be used if any * @return a MessageConsumer for the specified Destination * @throws JMSException */ public static MessageConsumer createConsumer(Session session, Destination dest, String messageSelector) throws JMSException { if (dest instanceof Queue) { if (session instanceof QueueSession) { return ((QueueSession) session).createReceiver((Queue) dest, messageSelector); } else { return session.createConsumer(dest, messageSelector); } } else { if (session instanceof TopicSession) { return ((TopicSession) session).createSubscriber((Topic) dest, messageSelector, false); } else { return session.createConsumer(dest, messageSelector); } } } /** * Create a temp queue or topic for synchronous receipt of responses, when a reply destination * is not specified * @param session the JMS Session to use * @return a temporary Queue or Topic, depending on the session * @throws JMSException */ public static Destination createTemporaryDestination(Session session) throws JMSException { if (session instanceof QueueSession) { return session.createTemporaryQueue(); } else { return session.createTemporaryTopic(); } } /** * Return the body length in bytes for a bytes message * @param bMsg the JMS BytesMessage * @return length of body in bytes */ public static long getBodyLength(BytesMessage bMsg) { try { Method mtd = bMsg.getClass().getMethod("getBodyLength", NOARGS); if (mtd != null) { return (Long) mtd.invoke(bMsg, NOPARMS); } } catch (Exception e) { // JMS 1.0 if (log.isDebugEnabled()) { log.debug("Error trying to determine JMS BytesMessage body length", e); } } // if JMS 1.0 long length = 0; try { byte[] buffer = new byte[2048]; bMsg.reset(); for (int bytesRead = bMsg.readBytes(buffer); bytesRead != -1; bytesRead = bMsg.readBytes(buffer)) { length += bytesRead; } } catch (JMSException ignore) {} return length; } /** * Get the length of the message in bytes * @param message * @return message size (or approximation) in bytes * @throws JMSException */ public static long getMessageSize(Message message) throws JMSException { if (message instanceof BytesMessage) { return JMSUtils.getBodyLength((BytesMessage) message); } else if (message instanceof TextMessage) { // TODO: Converting the whole message to a byte array is too much overhead just to determine the message size. // Anyway, the result is not accurate since we don't know what encoding the JMS provider uses. return ((TextMessage) message).getText().getBytes().length; } else { log.warn("Can't determine size of JMS message; unsupported message type : " + message.getClass().getName()); return 0; } } public static <T> T lookup(Context context, Class<T> clazz, String name) throws NamingException { Object object = context.lookup(name); try { return clazz.cast(object); } catch (ClassCastException ex) { // Instead of a ClassCastException, throw an exception with some // more information. if (object instanceof Reference) { Reference ref = (Reference)object; handleException("JNDI failed to de-reference Reference with name " + name + "; is the factory " + ref.getFactoryClassName() + " in your classpath?"); return null; } else { handleException("JNDI lookup of name " + name + " returned a " + object.getClass().getName() + " while a " + clazz + " was expected"); return null; } } } /** * This is a JMS spec independent method to create a Connection. Please be cautious when * making any changes * * @param conFac the ConnectionFactory to use * @param user optional user name * @param pass optional password * @param jmsSpec11 should we use JMS 1.1 API ? * @param isQueue is this to deal with a Queue? * @return a JMS Connection as requested * @throws JMSException on errors, to be handled and logged by the caller */ public static Connection createConnection(ConnectionFactory conFac, String user, String pass, boolean jmsSpec11, Boolean isQueue) throws JMSException { Connection connection = null; if (log.isDebugEnabled()) { log.debug("Creating a " + (isQueue == null ? "Generic" : isQueue ? "Queue" : "Topic") + "Connection using credentials : (" + user + "/" + pass + ")"); } if (jmsSpec11 || isQueue == null) { if (user != null && pass != null) { connection = conFac.createConnection(user, pass); } else { connection = conFac.createConnection(); } } else { QueueConnectionFactory qConFac = null; TopicConnectionFactory tConFac = null; if (isQueue) { qConFac = (QueueConnectionFactory) conFac; } else { tConFac = (TopicConnectionFactory) conFac; } if (user != null && pass != null) { if (qConFac != null) { connection = qConFac.createQueueConnection(user, pass); } else if (tConFac != null) { connection = tConFac.createTopicConnection(user, pass); } } else { if (qConFac != null) { connection = qConFac.createQueueConnection(); } else if (tConFac != null) { connection = tConFac.createTopicConnection(); } } } return connection; } /** * This is a JMS spec independent method to create a Session. Please be cautious when * making any changes * * @param connection the JMS Connection * @param transacted should the session be transacted? * @param ackMode the ACK mode for the session * @param jmsSpec11 should we use the JMS 1.1 API? * @param isQueue is this Session to deal with a Queue? * @return a Session created for the given information * @throws JMSException on errors, to be handled and logged by the caller */ public static Session createSession(Connection connection, boolean transacted, int ackMode, boolean jmsSpec11, Boolean isQueue) throws JMSException { if (jmsSpec11 || isQueue == null) { return connection.createSession(transacted, ackMode); } else { if (isQueue) { return ((QueueConnection) connection).createQueueSession(transacted, ackMode); } else { return ((TopicConnection) connection).createTopicSession(transacted, ackMode); } } } /** * This is a JMS spec independent method to create a MessageConsumer. Please be cautious when * making any changes * * @param session JMS session * @param destination the Destination * @param isQueue is the Destination a queue? * @param subscriberName optional client name to use for a durable subscription to a topic * @param messageSelector optional message selector * @param pubSubNoLocal should we receive messages sent by us during pub-sub? * @param isDurable is this a durable topic subscription? * @param jmsSpec11 should we use JMS 1.1 API ? * @return a MessageConsumer to receive messages * @throws JMSException on errors, to be handled and logged by the caller */ public static MessageConsumer createConsumer( Session session, Destination destination, Boolean isQueue, String subscriberName, String messageSelector, boolean pubSubNoLocal, boolean isDurable, boolean jmsSpec11) throws JMSException { if (jmsSpec11 || isQueue == null) { if (isDurable) { return session.createDurableSubscriber( (Topic) destination, subscriberName, messageSelector, pubSubNoLocal); } else { return session.createConsumer(destination, messageSelector, pubSubNoLocal); } } else { if (isQueue) { return ((QueueSession) session).createReceiver((Queue) destination, messageSelector); } else { if (isDurable) { return ((TopicSession) session).createDurableSubscriber( (Topic) destination, subscriberName, messageSelector, pubSubNoLocal); } else { return ((TopicSession) session).createSubscriber( (Topic) destination, messageSelector, pubSubNoLocal); } } } } /** * This is a JMS spec independent method to create a MessageProducer. Please be cautious when * making any changes * * @param session JMS session * @param destination the Destination * @param isQueue is the Destination a queue? * @param jmsSpec11 should we use JMS 1.1 API ? * @return a MessageProducer to send messages to the given Destination * @throws JMSException on errors, to be handled and logged by the caller */ public static MessageProducer createProducer( Session session, Destination destination, Boolean isQueue, boolean jmsSpec11) throws JMSException { if (jmsSpec11 || isQueue == null) { return session.createProducer(destination); } else { if (isQueue) { return ((QueueSession) session).createSender((Queue) destination); } else { return ((TopicSession) session).createPublisher((Topic) destination); } } } /** * Return a String representation of the destination type * @param destType the destination type indicator int * @return a descriptive String */ public static String getDestinationTypeAsString(int destType) { if (destType == JMSConstants.QUEUE) { return "Queue"; } else if (destType == JMSConstants.TOPIC) { return "Topic"; } else { return "Generic"; } } /** * Return the JMS destination with the given destination name looked up from the context * * @param context the Context to lookup * @param destinationName name of the destination to be looked up * @param destinationType type of the destination to be looked up * @return the JMS destination, or null if it does not exist */ public static Destination lookupDestination(Context context, String destinationName, String destinationType) throws NamingException { if (destinationName == null) { return null; } try { return JMSUtils.lookup(context, Destination.class, destinationName); } catch (NameNotFoundException e) { try { return JMSUtils.lookup(context, Destination.class, (JMSConstants.DESTINATION_TYPE_TOPIC.equalsIgnoreCase(destinationType) ? "dynamicTopics/" : "dynamicQueues/") + destinationName); } catch (NamingException x) { log.warn("Cannot locate destination : " + destinationName); // Rethrow the original exception. If we get here this most likely means that // the JMS provider doesn't support dynamic(Queues|Topics) and we should simply // report the original lookup failure. throw e; } } catch (NamingException e) { log.warn("Cannot locate destination : " + destinationName, e); throw e; } } }