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 }