/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.xmpp.util; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Map; import java.util.StringTokenizer; import java.util.concurrent.Executor; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.stream.XMLStreamException; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.om.OMException; import org.apache.axiom.soap.SOAPEnvelope; import org.apache.axiom.soap.SOAPFactory; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.builder.BuilderUtil; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisOperation; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.TransportInDescription; import org.apache.axis2.description.TransportOutDescription; import org.apache.axis2.engine.AxisEngine; import org.apache.axis2.transport.TransportUtils; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.axis2.transport.xmpp.XMPPSender; import org.apache.axis2.util.MessageContextBuilder; import org.apache.axis2.util.MultipleEntryHashMap; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jivesoftware.smack.PacketListener; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Packet; public class XMPPPacketListener implements PacketListener { private static final Log log = LogFactory.getLog(XMPPPacketListener.class); private XMPPConnectionFactory xmppConnectionFactory = null; private ConfigurationContext configurationContext = null; private Executor workerPool = null; public final static String CONTENT_TYPE = "mail.contenttype"; public XMPPPacketListener(XMPPConnectionFactory xmppConnectionFactory, ConfigurationContext configurationContext, Executor workerPool) { this.xmppConnectionFactory = xmppConnectionFactory; this.configurationContext = configurationContext; this.workerPool = workerPool; } /** * This method gets triggered when server side gets a message */ public void processPacket(Packet packet) { log.debug("Received : "+packet.toXML()); if(packet instanceof Message){ workerPool.execute(new Worker(packet)); } } /** * Creates message context using values received in XMPP packet * @param packet * @return MessageContext * @throws AxisFault */ private MessageContext createMessageContext(Packet packet) throws AxisFault { Message message = (Message) packet; Boolean isServerSide = (Boolean) message .getProperty(XMPPConstants.IS_SERVER_SIDE); String serviceName = (String) message .getProperty(XMPPConstants.SERVICE_NAME); String action = (String) message.getProperty(XMPPConstants.ACTION); MessageContext msgContext = null; TransportInDescription transportIn = configurationContext .getAxisConfiguration().getTransportIn("xmpp"); TransportOutDescription transportOut = configurationContext .getAxisConfiguration().getTransportOut("xmpp"); if ((transportIn != null) && (transportOut != null)) { msgContext = configurationContext.createMessageContext(); msgContext.setTransportIn(transportIn); msgContext.setTransportOut(transportOut); if (isServerSide != null) { msgContext.setServerSide(isServerSide.booleanValue()); } msgContext.setProperty( CONTENT_TYPE, "text/xml"); msgContext.setProperty( Constants.Configuration.CHARACTER_SET_ENCODING, "UTF-8"); msgContext.setIncomingTransportName("xmpp"); Map services = configurationContext.getAxisConfiguration() .getServices(); AxisService axisService = (AxisService) services.get(serviceName); msgContext.setAxisService(axisService); msgContext.setSoapAction(action); // pass the configurationFactory to transport sender msgContext.setProperty("XMPPConfigurationFactory", this.xmppConnectionFactory); if (packet.getFrom() != null) { msgContext.setFrom(new EndpointReference(packet.getFrom())); } if (packet.getTo() != null) { msgContext.setTo(new EndpointReference(packet.getTo())); } XMPPOutTransportInfo xmppOutTransportInfo = new XMPPOutTransportInfo(); xmppOutTransportInfo .setConnectionFactory(this.xmppConnectionFactory); String packetFrom = packet.getFrom(); if (packetFrom != null) { EndpointReference fromEPR = new EndpointReference(packetFrom); xmppOutTransportInfo.setFrom(fromEPR); xmppOutTransportInfo.setDestinationAccount(packetFrom); } // Save Message-Id to set as In-Reply-To on reply String xmppMessageId = packet.getPacketID(); if (xmppMessageId != null) { xmppOutTransportInfo.setInReplyTo(xmppMessageId); } xmppOutTransportInfo.setSequenceID((String)message.getProperty(XMPPConstants.SEQUENCE_ID)); msgContext.setProperty( org.apache.axis2.Constants.OUT_TRANSPORT_INFO, xmppOutTransportInfo); buildSOAPEnvelope(packet, msgContext); } else { throw new AxisFault("Either transport in or transport out is null"); } return msgContext; } /** * builds SOAP envelop using message contained in packet * @param packet * @param msgContext * @throws AxisFault */ private void buildSOAPEnvelope(Packet packet, MessageContext msgContext) throws AxisFault{ Message message = (Message)packet; String logMsg = "Trying to create " + "message content using XMPP message received :"+packet.toXML(); String messageBody = StringEscapeUtils.unescapeXml(message.getBody()); if(msgContext.isServerSide()){ log.debug("Received Envelope : "+messageBody); } InputStream inputStream = new ByteArrayInputStream(messageBody.getBytes()); SOAPEnvelope envelope = null; try { Object obj = message.getProperty(XMPPConstants.CONTAINS_SOAP_ENVELOPE); if(obj != null && ((Boolean)obj).booleanValue()){ String contentType = (String)message.getProperty(XMPPConstants.CONTENT_TYPE); if(contentType == null){ throw new AxisFault("Can not Find Content type Property in the XMPP Message"); } envelope = TransportUtils.createSOAPMessage(msgContext, inputStream, contentType); msgContext.setProperty(XMPPConstants.CONTAINS_SOAP_ENVELOPE, new Boolean(true)); }else{ //A text message has been received from a chat client //This message could either be a service call or a help command if(!(messageContainsCommandsFromChat(messageBody,msgContext))){ envelope = createSOAPEnvelopeForRawMessage(msgContext, messageBody); } } if(envelope != null){ msgContext.setEnvelope(envelope); } }catch (OMException e) { log.error(logMsg, e); throw new AxisFault(logMsg); }catch (XMLStreamException e) { log.error(logMsg, e); throw new AxisFault(logMsg); }catch (FactoryConfigurationError e) { log.error(logMsg, e); throw new AxisFault(logMsg); }catch (AxisFault e){ log.error(logMsg, e); throw new AxisFault(logMsg); } } /** * In the direct chat client scenario, client can send commands & retrieve details * on available services, operations,etc. This method checks if a client has sent * such command. Only limited set of commands are available as of now. * @param message * @param msgContext * @return */ private boolean messageContainsCommandsFromChat(String message,MessageContext msgContext){ boolean containsKnownCommand = false; if(message.trim().startsWith("help")){ containsKnownCommand = true; }else if(message.trim().startsWith("listServices")){ containsKnownCommand = true; }else if (message.trim().startsWith("getOperations")){ containsKnownCommand = true; } if(containsKnownCommand){ msgContext.setProperty(XMPPConstants.MESSAGE_FROM_CHAT,message.trim()); } return containsKnownCommand; } /** * Creates a SOAP envelope using details found in chat message. * @param msgCtx * @param chatMessage * @return */ private SOAPEnvelope createSOAPEnvelopeForRawMessage(MessageContext msgCtx,String chatMessage) throws AxisFault{ //TODO : need to add error handling logic String callRemoved = chatMessage.replaceFirst("call", ""); //extract Service name String serviceName = callRemoved.trim().substring(0, callRemoved.indexOf(":")-1); String operationName = callRemoved.trim().substring(callRemoved.indexOf(":"), callRemoved.indexOf("(")-1); //Extract parameters from IM message String parameterList = callRemoved.trim().substring(callRemoved.indexOf("("),callRemoved.trim().length()-1); StringTokenizer st = new StringTokenizer(parameterList,","); MultipleEntryHashMap parameterMap = new MultipleEntryHashMap(); while(st.hasMoreTokens()){ String token = st.nextToken(); String name = token.substring(0, token.indexOf("=")); String value = token.substring(token.indexOf("=")+1); parameterMap.put(name, value); } SOAPEnvelope envelope = null; try { msgCtx.setProperty(XMPPConstants.CONTAINS_SOAP_ENVELOPE, new Boolean(true)); if(serviceName != null && serviceName.trim().length() > 0){ AxisService axisService = msgCtx.getConfigurationContext().getAxisConfiguration().getService(serviceName); msgCtx.setAxisService(axisService); AxisOperation axisOperation = axisService.getOperationBySOAPAction("urn:"+operationName); if(axisOperation != null){ msgCtx.setAxisOperation(axisOperation); } } if(operationName != null && operationName.trim().length() > 0){ msgCtx.setSoapAction("urn:"+operationName); } XMPPOutTransportInfo xmppOutTransportInfo = (XMPPOutTransportInfo)msgCtx.getProperty( org.apache.axis2.Constants.OUT_TRANSPORT_INFO); //This should be only set for messages received via chat. //TODO : need to read from a constant xmppOutTransportInfo.setContentType("xmpp/text"); msgCtx.setServerSide(true); //TODO : need to support SOAP12 as well SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory(); envelope = BuilderUtil.buildsoapMessage(msgCtx, parameterMap, soapFactory); //TODO : improve error handling & messages } catch (AxisFault e) { throw new AxisFault(e.getMessage()); } catch (OMException e) { throw new AxisFault(e.getMessage()); } catch (FactoryConfigurationError e) { throw new AxisFault(e.getMessage()); } return envelope; } /** * The actual Runnable Worker implementation which will process the * received XMPP messages in the worker thread pool */ class Worker implements Runnable { private Packet packet = null; Worker(Packet packet) { this.packet = packet; } public void run() { MessageContext msgCtx = null; try { msgCtx = createMessageContext(packet); Object obj = msgCtx.getProperty(XMPPConstants.CONTAINS_SOAP_ENVELOPE); if(obj != null && ((Boolean)obj).booleanValue()){ if(msgCtx.isProcessingFault() && msgCtx.isServerSide()){ AxisEngine.sendFault(msgCtx); }else{ AxisEngine.receive(msgCtx); } }else{ //Send a text reply message to command received from chat client XMPPSender.processChatMessage(msgCtx); } } catch (AxisFault e) { log.error("Error occurred while sending message"+e); if (msgCtx != null && msgCtx.isServerSide()) { MessageContext faultContext; try { faultContext = MessageContextBuilder.createFaultMessageContext(msgCtx, e); AxisEngine.sendFault(faultContext); } catch (AxisFault e1) { log.error("Error occurred while creating SOAPFault message"+e1); } } } } } }