/*
* Copyright (c) 2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* 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.msmq;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.HashMap;
import java.util.Map;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.TransportOutDescription;
import org.apache.axis2.transport.MessageFormatter;
import org.apache.axis2.transport.OutTransportInfo;
import org.apache.axis2.transport.TransportUtils;
import org.apache.axis2.transport.base.AbstractTransportSender;
import org.apache.axis2.transport.base.BaseConstants;
import org.apache.axis2.transport.base.BaseUtils;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.msmq.MSMQConnectionManager.ConnectionType;
import org.apache.axis2.transport.msmq.util.IMSMQClient;
import org.apache.axis2.transport.msmq.util.MSMQCamelClient;
import org.apache.axis2.transport.msmq.util.MSMQUtil;
import org.apache.axis2.transport.msmq.util.Message;
import org.apache.axis2.transport.msmq.util.MessageQueueException;
import org.apache.axis2.util.MessageProcessorSelector;
import org.apache.commons.io.output.WriterOutputStream;
/**
* Transport sender for MSMSQ
*
* formats which will be supporting
*
* msmq:msmqQueueName[?options]
* ex: msmq:DIRECT=OS:localhost\\private$\\test?concurrentConsumers=1
* msmq:DIRECT=OS:localhost\\private$\\test?deliveryPersistent=true&priority=5&
* timeToLive=10
*/
public class MSMQSender extends AbstractTransportSender {
@Override
public void init(ConfigurationContext cfgCtx, TransportOutDescription transportOut) throws AxisFault {
super.init(cfgCtx, transportOut);
if (log.isDebugEnabled()) {
log.info("MSMQ transport Sender initialized");
}
MSMQConnectionManager.init(transportOut, ConnectionType.SENDER);
}
/**
* Performs the actual sending of the MSMQ message
*/
@Override
public void sendMessage(MessageContext msgCtx, String targetAddress, OutTransportInfo outTransportInfo) throws AxisFault {
if (targetAddress == null) { // no queue name defined..return..
return;
}
String queueName = targetAddress.substring(5, targetAddress.length());
// TODO: get this property from .NET message client, i.e. in .NET
// contentType or something
String contentType = (String) msgCtx.getProperty(MSMQConstants.CONENT_TYPE_PROPERTY_PARAM); // message type of the incoming message
if (contentType == null) {
contentType = MSMQConstants.DEFAULT_CONTENT_TYPE; // TODO:fix sender content type
}
//Needs to synchronize as the JNI calls are not thread safe..
synchronized (this) {
sendOverMSMQ(msgCtx, queueName, contentType);
}
}
private void sendOverMSMQ(MessageContext msgCtx, String queuName, String contentType) throws AxisFault {
IMSMQClient mqClient = new MSMQCamelClient();
Message message = null;
try {
message = createMSMQMessage(msgCtx, contentType, queuName);
message.setLabel(contentType);
} catch (AxisFault axisFault) {
handleException("Error creaging the MSMQ message from the message context", axisFault);
}
// TODO: should we wait for a response, ok we need to support MSMQ
// request/response scenario
// get the reply queue name and start to receive from here. See
// JMSSender.java:166 for more
// information
boolean waitForResponse = waitForSynchronousResponse(msgCtx);
// Map<String, String> msmqPropertyMap = new HashMap<String, String>();
// if (queuName.indexOf("?") > 0) {
// MSMQUtil.getMsmsqPropertyByName(msmqPropertyMap, queuName);
// queuName = queuName.substring(0, queuName.indexOf("?"));
// }
if(waitForResponse){
try {
String correlationId = "";
if (msgCtx.getRelatesTo() != null) {
correlationId = msgCtx.getRelatesTo().getValue();
}else{
correlationId = MSMQConstants.DEFAULT_MSG_CORRELATION_ID;// TODO: if we are having a one way message we don't have this
}
message.setCorrelationIdAsString(correlationId);
} catch (UnsupportedEncodingException e) {
handleException("Error while setting up message Correlation",e);
}
}else{
message.setCorrelationId(MSMQConstants.DEFAULT_MSG_CORRELATION_ID.getBytes());
}
try {
try{
mqClient.create(queuName, "MSMQ-WSO2", false); //By default we are handling queues without transactional
}catch (MessageQueueException e) {
log.warn("Queue " + queuName + " already there.");
}
mqClient.open(queuName,org.apache.axis2.transport.msmq.util.IMSMQClient.Access.SEND); //TODO: how to handle transactional messages
} catch (AxisFault axisFault) {
log.error("Queue " + queuName + " already there.");
handleException("Could not open the queu: " + queuName + " for lisinting", axisFault);
}
try {
if (message != null) {
mqClient.send(message);
}
} catch (AxisFault axisFault) {
handleException("Cloud not send the message: " + axisFault);
} finally {
try {
mqClient.close();
} catch (AxisFault axisFault) {
handleException("Cloud not close the queue: ", axisFault);
}
}
if (waitForResponse) {
// TODO: logic to be finalized on handling synchronous request/reply
// for the given MSMQ message
//MSMQClient replyClient = new MSMQClient();
}
}
/**
* Generating MSMQ message wrapper in order to communicate with JNI
* @param msgCtx
* @param contentTypeProperty
* @param queuName
* @return
* @throws AxisFault
*/
private Message createMSMQMessage(MessageContext msgCtx, String messageContentType, String queuName) throws AxisFault {
String msgBody = null;
byte[] msgByteBody = null;
String msgType = getProperty(msgCtx, MSMQConstants.MSMQ_MESSAGE_TYPE);
//String msgLable = "L:" + queuName+"["+contentType+"]"; // TODO
// check the first element of the SOAP body, do we have content wrapped
// using the
// default wrapper elements for binary
// (BaseConstants.DEFAULT_BINARY_WRAPPER) or
// text (BaseConstants.DEFAULT_TEXT_WRAPPER) ? If so, do not create SOAP
// messages
// for MSMQ but just get the payload in its native format
String msmqMessageType = guessMessageType(msgCtx);
if (msmqMessageType == null) {
OMOutputFormat format = BaseUtils.getOMOutputFormat(msgCtx);
MessageFormatter messageFormatter = null;
try {
messageFormatter = MessageProcessorSelector.getMessageFormatter(msgCtx);
} catch (AxisFault axisFault) {
handleException("Unable to get the message formatter to use", axisFault);
}
String contentType = messageFormatter != null ?messageFormatter.getContentType(msgCtx, format, msgCtx.getSoapAction()):"";
boolean useBytesMessage = msgType != null && MSMQConstants.MSMQ_BYTE_MESSAGE.equals(msgType) ||
contentType.indexOf(HTTPConstants.HEADER_ACCEPT_MULTIPART_RELATED) > -1;
OutputStream out = null;
StringWriter sw = null;
if (useBytesMessage) {
// TODO: handle MSMQ byte message here
} else {
sw = new StringWriter();
try {
out = new WriterOutputStream(sw, format.getCharSetEncoding());
} catch (UnsupportedCharsetException ex) {
handleException("Unsupported encoding " + format.getCharSetEncoding(), ex);
}
}
try {
if (out != null) {
messageFormatter.writeTo(msgCtx, format, out, true);
out.close();
}
} catch (IOException e) {
handleException("IO Error while creating BytesMessage", e);
}
if (!useBytesMessage) {
msgBody = sw.toString();
}
} else if (MSMQConstants.MSMQ_BYTE_MESSAGE.equals(msmqMessageType)) {
// TODO. handle .net byte messages here.
} else if (MSMQConstants.MSMQ_TEXT_MESSAGE.equals(msmqMessageType)) {
msgBody = msgCtx.getEnvelope().getBody().getFirstChildWithName(BaseConstants.DEFAULT_TEXT_WRAPPER).getText();
}
try {
Message message = new Message(msgBody !=null?msgBody:"", "", ""); //Keep message correlation empty will be deciding later on the process
return message;
} catch (UnsupportedEncodingException e) {
log.error("Unsported message has been received: ", e);
handleEexception("Unsported message has been received: ", e);
}
return null;
}
/**
* Based on the message payload body type invoking the message type
*
* @param msgContext
* @return
*/
private String guessMessageType(MessageContext msgContext) {
OMElement firstChild = msgContext.getEnvelope().getBody().getFirstElement();
if (firstChild != null) {
if (BaseConstants.DEFAULT_BINARY_WRAPPER.equals(firstChild.getQName())) {
return MSMQConstants.MSMQ_BYTE_MESSAGE;
} else if (BaseConstants.DEFAULT_TEXT_WRAPPER.equals(firstChild.getQName())) {
return MSMQConstants.MSMQ_TEXT_MESSAGE;
}
}
return null;
}
private String getProperty(MessageContext mc, String key) {
return (String) mc.getProperty(key);
}
private void handleEexception(String msg, Exception e) throws AxisFault {
log.error(msg, e);
throw new AxisFault(msg, e);
}
}