/* * 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; import org.apache.axiom.om.OMElement; import org.apache.axiom.soap.SOAP12Version; import org.apache.axiom.soap.SOAPVersion; import org.apache.axis2.AxisFault; import org.apache.axis2.Constants; import org.apache.axis2.client.Options; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisMessage; import org.apache.axis2.description.AxisOperation; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.Parameter; import org.apache.axis2.description.TransportOutDescription; import org.apache.axis2.description.WSDL2Constants; import org.apache.axis2.handlers.AbstractHandler; import org.apache.axis2.transport.OutTransportInfo; import org.apache.axis2.transport.TransportSender; import org.apache.axis2.transport.http.HTTPConstants; import org.apache.axis2.transport.xmpp.util.XMPPClientResponseManager; import org.apache.axis2.transport.xmpp.util.XMPPConnectionFactory; import org.apache.axis2.transport.xmpp.util.XMPPConstants; import org.apache.axis2.transport.xmpp.util.XMPPOutTransportInfo; import org.apache.axis2.transport.xmpp.util.XMPPServerCredentials; import org.apache.axis2.transport.xmpp.util.XMPPUtils; import org.apache.axis2.util.Utils; import org.apache.axis2.wsdl.WSDLConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ws.commons.schema.*; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManager; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.PacketFilter; import org.jivesoftware.smack.filter.PacketTypeFilter; import org.jivesoftware.smack.packet.Message; import javax.xml.namespace.QName; import java.util.Iterator; import java.util.Map; import java.util.UUID; public class XMPPSender extends AbstractHandler implements TransportSender { static Log log = null; XMPPServerCredentials serverCredentials; private XMPPClientResponseManager xmppClientSidePacketListener; private XMPPConnectionFactory defaultConnectionFactory; public XMPPSender() { log = LogFactory.getLog(XMPPSender.class); xmppClientSidePacketListener = new XMPPClientResponseManager(); } public void cleanup(MessageContext msgContext) throws AxisFault { } /** * Initialize the transport sender by reading pre-defined connection factories for * outgoing messages. These will create sessions (one per each destination dealt with) * to be used when messages are being sent. * @param confContext the configuration context * @param transportOut the transport sender definition from axis2.xml * @throws AxisFault on error */ public void init(ConfigurationContext confContext, TransportOutDescription transportOut) throws AxisFault { //if connection details are available from axis configuration //use those & connect to jabber server(s) serverCredentials = new XMPPServerCredentials(); getConnectionDetailsFromAxisConfiguration(transportOut); defaultConnectionFactory = new XMPPConnectionFactory(); } public void stop() {} public InvocationResponse invoke(MessageContext msgContext) throws AxisFault { String targetAddress = (String) msgContext.getProperty( Constants.Configuration.TRANSPORT_URL); if (targetAddress != null) { sendMessage(msgContext, targetAddress, null); } else if (msgContext.getTo() != null && !msgContext.getTo().hasAnonymousAddress()) { targetAddress = msgContext.getTo().getAddress(); if (!msgContext.getTo().hasNoneAddress()) { sendMessage(msgContext, targetAddress, null); } else { //Don't send the message. return InvocationResponse.CONTINUE; } } else if (msgContext.isServerSide()) { // get the out transport info for server side when target EPR is unknown sendMessage(msgContext, null, (OutTransportInfo) msgContext.getProperty(Constants.OUT_TRANSPORT_INFO)); } return InvocationResponse.CONTINUE; } /** * Send the given message over XMPP transport * * @param msgCtx the axis2 message context * @throws AxisFault on error */ public void sendMessage(MessageContext msgCtx, String targetAddress, OutTransportInfo outTransportInfo) throws AxisFault { XMPPConnection xmppConnection = null; XMPPOutTransportInfo xmppOutTransportInfo = null; XMPPConnectionFactory connectionFactory; //if on client side,create connection to xmpp server if(msgCtx.isServerSide()){ xmppOutTransportInfo = (XMPPOutTransportInfo)msgCtx.getProperty(org.apache.axis2.Constants.OUT_TRANSPORT_INFO); connectionFactory = xmppOutTransportInfo.getConnectionFactory(); }else{ getConnectionDetailsFromClientOptions(msgCtx); connectionFactory = defaultConnectionFactory; } synchronized (this) { xmppConnection = connectionFactory.getXmppConnection(); if(xmppConnection == null){ connectionFactory.connect(serverCredentials); xmppConnection = connectionFactory.getXmppConnection(); } } Message message = new Message(); Options options = msgCtx.getOptions(); String serviceName = XMPPUtils.getServiceName(targetAddress); SOAPVersion version = msgCtx.getEnvelope().getVersion(); if(version instanceof SOAP12Version){ message.setProperty(XMPPConstants.CONTENT_TYPE, HTTPConstants.MEDIA_TYPE_APPLICATION_SOAP_XML+ "; action="+ msgCtx.getSoapAction()); }else{ message.setProperty(XMPPConstants.CONTENT_TYPE, HTTPConstants.MEDIA_TYPE_TEXT_XML); } if (targetAddress != null) { xmppOutTransportInfo = new XMPPOutTransportInfo(targetAddress); xmppOutTransportInfo.setConnectionFactory(defaultConnectionFactory); } else if (msgCtx.getTo() != null && !msgCtx.getTo().hasAnonymousAddress()) { //TODO } else if (msgCtx.isServerSide()) { xmppOutTransportInfo = (XMPPOutTransportInfo) msgCtx.getProperty(Constants.OUT_TRANSPORT_INFO); } try{ if(msgCtx.isServerSide()){ message.setProperty(XMPPConstants.IS_SERVER_SIDE, new Boolean(false)); message.setProperty(XMPPConstants.IN_REPLY_TO, xmppOutTransportInfo.getInReplyTo()); message.setProperty(XMPPConstants.SEQUENCE_ID, xmppOutTransportInfo.getSequenceID()); }else{ //message is going to be processed on server side message.setProperty(XMPPConstants.IS_SERVER_SIDE,new Boolean(true)); //we are sending a soap envelope as a message message.setProperty(XMPPConstants.CONTAINS_SOAP_ENVELOPE, new Boolean(true)); message.setProperty(XMPPConstants.SERVICE_NAME, serviceName); String action = options.getAction(); if (action == null) { AxisOperation axisOperation = msgCtx.getAxisOperation(); if (axisOperation != null) { action = axisOperation.getSoapAction(); } } if (action != null) { message.setProperty(XMPPConstants.ACTION, action); } } if(xmppConnection == null){ handleException("Connection to XMPP Server is not established."); } //initialize the chat manager using connection ChatManager chatManager = xmppConnection.getChatManager(); Chat chat = chatManager.createChat(xmppOutTransportInfo.getDestinationAccount(), null); boolean waitForResponse = msgCtx.getOperationContext() != null && WSDL2Constants.MEP_URI_OUT_IN.equals( msgCtx.getOperationContext().getAxisOperation().getMessageExchangePattern()); OMElement msgElement; String messageToBeSent = ""; if(XMPPConstants.XMPP_CONTENT_TYPE_STRING.equals(xmppOutTransportInfo.getContentType())){ //if request is received from a chat client, whole soap envelope //should not be sent. OMElement soapBodyEle = msgCtx.getEnvelope().getBody(); OMElement responseEle = soapBodyEle.getFirstElement(); if(responseEle != null){ msgElement = responseEle.getFirstElement(); }else{ msgElement = responseEle; } }else{ //if request received from a ws client whole soap envelope //must be sent. msgElement = msgCtx.getEnvelope(); } messageToBeSent = msgElement.toString(); message.setBody(messageToBeSent); String key = null; if(waitForResponse && !msgCtx.isServerSide()){ PacketFilter filter = new PacketTypeFilter(message.getClass()); xmppConnection.addPacketListener(xmppClientSidePacketListener,filter); key = UUID.randomUUID().toString(); xmppClientSidePacketListener.listenForResponse(key, msgCtx); message.setProperty(XMPPConstants.SEQUENCE_ID, key); } chat.sendMessage(message); log.debug("Sent message :"+message.toXML()); //If this is on client side, wait for the response from server. //Is this the best way to do this? if(waitForResponse && !msgCtx.isServerSide()){ xmppClientSidePacketListener.waitFor(key); //xmppConnection.disconnect(); log.debug("Received response sucessfully"); } } catch (XMPPException e) { log.error("Error occurred while sending the message : "+message.toXML(),e); handleException("Error occurred while sending the message : "+message.toXML(),e); } catch (InterruptedException e) { log.error("Error occurred while sending the message : "+message.toXML(),e); handleException("Error occurred while sending the message : "+message.toXML(),e); }finally{ // if(xmppConnection != null && !msgCtx.isServerSide()){ // xmppConnection.disconnect(); // } } } /** * Process message requests that came in through chat clients * @param msgCtx * @throws AxisFault */ public static void processChatMessage(MessageContext msgCtx) throws AxisFault { Object obj = msgCtx.getProperty(XMPPConstants.MESSAGE_FROM_CHAT); if(obj != null){ String message = (String)obj; String response = ""; if(message.trim().startsWith("help")){ response = prepareHelpTextForChat(); }else if(message.trim().startsWith("listServices")){ response = prepareServicesList(msgCtx); }else if (message.trim().startsWith("getOperations")){ response = prepareOperationList(msgCtx,message); }else{ //TODO add support for more help commands } sendChatMessage(msgCtx,response); } } /** * Prepares a list of service names deployed in current runtime * @param msgCtx * @return */ private static String prepareOperationList(MessageContext msgCtx,String chatMessage) { StringBuffer sb = new StringBuffer(); //extract service name String serviceName = chatMessage.replace("getOperations", ""); serviceName = serviceName.replaceAll(" ", ""); if(log.isDebugEnabled()){ log.debug("Finding operations for service :"+ serviceName); } try { AxisService service = msgCtx.getConfigurationContext().getAxisConfiguration().getService(serviceName); Iterator itrOperations = service.getOperations(); int index = 1; while(itrOperations.hasNext()){ AxisOperation operation = (AxisOperation)itrOperations.next(); String parameterList = getParameterListForOperation(operation); sb.append(index +"."+operation.getName().getLocalPart()+"("+parameterList+")"+"\n"); index++; } } catch (AxisFault e) { log.error("Error occurred while retreiving AxisService : "+serviceName,e); sb.append("Error occurred while retrieving operations for service : "+serviceName); } return sb.toString(); } /** * Retrieves list of parameter names & their type for a given operation * @param operation */ private static String getParameterListForOperation(AxisOperation operation) { //Logic copied from BuilderUtil.buildsoapMessage(...) StringBuffer paramList = new StringBuffer(); AxisMessage axisMessage = operation.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE); XmlSchemaElement xmlSchemaElement = axisMessage.getSchemaElement(); if(xmlSchemaElement != null){ XmlSchemaType schemaType = xmlSchemaElement.getSchemaType(); if (schemaType instanceof XmlSchemaComplexType) { XmlSchemaComplexType complexType = ((XmlSchemaComplexType)schemaType); XmlSchemaParticle particle = complexType.getParticle(); if (particle instanceof XmlSchemaSequence || particle instanceof XmlSchemaAll) { if (particle instanceof XmlSchemaSequence) { XmlSchemaSequence sequence = (XmlSchemaSequence) particle; for (XmlSchemaSequenceMember member : sequence.getItems()) { XmlSchemaElement innerElement = (XmlSchemaElement) member; QName qName = innerElement.getQName(); if (qName == null && innerElement.getSchemaTypeName() .equals(org.apache.ws.commons.schema.constants.Constants.XSD_ANYTYPE)) { break; } String name = qName != null ? qName.getLocalPart() : innerElement.getName(); String type = innerElement.getSchemaTypeName().toString(); paramList.append(","+type +" " +name); } } else { XmlSchemaAll xmlSchemaAll = (XmlSchemaAll) particle; for (XmlSchemaAllMember member : xmlSchemaAll.getItems()) { XmlSchemaElement innerElement = (XmlSchemaElement) member; QName qName = innerElement.getQName(); if (qName == null && innerElement.getSchemaTypeName() .equals(org.apache.ws.commons.schema.constants.Constants.XSD_ANYTYPE)) { break; } String name = qName != null ? qName.getLocalPart() : innerElement.getName(); String type = innerElement.getSchemaTypeName().toString(); paramList.append("," + type + " " + name); } } } } } //remove first "," String list = paramList.toString(); return list.replaceFirst(",", ""); } /** * Prepares a list of service names deployed in current runtime * @param msgCtx * @return */ private static String prepareServicesList(MessageContext msgCtx) { Map services = msgCtx.getConfigurationContext().getAxisConfiguration().getServices(); StringBuffer sb = new StringBuffer(); if(services != null && services.size() > 0){ Iterator itrServiceNames = services.keySet().iterator(); int index = 1; while (itrServiceNames.hasNext()) { String serviceName = (String) itrServiceNames.next(); sb.append(index+"."+serviceName+"\n"); index++; } } return sb.toString(); } /** * Generate help text for chat client * @return {@link String} */ private static String prepareHelpTextForChat(){ StringBuffer helpText = new StringBuffer(); helpText.append("Following commands are supported :"+"\n"); helpText.append("-----------------------------------"+"\n"); helpText.append("1. listServices"+"\n"); helpText.append("2. getOperations <service-name>"+"\n"); helpText.append("3. call <service-name>:<operation>(<param1>,<param2>,...)"+"\n"); return helpText.toString(); } /** * Replies to IM clients via a chat message. The reply contains the invocation response as a string. * @param msgCtx * @param responseMsg * @throws AxisFault */ private static void sendChatMessage(MessageContext msgCtx,String responseMsg) throws AxisFault { XMPPConnection xmppConnection = null; XMPPOutTransportInfo xmppOutTransportInfo = null; Message message = new Message(); xmppOutTransportInfo = (XMPPOutTransportInfo)msgCtx.getProperty(Constants.OUT_TRANSPORT_INFO); if(xmppOutTransportInfo != null){ message.setProperty(XMPPConstants.IN_REPLY_TO, xmppOutTransportInfo.getInReplyTo()); xmppConnection = xmppOutTransportInfo.getConnectionFactory().getXmppConnection(); if(xmppConnection == null){ handleException("Connection to XMPP Server is not established."); } }else{ handleException("Could not find message sender details."); } //initialize the chat manager using connection ChatManager chatManager = xmppConnection.getChatManager(); Chat chat = chatManager.createChat(xmppOutTransportInfo.getDestinationAccount(), null); try{ message.setProperty(XMPPConstants.SEQUENCE_ID, xmppOutTransportInfo.getSequenceID()); message.setBody(responseMsg); chat.sendMessage(message); log.debug("Sent message :"+message.toXML()); } catch (XMPPException e) { XMPPSender.handleException("Error occurred while sending the message : "+message.toXML(),e); } } /** * Extract connection details from axis2.xml's transportsender section * @param serverCredentials * @param transportOut */ private void getConnectionDetailsFromAxisConfiguration(TransportOutDescription transportOut){ if(transportOut != null){ Parameter serverUrl = transportOut.getParameter(XMPPConstants.XMPP_SERVER_URL); if (serverUrl != null) { serverCredentials.setServerUrl(Utils.getParameterValue(serverUrl)); } Parameter userName = transportOut.getParameter(XMPPConstants.XMPP_SERVER_USERNAME); if (userName != null) { serverCredentials.setAccountName(Utils.getParameterValue(userName)); } Parameter password = transportOut.getParameter(XMPPConstants.XMPP_SERVER_PASSWORD); if (password != null) { serverCredentials.setPassword(Utils.getParameterValue(password)); } Parameter serverType = transportOut.getParameter(XMPPConstants.XMPP_SERVER_TYPE); if (serverType != null) { serverCredentials.setServerType(Utils.getParameterValue(serverType)); } Parameter domainName = transportOut.getParameter(XMPPConstants.XMPP_DOMAIN_NAME); if (serverUrl != null) { serverCredentials.setDomainName(Utils.getParameterValue(domainName)); } } } /** * Extract connection details from client options * @param serverCredentials * @param msgContext */ private void getConnectionDetailsFromClientOptions(MessageContext msgContext){ Options clientOptions = msgContext.getOptions(); if (clientOptions.getProperty(XMPPConstants.XMPP_SERVER_USERNAME) != null){ serverCredentials.setAccountName((String)clientOptions.getProperty(XMPPConstants.XMPP_SERVER_USERNAME)); } if (clientOptions.getProperty(XMPPConstants.XMPP_SERVER_PASSWORD) != null){ serverCredentials.setPassword((String)clientOptions.getProperty(XMPPConstants.XMPP_SERVER_PASSWORD)); } if (clientOptions.getProperty(XMPPConstants.XMPP_SERVER_URL) != null){ serverCredentials.setServerUrl((String)clientOptions.getProperty(XMPPConstants.XMPP_SERVER_URL)); } if (clientOptions.getProperty(XMPPConstants.XMPP_SERVER_TYPE) != null){ serverCredentials.setServerType((String)clientOptions.getProperty(XMPPConstants.XMPP_SERVER_TYPE)); } } private static void handleException(String msg, Exception e) throws AxisFault { log.error(msg, e); throw new AxisFault(msg, e); } private static void handleException(String msg) throws AxisFault { log.error(msg); throw new AxisFault(msg); } }