001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.http.endpoints;
018    
019    import java.io.IOException;
020    import java.io.InputStream;
021    import java.net.InetSocketAddress;
022    import java.security.GeneralSecurityException;
023    import java.security.KeyStore;
024    import java.security.SecureRandom;
025    
026    import javax.jbi.messaging.ExchangeStatus;
027    import javax.jbi.messaging.MessageExchange;
028    import javax.jbi.messaging.NormalizedMessage;
029    import javax.jbi.servicedesc.ServiceEndpoint;
030    import javax.jbi.management.DeploymentException;
031    import javax.xml.namespace.QName;
032    import javax.net.ssl.SSLContext;
033    import javax.net.ssl.KeyManager;
034    import javax.net.ssl.KeyManagerFactory;
035    import javax.net.ssl.TrustManager;
036    import javax.net.ssl.TrustManagerFactory;
037    
038    import org.apache.servicemix.common.JbiConstants;
039    import org.apache.servicemix.common.DefaultComponent;
040    import org.apache.servicemix.common.ServiceUnit;
041    import org.apache.servicemix.common.security.KeystoreManager;
042    import org.apache.servicemix.common.endpoints.ProviderEndpoint;
043    import org.apache.servicemix.http.HttpComponent;
044    import org.apache.servicemix.http.HttpEndpointType;
045    import org.apache.servicemix.http.HttpConfiguration;
046    import org.apache.servicemix.http.SslParameters;
047    import org.apache.servicemix.http.jetty.SmxHttpExchange;
048    import org.mortbay.jetty.client.HttpClient;
049    import org.mortbay.jetty.client.security.ProxyAuthorization;
050    import org.mortbay.thread.QueuedThreadPool;
051    import org.mortbay.resource.Resource;
052    
053    /**
054     * A plain HTTP provider. This type of endpoint can be used to send non-SOAP requests to HTTP endpoints.
055     * 
056     * @author gnodet
057     * @since 3.2
058     * @org.apache.xbean.XBean element="provider"
059     */
060    public class HttpProviderEndpoint extends ProviderEndpoint implements HttpEndpointType {
061    
062        // private BasicAuthCredentials basicAuthentication;
063        private HttpProviderMarshaler marshaler;
064        private String locationURI;
065        private int clientSoTimeout = 60000;
066        private HttpClient jettyClient;
067        private boolean ownClient = false;
068    
069        private String proxyHost;
070        private int proxyPort = 80;
071        private String proxyUsername;
072        private String proxyPassword;
073    
074        private SslParameters ssl;
075    
076        public HttpProviderEndpoint() {
077            super();
078        }
079    
080        public HttpProviderEndpoint(DefaultComponent component, ServiceEndpoint endpoint) {
081            super(component, endpoint);
082        }
083    
084        public HttpProviderEndpoint(ServiceUnit serviceUnit, QName service, String endpoint) {
085            super(serviceUnit, service, endpoint);
086        }
087    
088        /**
089         * Returns the URI to which the endpoint sends requests.
090         * 
091         * @return a string representing the URI to which requests are sent
092         */
093        public String getLocationURI() {
094            return locationURI;
095        }
096    
097        /**
098         * Sets the URI to which an endpoint sends requests.
099         * 
100         * @param locationURI a string representing the URI
101         * @org.apache.xbean.Property description="the URI to which the endpoint sends requests"
102         */
103        public void setLocationURI(String locationURI) {
104            this.locationURI = locationURI;
105        }
106    
107        /**
108         * @return the marshaler
109         */
110        public HttpProviderMarshaler getMarshaler() {
111            return marshaler;
112        }
113    
114        /**
115         * Sets the class used to marshal messages.
116         * 
117         * @param marshaler the marshaler to set
118         * @org.apache.xbean.Property description="the bean used to marshal HTTP messages. The default is a
119         *                            <code>DefaultHttpProviderMarshaler</code>."
120         */
121        public void setMarshaler(HttpProviderMarshaler marshaler) {
122            this.marshaler = marshaler;
123        }
124    
125        public String getProxyHost() {
126            return proxyHost;
127        }
128    
129        /**
130         * Sets the host name of the HTTP proxy used
131         *
132         * @param proxyHost the host name of the HTTP proxy
133         * @org.apache.xbean.Property description="the host name of the HTTP proxy"
134         */
135        public void setProxyHost(String proxyHost) {
136            this.proxyHost = proxyHost;
137        }
138    
139        public int getProxyPort() {
140            return proxyPort;
141        }
142    
143        /**
144         * Sets the host port of the HTTP proxy used (defaults to 80)
145         *
146         * @param proxyPort the host name of the HTTP proxy
147         * @org.apache.xbean.Property description="the host port of the HTTP proxy (defaults to 80)"
148         */
149        public void setProxyPort(int proxyPort) {
150            this.proxyPort = proxyPort;
151        }
152    
153        public String getProxyUsername() {
154            return proxyUsername;
155        }
156    
157        /**
158         * Sets the user name for the HTTP proxy authentication
159         *
160         * @param proxyUsername the user name for the HTTP proxy authentication
161         * @org.apache.xbean.Property description="the user name for the HTTP proxy authentication"
162         */
163        public void setProxyUsername(String proxyUsername) {
164            this.proxyUsername = proxyUsername;
165        }
166    
167        public String getProxyPassword() {
168            return proxyPassword;
169        }
170    
171        /**
172         * Sets the password for the HTTP proxy authentication
173         *
174         * @param proxyPassword the password for the HTTP proxy authentication
175         * @org.apache.xbean.Property description="the password for the HTTP proxy authentication"
176         */
177        public void setProxyPassword(String proxyPassword) {
178            this.proxyPassword = proxyPassword;
179        }
180    
181        public SslParameters getSsl() {
182            return ssl;
183        }
184    
185        /**
186         * Sets the SSL parameters
187         *
188         * @param ssl the SSL parameters
189         * @org.apache.xbean.Property description="the SSL parameters"
190         */
191        public void setSsl(SslParameters ssl) {
192            this.ssl = ssl;
193        }
194    
195        public void process(MessageExchange exchange) throws Exception {
196            if (exchange.getStatus() == ExchangeStatus.ACTIVE) {
197                NormalizedMessage nm = exchange.getMessage("in");
198                if (nm == null) {
199                    throw new IllegalStateException("Exchange has no input message");
200                }
201                SmxHttpExchange httpEx = new Exchange(exchange);
202                marshaler.createRequest(exchange, nm, httpEx);
203                getConnectionPool().send(httpEx);
204            }
205        }
206    
207    
208        public void stop() throws Exception {
209            if (ownClient && jettyClient != null) {
210                jettyClient.stop();
211                jettyClient = null;
212            }
213            super.stop();
214        }
215    
216        protected void handle(SmxHttpExchange httpExchange, MessageExchange exchange) throws IOException {
217            try {
218                marshaler.handleResponse(exchange, httpExchange);
219            } catch (Exception e) {
220                exchange.setError(e);
221            }
222            try {
223                boolean txSync = exchange.getStatus() == ExchangeStatus.ACTIVE && exchange.isTransacted()
224                                 && Boolean.TRUE.equals(exchange.getProperty(JbiConstants.SEND_SYNC));
225                if (txSync) {
226                    sendSync(exchange);
227                } else {
228                    send(exchange);
229                }
230            } catch (Exception e) {
231                throw (IOException) new IOException(e.getMessage()).initCause(e);
232            }
233        }
234    
235        protected void handleException(SmxHttpExchange httpExchange, MessageExchange exchange, Throwable ex)  {
236            try {
237                marshaler.handleException(exchange, httpExchange, ex);
238                boolean txSync = exchange.getStatus() == ExchangeStatus.ACTIVE
239                                 && exchange.isTransacted()
240                                && Boolean.TRUE.equals(exchange.getProperty(JbiConstants.SEND_SYNC));
241                if (txSync) {
242                    sendSync(exchange);
243                } else {
244                    send(exchange);
245                }
246            } catch (Exception e) {
247                logger.warn("Unable to send back exchange in error", e);
248            }
249        }
250    
251        protected org.mortbay.jetty.client.HttpClient getConnectionPool() throws Exception {
252            if (jettyClient == null) {
253                HttpComponent comp = (HttpComponent) getServiceUnit().getComponent();
254                if (comp.getConfiguration().isJettyClientPerProvider() || proxyHost != null || ssl != null) {
255                    ownClient = true;
256                    jettyClient = new SSLManagedHttpClient();
257                    jettyClient.setThreadPool(new QueuedThreadPool(getConfiguration().getJettyClientThreadPoolSize()));
258                    jettyClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
259                    if (proxyHost != null) {
260                        jettyClient.setProxy(new InetSocketAddress(proxyHost, proxyPort));
261                        if (proxyUsername != null) {
262                            jettyClient.setProxyAuthentication(new ProxyAuthorization(proxyUsername, proxyPassword));
263                        }
264                    }
265                    jettyClient.setSoTimeout(getClientSoTimeout());
266                    jettyClient.start();
267                } else {
268                    ownClient = false;
269                    // return shared client
270                    jettyClient = comp.getConnectionPool();
271                }
272            }
273            if (!ownClient) {
274                // Always reset the SO timeout, in case the client is shared
275                jettyClient.setSoTimeout(getClientSoTimeout());
276            }
277            return jettyClient;
278        }
279    
280        public int getClientSoTimeout() {
281            return clientSoTimeout;
282        }
283    
284        /**
285         * Sets the number of milliseconds the endpoint will block while attempting to read a request. The default value is 60000.
286         * Setting this to 0 specifies that the endpoint will never timeout.
287         * 
288         * @param clientTimeout an int specifying the number of milliseconds the socket will block while attempting to read a request
289         * @org.apache.xbean.Property description="the number of milliseconds the endpoint will block while attempting to read a request. The default value is 60000. Setting this to 0 specifies that the endpoint will never timeout."
290         */
291        public void setClientSoTimeout(int clientTimeout) {
292            this.clientSoTimeout = clientTimeout;
293        }
294    
295        public void validate() throws DeploymentException {
296            super.validate();
297            if (marshaler == null) {
298                marshaler = new DefaultHttpProviderMarshaler();
299            }
300            if (marshaler instanceof DefaultHttpProviderMarshaler && locationURI != null) {
301                ((DefaultHttpProviderMarshaler)marshaler).setLocationURI(locationURI);
302            }
303        }
304    
305        private HttpConfiguration getConfiguration() {
306            return ((HttpComponent) getServiceUnit().getComponent()).getConfiguration();
307        }
308    
309        protected class Exchange extends SmxHttpExchange {
310            MessageExchange jbiExchange;
311    
312            public Exchange(MessageExchange jbiExchange) {
313                this.jbiExchange = jbiExchange;
314            }
315    
316            protected void onResponseComplete() throws IOException {
317                handle(this, jbiExchange);
318            }
319    
320            protected void onConnectionFailed(Throwable throwable) {
321                handleException(this, jbiExchange, throwable);
322            }
323    
324            protected void onException(Throwable throwable) {
325                handleException(this, jbiExchange, throwable);
326            }
327        }
328    
329        protected class SSLManagedHttpClient extends HttpClient {
330    
331            protected SSLContext getSSLContext() throws IOException {
332                if (ssl.getKeyStore() != null) {
333                    return getStrictSSLContext();
334                } else {
335                    return getLooseSSLContext();
336                }
337            }
338    
339            protected SSLContext getStrictSSLContext() throws IOException {
340                try {
341                    if (ssl.isManaged()) {
342                        KeystoreManager keystoreMgr = KeystoreManager.Proxy.create(getConfiguration().getKeystoreManager());
343                        return keystoreMgr.createSSLContext(ssl.getProvider(), ssl.getProtocol(),
344                                                            ssl.getKeyManagerFactoryAlgorithm(), ssl.getKeyStore(),
345                                                            ssl.getKeyAlias(), ssl.getTrustStore());
346                    } else {
347                        if (ssl.getTrustStore() == null) {
348                            ssl.setTrustStore(ssl.getKeyStore());
349                            ssl.setTrustStoreType(ssl.getKeyStoreType());
350                            ssl.setTrustManagerFactoryAlgorithm(ssl.getKeyManagerFactoryAlgorithm());
351                        }
352    
353                        KeyManager[] keyManagers;
354                        TrustManager[] trustManagers;
355    
356                        InputStream keystoreInputStream = Resource.newResource(ssl.getKeyStore()).getInputStream();
357                        KeyStore keyStore = KeyStore.getInstance(ssl.getKeyStoreType());
358                        keyStore.load(keystoreInputStream, ssl.getKeyStorePassword() == null ? null : ssl.getKeyStorePassword().toString().toCharArray());
359    
360                        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(ssl.getKeyManagerFactoryAlgorithm());
361                        keyManagerFactory.init(keyStore, ssl.getKeyPassword() == null ? null : ssl.getKeyPassword().toString().toCharArray());
362                        keyManagers = keyManagerFactory.getKeyManagers();
363    
364                        InputStream truststoreInputStream = Resource.newResource(ssl.getTrustStore()).getInputStream();
365                        KeyStore trustStore = KeyStore.getInstance(ssl.getTrustStoreType());
366                        trustStore.load(truststoreInputStream, ssl.getTrustStorePassword() == null ? null : ssl.getTrustStorePassword().toString().toCharArray());
367    
368                        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(ssl.getTrustManagerFactoryAlgorithm());
369                        trustManagerFactory.init(trustStore);
370                        trustManagers = trustManagerFactory.getTrustManagers();
371    
372                        SSLContext context = ssl.getProvider() == null
373                                                        ? SSLContext.getInstance(ssl.getProtocol())
374                                                        : SSLContext.getInstance(ssl.getProtocol(), ssl.getProvider());
375                        context.init(keyManagers, trustManagers, new SecureRandom());
376                        return context;
377                    }
378                } catch (GeneralSecurityException e) {
379                    throw (IOException) new IOException("Unable to create SSL context").initCause(e);
380                }
381            }
382    
383        }
384    
385    }