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.camel.component.jetty;
018    
019    import java.net.URI;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.camel.Endpoint;
025    import org.apache.camel.RuntimeCamelException;
026    import org.apache.camel.component.http.CamelServlet;
027    import org.apache.camel.component.http.HttpComponent;
028    import org.apache.camel.component.http.HttpConsumer;
029    import org.apache.camel.component.http.HttpEndpoint;
030    import org.apache.camel.util.CastUtils;
031    import org.apache.camel.util.IntrospectionSupport;
032    import org.apache.camel.util.URISupport;
033    import org.apache.camel.util.UnsafeUriCharactersEncoder;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.mortbay.component.LifeCycle;
037    import org.mortbay.jetty.Connector;
038    import org.mortbay.jetty.Handler;
039    import org.mortbay.jetty.Server;
040    import org.mortbay.jetty.client.Address;
041    import org.mortbay.jetty.client.HttpClient;
042    import org.mortbay.jetty.handler.ContextHandlerCollection;
043    import org.mortbay.jetty.nio.SelectChannelConnector;
044    import org.mortbay.jetty.security.SslSocketConnector;
045    import org.mortbay.jetty.servlet.Context;
046    import org.mortbay.jetty.servlet.ServletHolder;
047    import org.mortbay.jetty.servlet.SessionHandler;
048    import org.mortbay.thread.QueuedThreadPool;
049    import org.mortbay.thread.ThreadPool;
050    
051    /**
052     * An HttpComponent which starts an embedded Jetty for to handle consuming from
053     * the http endpoints.
054     *
055     * @version $Revision: 890042 $
056     */
057    public class JettyHttpComponent extends HttpComponent {
058        
059        protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>();
060       
061        private static final transient Log LOG = LogFactory.getLog(JettyHttpComponent.class);
062        private static final String JETTY_SSL_KEYSTORE = "jetty.ssl.keystore";
063        
064        protected String sslKeyPassword;
065        protected String sslPassword;
066        protected String sslKeystore;
067        protected Map<Integer, SslSocketConnector> sslSocketConnectors;
068        protected HttpClient httpClient;
069        protected ThreadPool httpClientThreadPool;
070        protected Integer httpClientMinThreads;
071        protected Integer httpClientMaxThreads;
072    
073        class ConnectorRef {
074            Server server;
075            Connector connector;
076            CamelServlet servlet;
077            int refCount;
078    
079            public ConnectorRef(Server server, Connector connector, CamelServlet servlet) {
080                this.server = server;
081                this.connector = connector;
082                this.servlet = servlet;
083                increment();
084            }
085    
086            public int increment() {
087                return ++refCount;
088            }
089    
090            public int decrement() {
091                return --refCount;
092            }
093        }
094        
095        
096        @Override
097        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
098            uri = uri.startsWith("jetty:") ? remaining : uri;
099    
100            List<Handler> handlerList = resolveAndRemoveReferenceListParameter(parameters, "handlers", Handler.class);
101            
102            // configure regular parameters
103            configureParameters(parameters);
104    
105            JettyHttpEndpoint result = new JettyHttpEndpoint(this, uri, null);
106            if (httpBinding != null) {
107                result.setBinding(httpBinding);
108            }
109            setEndpointHeaderFilterStrategy(result);
110            if (handlerList.size() > 0) {
111                result.setHandlers(handlerList);
112            }
113            setProperties(result, parameters);
114    
115            // configure http client if we have url configuration for it
116            if (IntrospectionSupport.hasProperties(parameters, "httpClient.")) {
117                // configure Jetty http client
118                result.setClient(getHttpClient());
119                // set additional parameters on http client
120                IntrospectionSupport.setProperties(getHttpClient(), parameters, "httpClient.");
121                // validate that we could resolve all httpClient. parameters as this component is lenient
122                validateParameters(uri, parameters, "httpClient.");
123            }
124    
125            // create the http uri after we have configured all the parameters on the camel objects
126            URI httpUri = URISupport.createRemainingURI(new URI(UnsafeUriCharactersEncoder.encode(uri)), 
127                    CastUtils.cast(parameters));
128            result.setHttpUri(httpUri);
129    
130            return result;
131        }
132    
133        /**
134         * Connects the URL specified on the endpoint to the specified processor.
135         */
136        @Override
137        public void connect(HttpConsumer consumer) throws Exception {
138            // Make sure that there is a connector for the requested endpoint.
139            JettyHttpEndpoint endpoint = (JettyHttpEndpoint)consumer.getEndpoint();
140            String connectorKey = getConnectorKey(endpoint);
141    
142            synchronized (CONNECTORS) {
143                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
144                if (connectorRef == null) {
145                    Connector connector;
146                    if ("https".equals(endpoint.getProtocol())) {
147                        connector = getSslSocketConnector(endpoint.getPort());
148                    } else {
149                        connector = new SelectChannelConnector();
150                    }
151                    connector.setPort(endpoint.getPort());
152                    connector.setHost(endpoint.getHttpUri().getHost());
153                    if ("localhost".equalsIgnoreCase(endpoint.getHttpUri().getHost())) {
154                        LOG.warn("You use localhost interface! It means that no external connections will be available. Don't you want to use 0.0.0.0 instead (all network interfaces)?");
155                    }
156                    Server server = createServer();
157                    server.addConnector(connector);
158    
159                    connectorRef = new ConnectorRef(server, connector, createServletForConnector(server, connector, endpoint.getHandlers()));
160                    connector.start();
161                    
162                    CONNECTORS.put(connectorKey, connectorRef);
163                    
164                } else {
165                    // ref track the connector
166                    connectorRef.increment();
167                }
168                // check the session support
169                if (endpoint.isSessionSupport()) {                
170                    enableSessionSupport(connectorRef.server);
171                }
172                connectorRef.servlet.connect(consumer);
173            }
174        }
175    
176        private void enableSessionSupport(Server server) throws Exception {
177            Context context = (Context)server.getChildHandlerByClass(Context.class);
178            if (context.getSessionHandler() == null) {
179                SessionHandler sessionHandler = new SessionHandler();
180                context.setSessionHandler(sessionHandler);
181                if (context.isStarted()) {
182                    // restart the context
183                    context.stop();
184                    context.start();
185                }
186            }
187    
188        }
189    
190        /**
191         * Disconnects the URL specified on the endpoint from the specified processor.
192         */
193        @Override
194        public void disconnect(HttpConsumer consumer) throws Exception {
195            // If the connector is not needed anymore then stop it
196            HttpEndpoint endpoint = consumer.getEndpoint();
197            String connectorKey = getConnectorKey(endpoint);
198            
199            synchronized (CONNECTORS) {
200                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
201                if (connectorRef != null) {
202                    connectorRef.servlet.disconnect(consumer);
203                    if (connectorRef.decrement() == 0) {
204                        connectorRef.server.removeConnector(connectorRef.connector);
205                        connectorRef.connector.stop();
206                        connectorRef.server.stop();
207                        CONNECTORS.remove(connectorKey);
208                    }
209                }
210            }
211        }
212        
213        private String getConnectorKey(HttpEndpoint endpoint) {
214            return endpoint.getProtocol() + ":" + endpoint.getHttpUri().getHost() + ":" + endpoint.getPort();
215        }
216    
217        // Properties
218        // -------------------------------------------------------------------------
219           
220        public String getSslKeyPassword() {
221            return sslKeyPassword;
222        }
223    
224        public void setSslKeyPassword(String sslKeyPassword) {
225            this.sslKeyPassword = sslKeyPassword;
226        }
227    
228        public String getSslPassword() {
229            return sslPassword;
230        }
231    
232        public void setSslPassword(String sslPassword) {
233            this.sslPassword = sslPassword;
234        }
235    
236        public void setKeystore(String sslKeystore) {
237            this.sslKeystore = sslKeystore;
238        }
239    
240        public String getKeystore() {
241            return sslKeystore;
242        }
243    
244        public SslSocketConnector getSslSocketConnector(int port) {
245            SslSocketConnector answer = null;
246            if (sslSocketConnectors != null) {
247                answer = sslSocketConnectors.get(port);
248            }
249            if (answer == null) {
250                answer = createSslSocketConnector();
251            } else {
252                // try the keystore system property as a backup, jetty doesn't seem
253                // to read this property anymore
254                String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
255                if (keystoreProperty != null) {
256                    answer.setKeystore(keystoreProperty);
257                }
258    
259            }
260            return answer;
261        }
262        
263        public SslSocketConnector createSslSocketConnector() {
264            SslSocketConnector answer = new SslSocketConnector();
265            // with default null values, jetty ssl system properties
266            // and console will be read by jetty implementation
267            answer.setPassword(sslPassword);
268            answer.setKeyPassword(sslKeyPassword);
269            if (sslKeystore != null) {
270                answer.setKeystore(sslKeystore);
271            } else {
272                // try the keystore system property as a backup, jetty doesn't seem
273                // to read this property anymore
274                String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
275                if (keystoreProperty != null) {
276                    answer.setKeystore(keystoreProperty);
277                }
278            }
279            
280            return answer;
281        }
282    
283        public void setSslSocketConnectors(Map <Integer, SslSocketConnector> connectors) {
284            sslSocketConnectors = connectors;
285        }
286    
287        public synchronized HttpClient getHttpClient() {
288            if (httpClient == null) {
289                httpClient = new HttpClient();
290                httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
291    
292                if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) {
293                    String host = System.getProperty("http.proxyHost");
294                    int port = Integer.parseInt(System.getProperty("http.proxyPort"));
295                    if (LOG.isDebugEnabled()) {
296                        LOG.debug("Java System Property http.proxyHost and http.proxyPort detected. Using http proxy host: "
297                                + host + " port: " + port);
298                    }
299                    httpClient.setProxy(new Address(host, port));
300                }
301    
302                // use QueueThreadPool as the default bounded is deprecated (see SMXCOMP-157)
303                if (getHttpClientThreadPool() == null) {
304                    QueuedThreadPool qtp = new QueuedThreadPool();
305                    if (httpClientMinThreads != null) {
306                        qtp.setMinThreads(httpClientMinThreads.intValue());
307                    }
308                    if (httpClientMaxThreads != null) {
309                        qtp.setMaxThreads(httpClientMaxThreads.intValue());
310                    }
311                    try {
312                        qtp.start();
313                    } catch (Exception e) {
314                        throw new RuntimeCamelException("Error starting JettyHttpClient thread pool: " + qtp, e);
315                    }
316                    setHttpClientThreadPool(qtp);
317                }
318                httpClient.setThreadPool(getHttpClientThreadPool());
319            }
320            return httpClient;
321        }
322    
323        public void setHttpClient(HttpClient httpClient) {
324            this.httpClient = httpClient;
325        }
326    
327        public ThreadPool getHttpClientThreadPool() {
328            return httpClientThreadPool;
329        }
330    
331        public void setHttpClientThreadPool(ThreadPool httpClientThreadPool) {
332            this.httpClientThreadPool = httpClientThreadPool;
333        }
334    
335        public Integer getHttpClientMinThreads() {
336            return httpClientMinThreads;
337        }
338    
339        public void setHttpClientMinThreads(Integer httpClientMinThreads) {
340            this.httpClientMinThreads = httpClientMinThreads;
341        }
342    
343        public Integer getHttpClientMaxThreads() {
344            return httpClientMaxThreads;
345        }
346    
347        public void setHttpClientMaxThreads(Integer httpClientMaxThreads) {
348            this.httpClientMaxThreads = httpClientMaxThreads;
349        }
350    
351        // Implementation methods
352        // -------------------------------------------------------------------------
353        protected CamelServlet createServletForConnector(Server server, Connector connector, List<Handler> handlers) throws Exception {
354            CamelServlet camelServlet = new CamelServlet();
355    
356            Context context = new Context(server, "/", Context.NO_SECURITY | Context.NO_SESSIONS);
357            context.setConnectorNames(new String[] {connector.getName()});
358    
359            if (handlers != null) {
360                for (Handler handler : handlers) {
361                    context.addHandler(handler);
362                }
363            }
364    
365            ServletHolder holder = new ServletHolder();
366            holder.setServlet(camelServlet);
367            context.addServlet(holder, "/*");
368            connector.start();
369            context.start();
370    
371            return camelServlet;
372        }
373        
374        protected Server createServer() throws Exception {
375            Server server = new Server();
376            ContextHandlerCollection collection = new ContextHandlerCollection();
377            collection.setServer(server);
378            server.addHandler(collection);
379            server.start();
380            return server;
381        }
382    
383        @Override
384        protected void doStart() throws Exception {
385            super.doStart();
386            if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
387                LifeCycle lc = (LifeCycle) httpClientThreadPool;
388                lc.start();
389            }
390            if (httpClient != null && !httpClient.isStarted()) {
391                httpClient.start();
392            }
393        }
394    
395        @Override
396        protected void doStop() throws Exception {
397            super.doStop();
398            if (httpClient != null) {
399                httpClient.stop();
400            }
401            if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
402                LifeCycle lc = (LifeCycle) httpClientThreadPool;
403                lc.stop();
404            }
405        }
406    }