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    }