001    /* 
002     * JBoss, Home of Professional Open Source 
003     * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors
004     * as indicated by the @author tags. All rights reserved. 
005     * See the copyright.txt in the distribution for a 
006     * full listing of individual contributors.
007     *
008     * This copyrighted material is made available to anyone wishing to use, 
009     * modify, copy, or redistribute it subject to the terms and conditions 
010     * of the GNU Lesser General Public License, v. 2.1. 
011     * This program is distributed in the hope that it will be useful, but WITHOUT A 
012     * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
013     * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details. 
014     * You should have received a copy of the GNU Lesser General Public License, 
015     * v.2.1 along with this distribution; if not, write to the Free Software 
016     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
017     * MA  02110-1301, USA.
018     */
019    package org.switchyard.remote.http;
020    
021    import java.io.OutputStream;
022    import java.net.HttpURLConnection;
023    import java.net.MalformedURLException;
024    import java.net.URL;
025    
026    import org.apache.log4j.Logger;
027    import org.switchyard.Context;
028    import org.switchyard.Exchange;
029    import org.switchyard.ExchangePattern;
030    import org.switchyard.Message;
031    import org.switchyard.Property;
032    import org.switchyard.Scope;
033    import org.switchyard.internal.DefaultContext;
034    import org.switchyard.remote.RemoteInvoker;
035    import org.switchyard.remote.RemoteMessage;
036    import org.switchyard.serial.FormatType;
037    import org.switchyard.serial.Serializer;
038    import org.switchyard.serial.SerializerFactory;
039    import org.switchyard.transform.TransformSequence;
040    
041    /**
042     * Remote service invoker which uses HTTP as a transport.
043     */
044    public class HttpInvoker implements RemoteInvoker {
045        
046        /**
047         * HTTP header used to communicate the domain name for a service invocation.
048         */
049        public static final String SERVICE_HEADER = "switchyard-service";
050    
051        private static Logger _log = Logger.getLogger(HttpInvoker.class);
052        private Serializer _serializer = SerializerFactory.create(FormatType.JSON, null, true);
053        private URL _endpoint;
054        
055        /**
056         * Create a new HttpInvoker from the specified URL string.
057         * @param endpoint url string
058         */
059        public HttpInvoker(String endpoint) {
060            try {
061                _endpoint = new URL(endpoint);
062            } catch (MalformedURLException badURL) {
063                throw new IllegalArgumentException(
064                        "Invalid URL for remote endpoint: " + endpoint, badURL);
065            }
066        }
067        
068        /**
069         * Create a new HttpInvoker with the specified URL.
070         * @param endpoint the endpoint URL
071         */
072        public HttpInvoker(URL endpoint) {
073            _endpoint = endpoint;
074        }
075    
076        @Override
077        public void invoke(Exchange exchange) {
078            RemoteMessage request = new RemoteMessage()
079            .setDomain(exchange.getProvider().getDomain().getName())
080            .setService(exchange.getProvider().getName())
081            .setContent(exchange.getMessage().getContent())
082            .setContext(exchange.getContext());
083            
084            try {
085                RemoteMessage reply = invoke(request);
086                if (isInOut(exchange) && reply != null) {
087                    Message msg = exchange.getMessage().setContent(reply.getContent());
088                    if (reply.isFault()) {
089                        exchange.sendFault(msg);
090                    } else {
091                        exchange.send(msg);
092                    }
093                }
094            } catch (java.io.IOException ioEx) {
095                ioEx.printStackTrace();
096                exchange.sendFault(exchange.createMessage().setContent(ioEx));
097            }
098        }
099        
100        @Override
101        public RemoteMessage invoke(RemoteMessage request) throws java.io.IOException {
102            RemoteMessage reply = null;
103            HttpURLConnection conn = null;
104            
105            if (_log.isDebugEnabled()) {
106                _log.debug("Invoking " + request.getService() + " at endpoint " + _endpoint.toString());
107            }
108            
109            // Initialize HTTP connection
110            conn = (HttpURLConnection)_endpoint.openConnection();
111            conn.setDoOutput(true);
112            conn.addRequestProperty(SERVICE_HEADER, request.getService().toString());
113            conn.connect();
114            OutputStream os = conn.getOutputStream();
115            
116            // Sanitize context properties
117            Context ctx = cloneContext(request.getContext());
118            request.setContext(ctx);
119            
120            // Write the request message
121            _serializer.serialize(request, RemoteMessage.class, os);
122            os.flush();
123            os.close();
124            
125            // Check for response and process accordingly
126            if (conn.getResponseCode() == 200) {
127                if (_log.isDebugEnabled()) {
128                    _log.debug("Processing reply for service " + request.getService());
129                }
130                reply = _serializer.deserialize(conn.getInputStream(), RemoteMessage.class);
131            }
132            
133            return reply;
134        }
135        
136        // Remove troublesome context properties from remote message.
137        private Context cloneContext(Context context) {
138            Context newCtx = new DefaultContext();
139            // return empty context if context to clone is null
140            if (context == null) {
141                return newCtx;
142            }
143            
144            newCtx.setProperties(context.getProperties());
145            Property inTransform = newCtx.getProperty(TransformSequence.class.getName(), Scope.IN);
146            Property outTransform = newCtx.getProperty(TransformSequence.class.getName(), Scope.OUT);
147            if (inTransform != null) {
148                newCtx.removeProperty(inTransform);
149            }
150            if (outTransform != null) {
151                newCtx.removeProperty(outTransform);
152            }
153            
154            return newCtx;
155        }
156        
157        private boolean isInOut(Exchange exchange) {
158            return ExchangePattern.IN_OUT.equals(
159                    exchange.getContract().getConsumerOperation().getExchangePattern());
160        }
161    }