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