001 /****************************************************************************** 002 * Copyright (C) MActor Developers. All rights reserved. * 003 * ---------------------------------------------------------------------------* 004 * This file is part of MActor. * 005 * * 006 * MActor is free software; you can redistribute it and/or modify * 007 * it under the terms of the GNU General Public License as published by * 008 * the Free Software Foundation; either version 2 of the License, or * 009 * (at your option) any later version. * 010 * * 011 * MActor is distributed in the hope that it will be useful, * 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 014 * GNU General Public License for more details. * 015 * * 016 * You should have received a copy of the GNU General Public License * 017 * along with MActor; if not, write to the Free Software * 018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * 019 ******************************************************************************/ 020 package org.mactor.brokers.soap; 021 022 import java.io.BufferedReader; 023 import java.io.ByteArrayOutputStream; 024 import java.io.File; 025 import java.io.IOException; 026 import java.io.InputStreamReader; 027 import java.io.OutputStream; 028 import java.io.OutputStreamWriter; 029 import java.net.HttpURLConnection; 030 import java.net.MalformedURLException; 031 import java.net.URL; 032 import java.nio.charset.Charset; 033 import java.util.Collections; 034 import java.util.HashMap; 035 import java.util.Map; 036 037 import org.dom4j.io.SAXReader; 038 import org.mactor.brokers.AbstractMessageBroker; 039 import org.mactor.brokers.Message; 040 import org.mactor.brokers.MessageBroker; 041 import org.mactor.brokers.http.HttpRequest; 042 import org.mactor.brokers.http.HttpRequestListener; 043 import org.mactor.brokers.http.HttpResponse; 044 import org.mactor.brokers.http.HttpServerManager; 045 import org.mactor.framework.ConfigException; 046 import org.mactor.framework.MactorException; 047 import org.mactor.framework.spec.ProjectContext; 048 import org.mactor.framework.spec.MessageBrokersConfig.MessageBrokerConfig; 049 import org.mactor.framework.spec.MessageBrokersConfig.MessageBrokerConfig.ChannelConfig; 050 051 /** 052 * A SOAP message broker that supports both incoming operations (MActor working 053 * as a SOAP-server) and outgoing operations (MActor working as a SOAP-client) 054 * 055 * </p> 056 * 057 * @author Lars Ivar Almli 058 * @see MessageBroker 059 */ 060 public class SoapMessageBroker extends AbstractMessageBroker { 061 public SoapMessageBroker(MessageBrokerConfig config) { 062 super(config); 063 } 064 public void publish(String channel, Message message) throws MactorException { 065 ChannelConfig cc = config.getRequieredChannelConfig(channel); 066 sendSoapMessage(cc.getRequieredValue("SoapEndpoint"), cc.getRequieredValue("SoapAction"), cc.getValue("Username"), cc.getValue("Password"), message); 067 } 068 public Message publishWithResponse(String channel, Message message) throws MactorException { 069 ChannelConfig cc = config.getRequieredChannelConfig(channel); 070 return sendSoapMessage(cc.getRequieredValue("SoapEndpoint"), cc.getRequieredValue("SoapAction"), cc.getValue("Username"), cc.getValue("Password"), message); 071 } 072 @Override 073 protected void onFirstSubscribe(String channel) throws MactorException { 074 // System.out.println("onFirstSubscribe"); 075 ChannelConfig cc = config.getRequieredChannelConfig(channel); 076 SoapEndpointListener epListener = getSoapEndpointListener(cc); 077 String action = cc.getRequieredValue("SoapAction"); 078 epListener.addActionBinding(action, channel); 079 } 080 @Override 081 protected void onLastSubscribe(String channel) throws MactorException { 082 // System.out.println("onLastSubscribe"); 083 ChannelConfig cc = config.getRequieredChannelConfig(channel); 084 SoapEndpointListener epListener = getSoapEndpointListener(cc); 085 String action = cc.getRequieredValue("SoapAction"); 086 epListener.removeActionBinding(action); 087 if (epListener.getActionBindingCount() == 0) { 088 endpointListenerMap.remove(epListener.endpointKey); 089 epListener.terminate(); 090 } 091 } 092 private SoapEndpointListener getSoapEndpointListener(ChannelConfig cc) throws MactorException { 093 try { 094 String endpoint = cc.getRequieredValue("SoapEndpoint"); 095 URL url = new URL(endpoint); 096 int port = url.getDefaultPort(); 097 if (url.getPort() > 0) 098 port = url.getPort(); 099 String real = url.getPath(); 100 if (url.getQuery() != null) 101 real = real + "?" + url.getQuery(); 102 real = real.toLowerCase(); 103 String key = port + "-" + real; 104 SoapEndpointListener l = endpointListenerMap.get(key); 105 if (l == null) { 106 l = new SoapEndpointListener(key, real, port, cc.getValue("WsdlFile")); 107 endpointListenerMap.put(key, l); 108 } 109 return l; 110 } catch (MalformedURLException e) { 111 throw new ConfigException(e); 112 } 113 } 114 public void terminate() { 115 super.terminate(); 116 for (SoapEndpointListener l : endpointListenerMap.values()) 117 l.terminate(); 118 } 119 private class WsdlProvider implements HttpRequestListener { 120 File w; 121 public WsdlProvider(File w) { 122 this.w = w; 123 } 124 public HttpResponse onRequest(HttpRequest request) throws Exception { 125 HttpResponse res = new HttpResponse(); 126 res.addHeader("Content-type", "text/xml"); 127 res.setData(new SAXReader().read(w)); 128 return res; 129 } 130 } 131 private Map<String, SoapEndpointListener> endpointListenerMap = Collections.synchronizedMap(new HashMap<String, SoapEndpointListener>()); 132 private class SoapEndpointListener implements HttpRequestListener { 133 private Map<String, String> actionToChannelMap = Collections.synchronizedMap(new HashMap<String, String>()); 134 String endpointKey; 135 String url; 136 int port; 137 public SoapEndpointListener(String endpointKey, String url, int port, String wsdlFileName) throws MactorException { 138 this.endpointKey = endpointKey; 139 this.port = port; 140 this.url = url; 141 HttpServerManager.getHttpServer(port).addRequestListener(url, this); 142 if (wsdlFileName != null) { 143 File f = new File(ProjectContext.getGlobalInstance().getProjectConfigDir().getAbsolutePath() + "/" + wsdlFileName); 144 if (f.exists()) { 145 WsdlProvider wsdlP = new WsdlProvider(f); 146 HttpServerManager.getHttpServer(port).addRequestListener(url + "?wsdl", wsdlP); 147 HttpServerManager.getHttpServer(port).addRequestListener(url + "?wsdl=", wsdlP); 148 } 149 } 150 } 151 public void terminate() { 152 log.info("Releasing endpoint '" + url + "'"); 153 removeListener(port, url); 154 removeListener(port, url + "?wsdl"); 155 removeListener(port, url + "?wsdl="); 156 } 157 private void removeListener(int port, String url) { 158 try { 159 HttpServerManager.getHttpServer(port).removeRequestListener(url); 160 } catch (MactorException me) { 161 log.warn("Failed to remove HTTP listener on port '" + port + "'. Error:" + me.getMessage(), me); 162 } 163 } 164 public int getActionBindingCount() { 165 return actionToChannelMap.size(); 166 } 167 public void removeActionBinding(String action) { 168 actionToChannelMap.remove(action.toLowerCase()); 169 } 170 public void addActionBinding(String action, String channel) { 171 actionToChannelMap.put(action.toLowerCase(), channel); 172 } 173 public HttpResponse onRequest(HttpRequest request) throws Exception { 174 Message m = Message.createMessage(request.getData()); 175 String action = trimAction(request.getHeader("soapaction")); 176 String channel = actionToChannelMap.get(action); 177 if (channel == null) { 178 log.info("No listeneres registered for action '" + action + "'"); 179 return null; 180 } 181 Message resultMessage = raiseOnMessage(channel, m, false); 182 if (resultMessage != null) { 183 HttpResponse res = new HttpResponse(); 184 res.setData(resultMessage.getContentDocument()); 185 res.addHeader("Content-Type", " text/xml; charset=" + Charset.defaultCharset().name()); 186 return res; 187 } 188 return null; 189 } 190 private String trimAction(String action) { 191 if (action == null || action.length() == 0) 192 return action; 193 if (action.startsWith("\"") || action.startsWith("'")) 194 action = action.substring(1); 195 if (action.endsWith("\"") || action.endsWith("'")) 196 action = action.substring(0, action.length() - 1); 197 return action.toLowerCase(); 198 } 199 } 200 private Message sendSoapMessage(String soapEndpoint, String soapAction, String username, String password, Message soapEnvelopeMessage) throws MactorException { 201 try { 202 URL url = new URL(soapEndpoint); 203 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 204 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 205 OutputStreamWriter w = new OutputStreamWriter(bos); 206 soapEnvelopeMessage.getContentDocument().write(w); 207 w.flush(); 208 byte[] contentBuffer = bos.toByteArray(); 209 conn.setRequestProperty("Content-Length", contentBuffer.length + ""); 210 conn.setRequestProperty("Content-Type", " text/xml; charset=" + Charset.defaultCharset().name()); 211 conn.setRequestProperty("SOAPAction", soapAction); 212 if (username != null && username.length() != 0) 213 conn.setRequestProperty("Authorization", "Basic " + new sun.misc.BASE64Encoder().encode((username + ":" + password).getBytes())); 214 conn.setRequestMethod("POST"); 215 conn.setDoOutput(true); 216 conn.setDoInput(true); 217 OutputStream out = conn.getOutputStream(); 218 out.write(contentBuffer); 219 out.flush(); 220 out.close(); 221 int rc = conn.getResponseCode(); 222 if (rc != 200) 223 throw new MactorException("Server rejected the soap message. Endpoint'" + soapEndpoint + " '. Action '" + soapAction + "' HTTP response code: " + rc); 224 BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); 225 Message response = Message.createMessage(in); 226 in.close(); 227 return response; 228 } catch (IOException ioe) { 229 // TODO more specific error description 230 throw new MactorException("Failed to send soap message " + ioe.getMessage(), ioe); 231 } 232 } 233 }