/*
* 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.axis2.context.MessageContext;
import org.apache.axis2.transport.OutTransportInfo;
import org.apache.axis2.transport.base.BaseUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.jms.*;
import javax.jms.Queue;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* The JMS OutTransportInfo is a holder of information to send an outgoing message
* (e.g. a Response) to a JMS destination. Thus at a minimum a reference to a
* ConnectionFactory and a Destination are held
*/
public class JMSOutTransportInfo implements OutTransportInfo {
private static final Log log = LogFactory.getLog(JMSOutTransportInfo.class);
/**
* this is a reference to the underlying JMS ConnectionFactory when sending messages
* through connection factories not defined at the TransportSender level
*/
private ConnectionFactory connectionFactory = null;
/**
* this is a reference to a JMS Connection Factory instance, which has a reference
* to the underlying actual connection factory, an open connection to the JMS provider
* and optionally a session already available for use
*/
private JMSConnectionFactory jmsConnectionFactory = null;
/** the Destination queue or topic for the outgoing message */
private Destination destination = null;
/** the Destination queue or topic for the outgoing message
* i.e. JMSConstants.DESTINATION_TYPE_QUEUE, DESTINATION_TYPE_TOPIC or DESTINATION_TYPE_GENERIC
*/
private String destinationType = JMSConstants.DESTINATION_TYPE_GENERIC;
/** the Reply Destination queue or topic for the outgoing message */
private Destination replyDestination = null;
/** the Reply Destination name */
private String replyDestinationName = null;
/** the Reply Destination queue or topic for the outgoing message
* i.e. JMSConstants.DESTINATION_TYPE_QUEUE, DESTINATION_TYPE_TOPIC or DESTINATION_TYPE_GENERIC
*/
private String replyDestinationType = JMSConstants.DESTINATION_TYPE_GENERIC;
/** the EPR properties when the out-transport info is generated from a target EPR */
private Hashtable<String,String> properties = null;
/** the target EPR string where applicable */
private String targetEPR = null;
/** the message property name that stores the content type of the outgoing message */
private String contentTypeProperty;
/** the message property name that stores the cache level for the JMS endpoint */
private int cacheLevel;
private String jmsSpecVersion;
/**
* Creates an instance using the given JMS connection factory and destination
*
* @param jmsConnectionFactory the JMS connection factory
* @param dest the destination
* @param contentTypeProperty the content type
*/
JMSOutTransportInfo(JMSConnectionFactory jmsConnectionFactory, Destination dest,
String contentTypeProperty) {
this.jmsConnectionFactory = jmsConnectionFactory;
this.destination = dest;
destinationType = dest instanceof Topic ? JMSConstants.DESTINATION_TYPE_TOPIC
: JMSConstants.DESTINATION_TYPE_QUEUE;
this.contentTypeProperty = contentTypeProperty;
}
/**
* Creates and instance using the given URL
*
* @param targetEPR the target EPR
*/
JMSOutTransportInfo(String targetEPR) {
this.targetEPR = targetEPR;
properties = BaseUtils.getEPRProperties(targetEPR);
String destinationType = properties.get(JMSConstants.PARAM_DEST_TYPE);
if (destinationType != null) {
setDestinationType(destinationType);
}
String replyDestinationType = properties.get(JMSConstants.PARAM_REPLY_DEST_TYPE);
if (replyDestinationType != null) {
setReplyDestinationType(replyDestinationType);
}
replyDestinationName = properties.get(JMSConstants.PARAM_REPLY_DESTINATION);
contentTypeProperty = properties.get(JMSConstants.CONTENT_TYPE_PROPERTY_PARAM);
cacheLevel = getCacheLevel(properties.get(JMSConstants.PARAM_CACHE_LEVEL));
jmsSpecVersion =
properties.get(JMSConstants.JMS_SPEC_VERSION) != null
? properties.get(JMSConstants.JMS_SPEC_VERSION)
: "1.0.2b";
}
/**
* Provides a lazy load when created with a target EPR. This method performs actual
* lookup for the connection factory and destination
*/
private void loadConnectionFactoryFromProperties() {
if (properties != null) {
// This condition passes only when the JMSOutTransportInfo is created from an EPR
InitialContext context = null;
try {
// It's a little expensive to do this for each and every outgoing request, but
// the user can avoid this by defining a connection factory for JMS sender
// TODO: See if this can be further optimized by caching the destination by EPR
context = new InitialContext(properties);
} catch (NamingException e) {
handleException("Could not get an initial context using " + properties, e);
}
if (destination == null) {
destination = getDestination(context, targetEPR);
replyDestination = getReplyDestination(context, targetEPR);
}
connectionFactory = getConnectionFactory(context, properties);
if (context != null) {
try {
context.close();
} catch (NamingException e) {
log.warn("Error while cleaning up the InitialContext", e);
}
}
}
}
/**
* Get the referenced ConnectionFactory using the properties from the context
*
* @param context the context to use for lookup
* @param props the properties which contains the JNDI name of the factory
* @return the connection factory
*/
private ConnectionFactory getConnectionFactory(Context context, Hashtable<String,String> props) {
try {
String conFacJndiName = props.get(JMSConstants.PARAM_CONFAC_JNDI_NAME);
if (conFacJndiName != null) {
return JMSUtils.lookup(context, ConnectionFactory.class, conFacJndiName);
} else {
handleException("Connection Factory JNDI name cannot be determined");
}
} catch (NamingException e) {
handleException("Failed to look up connection factory from JNDI", e);
}
return null;
}
/**
* Get the JMS destination specified by the given URL from the context
*
* @param context the Context to lookup
* @param url URL
* @return the JMS destination, or null if it does not exist
*/
private Destination getDestination(Context context, String url) {
String destinationName = JMSUtils.getDestination(url);
if (log.isDebugEnabled()) {
log.debug("Lookup the JMS destination " + destinationName + " of type " + destinationType
+ " extracted from the URL " + JMSUtils.maskURLPasswordAndCredentials(url));
}
try {
return JMSUtils.lookupDestination(context, destinationName, destinationType);
} catch (NamingException e) {
handleException("Couldn't locate the JMS destination " + destinationName + " of type " + destinationType
+ " extracted from the URL " + JMSUtils.maskURLPasswordAndCredentials(url), e);
}
// never executes but keeps the compiler happy
return null;
}
/**
* Get the JMS reply destination specified by the given URL from the context
*
* @param context the Context to lookup
* @param url URL
* @return the JMS destination, or null if it does not exist
*/
private Destination getReplyDestination(Context context, String url) {
String replyDestinationName = properties.get(JMSConstants.PARAM_REPLY_DESTINATION);
if (log.isDebugEnabled()) {
log.debug("Lookup the JMS destination " + replyDestinationName + " of type " + replyDestinationType
+ " extracted from the URL " + JMSUtils.maskURLPasswordAndCredentials(url));
}
try {
return JMSUtils.lookupDestination(context, replyDestinationName, replyDestinationType);
} catch (NamingException e) {
handleException(
"Couldn't locate the JMS destination " + replyDestinationName + " of type " + replyDestinationType
+ " extracted from the URL " + JMSUtils.maskURLPasswordAndCredentials(url), e);
}
// never executes but keeps the compiler happy
return null;
}
/**
* Look up for the given destination
* @param replyDest the JNDI name to lookup Destination required
* @return Destination for the JNDI name passed
*/
public Destination getReplyDestination(String replyDest) {
if (log.isDebugEnabled()) {
log.debug("Lookup the JMS destination " + replyDest + " of type "
+ replyDestinationType);
}
try {
return JMSUtils.lookupDestination(
jmsConnectionFactory.getContext(), replyDest, replyDestinationType);
} catch (NamingException e) {
handleException("Couldn't locate the JMS destination " + replyDest
+ " of type " + replyDestinationType, e);
}
// never executes but keeps the compiler happy
return null;
}
private void handleException(String s) {
log.error(s);
throw new AxisJMSException(s);
}
private void handleException(String s, Exception e) {
log.error(s, e);
throw new AxisJMSException(s, e);
}
public Destination getDestination() {
return destination;
}
public ConnectionFactory getConnectionFactory() {
return connectionFactory;
}
public JMSConnectionFactory getJmsConnectionFactory() {
return jmsConnectionFactory;
}
public void setContentType(String contentType) {
// this is a useless Axis2 method imposed by the OutTransportInfo interface :(
}
public Hashtable<String,String> getProperties() {
return properties;
}
public String getTargetEPR() {
return targetEPR;
}
public String getDestinationType() {
return destinationType;
}
public void setDestinationType(String destinationType) {
if (destinationType != null) {
this.destinationType = destinationType;
}
}
public Destination getReplyDestination() {
return replyDestination;
}
public void setReplyDestination(Destination replyDestination) {
this.replyDestination = replyDestination;
}
public String getReplyDestinationType() {
return replyDestinationType;
}
public void setReplyDestinationType(String replyDestinationType) {
this.replyDestinationType = replyDestinationType;
}
public String getReplyDestinationName() {
return replyDestinationName;
}
public void setReplyDestinationName(String replyDestinationName) {
this.replyDestinationName = replyDestinationName;
}
public String getContentTypeProperty() {
return contentTypeProperty;
}
public void setContentTypeProperty(String contentTypeProperty) {
this.contentTypeProperty = contentTypeProperty;
}
/**
* Create a one time MessageProducer for this JMS OutTransport information.
* For simplicity and best compatibility, this method uses only JMS 1.0.2b API.
* Please be cautious when making any changes
*
* @return a JMSSender based on one-time use resources
* @throws JMSException on errors, to be handled and logged by the caller
*/
public JMSMessageSender createJMSSender(MessageContext msgCtx) throws JMSException {
// digest the targetAddress and locate CF from the EPR
loadConnectionFactoryFromProperties();
// create a one time connection and session to be used
String user = properties != null ? properties.get(JMSConstants.PARAM_JMS_USERNAME) : null;
String pass = properties != null ? properties.get(JMSConstants.PARAM_JMS_PASSWORD) : null;
QueueConnectionFactory qConFac = null;
TopicConnectionFactory tConFac = null;
int destType = -1;
// TODO: there is something missing here for destination type generic
if (JMSConstants.DESTINATION_TYPE_QUEUE.equals(destinationType)) {
destType = JMSConstants.QUEUE;
qConFac = (QueueConnectionFactory) connectionFactory;
} else if (JMSConstants.DESTINATION_TYPE_TOPIC.equals(destinationType)) {
destType = JMSConstants.TOPIC;
tConFac = (TopicConnectionFactory) connectionFactory;
} else{
//treat jmsdestination type=queue(default is queue)
destType = JMSConstants.QUEUE;
qConFac = (QueueConnectionFactory) connectionFactory;
}
if (msgCtx.getProperty(JMSConstants.JMS_XA_TRANSACTION_MANAGER) != null) {
XAConnection connection = null;
if (user != null && pass != null) {
if (qConFac != null) {
connection = ((XAConnectionFactory) qConFac).createXAConnection(user, pass);
} else if (tConFac != null) {
connection = ((XAConnectionFactory) tConFac).createXAConnection(user, pass);
}
} else {
if (qConFac != null) {
connection = ((XAConnectionFactory) qConFac).createXAConnection();
} else if (tConFac != null) {
connection = ((XAConnectionFactory) tConFac).createXAConnection();
}
}
if (connection == null) {
connection = ((XAConnectionFactory) qConFac).createXAConnection();
}
XASession session = null;
MessageProducer producer = null;
if (connection != null) {
if (destType == JMSConstants.QUEUE) {
session = connection.createXASession();
producer = session.createProducer(destination);
} else {
session = connection.createXASession();
producer = session.createProducer(destination);
}
}
XAResource xaResource = session.getXAResource();
TransactionManager tx = null;
Xid xid1 = null;
Transaction transaction = null;
java.util.UUID uuid = java.util.UUID.randomUUID();
try {
tx = (TransactionManager) msgCtx.getProperty(JMSConstants.JMS_XA_TRANSACTION_MANAGER);
transaction = tx.getTransaction();
msgCtx.setProperty(JMSConstants.JMS_XA_TRANSACTION_MANAGER, tx);
msgCtx.setProperty(JMSConstants.JMS_XA_TRANSACTION, transaction);
xid1 = new JMSXid(JMSConstants.JMS_XA_TRANSACTION_PREFIX.getBytes(StandardCharsets.UTF_8), 1,
uuid.toString().getBytes());
msgCtx.setProperty("XID", xid1);
xaResource.start(xid1, XAResource.TMNOFLAGS);
} catch (SystemException e) {
handleException("Error Occurred during starting getting Transaction.", e);
} catch (XAException e) {
handleException("Error Occurred during starting XA resource.", e);
}
return new JMSMessageSender(
connection,
session,
producer,
destination,
jmsConnectionFactory == null ?
this.cacheLevel : jmsConnectionFactory.getCacheLevel(),
jmsSpecVersion,
destType == -1 ?
null : destType == JMSConstants.QUEUE ? Boolean.TRUE : Boolean.FALSE,
transaction,
xid1,
xaResource
);
} else {
Connection connection = null;
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();
}
}
if (connection == null) {
connection = jmsConnectionFactory != null ? jmsConnectionFactory.getConnection() : null;
}
Session session = null;
MessageProducer producer = null;
if (connection != null) {
if (destType == JMSConstants.QUEUE) {
session = ((QueueConnection) connection).
createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
producer = ((QueueSession) session).createSender((Queue) destination);
} else {
session = ((TopicConnection) connection).
createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
producer = ((TopicSession) session).createPublisher((Topic) destination);
}
}
return new JMSMessageSender(
connection,
session,
producer,
destination,
jmsConnectionFactory == null ?
this.cacheLevel : jmsConnectionFactory.getCacheLevel(),
jmsSpecVersion,
destType == -1 ?
null : destType == JMSConstants.QUEUE ? Boolean.TRUE : Boolean.FALSE
);
}
}
/**
* Convert the cache value to int
*/
private int getCacheLevel(String cacheValue) {
int cacheLevel = JMSConstants.CACHE_NONE;
if ("none".equalsIgnoreCase(cacheValue)) {
cacheLevel = JMSConstants.CACHE_NONE;
} else if ("connection".equalsIgnoreCase(cacheValue)) {
cacheLevel = JMSConstants.CACHE_CONNECTION;
} else if ("session".equals(cacheValue)) {
cacheLevel = JMSConstants.CACHE_SESSION;
} else if ("producer".equals(cacheValue)) {
cacheLevel = JMSConstants.CACHE_PRODUCER;
} else if ("consumer".equals(cacheValue)) {
cacheLevel = JMSConstants.CACHE_CONSUMER;
} else if (cacheValue != null) {
throw new AxisJMSException("Invalid cache level : " + cacheValue + " for JMSOutTransportInfo");
}
return cacheLevel;
}
}