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.io.File;
020    import java.net.URI;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import javax.management.MBeanServer;
026    import javax.servlet.Filter;
027    
028    import org.apache.camel.CamelContext;
029    import org.apache.camel.Endpoint;
030    import org.apache.camel.RuntimeCamelException;
031    import org.apache.camel.component.http.CamelServlet;
032    import org.apache.camel.component.http.HttpBinding;
033    import org.apache.camel.component.http.HttpComponent;
034    import org.apache.camel.component.http.HttpConsumer;
035    import org.apache.camel.component.http.HttpEndpoint;
036    import org.apache.camel.spi.ManagementAgent;
037    import org.apache.camel.spi.ManagementStrategy;
038    import org.apache.camel.util.CastUtils;
039    import org.apache.camel.util.IntrospectionSupport;
040    import org.apache.camel.util.ObjectHelper;
041    import org.apache.camel.util.URISupport;
042    import org.eclipse.jetty.client.Address;
043    import org.eclipse.jetty.client.HttpClient;
044    import org.eclipse.jetty.jmx.MBeanContainer;
045    import org.eclipse.jetty.server.Connector;
046    import org.eclipse.jetty.server.Handler;
047    import org.eclipse.jetty.server.Server;
048    import org.eclipse.jetty.server.handler.ContextHandlerCollection;
049    import org.eclipse.jetty.server.handler.HandlerCollection;
050    import org.eclipse.jetty.server.handler.HandlerWrapper;
051    import org.eclipse.jetty.server.nio.SelectChannelConnector;
052    import org.eclipse.jetty.server.session.SessionHandler;
053    import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
054    import org.eclipse.jetty.servlet.FilterHolder;
055    import org.eclipse.jetty.servlet.ServletContextHandler;
056    import org.eclipse.jetty.servlet.ServletHolder;
057    import org.eclipse.jetty.servlets.MultiPartFilter;
058    import org.eclipse.jetty.util.component.LifeCycle;
059    import org.eclipse.jetty.util.thread.QueuedThreadPool;
060    import org.eclipse.jetty.util.thread.ThreadPool;
061    import org.slf4j.Logger;
062    import org.slf4j.LoggerFactory;
063    
064    /**
065     * An HttpComponent which starts an embedded Jetty for to handle consuming from
066     * the http endpoints.
067     *
068     * @version 
069     */
070    public class JettyHttpComponent extends HttpComponent {
071        public static final String TMP_DIR = "CamelJettyTempDir";
072        
073        protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>();
074       
075        private static final transient Logger LOG = LoggerFactory.getLogger(JettyHttpComponent.class);
076        private static final String JETTY_SSL_KEYSTORE = "org.eclipse.jetty.ssl.keystore";
077        private static final String JETTY_SSL_KEYPASSWORD = "org.eclipse.jetty.ssl.keypassword";
078        private static final String JETTY_SSL_PASSWORD = "org.eclipse.jetty.ssl.password";
079    
080        protected String sslKeyPassword;
081        protected String sslPassword;
082        protected String sslKeystore;
083        protected Map<Integer, SslSelectChannelConnector> sslSocketConnectors;
084        protected Map<Integer, SelectChannelConnector> socketConnectors;
085        protected Map<String, Object> sslSocketConnectorProperties;
086        protected Map<String, Object> socketConnectorProperties;
087        protected HttpClient httpClient;
088        protected ThreadPool httpClientThreadPool;
089        protected Integer httpClientMinThreads;
090        protected Integer httpClientMaxThreads;
091        protected Integer minThreads;
092        protected Integer maxThreads;
093        protected ThreadPool threadPool;
094        protected MBeanContainer mbContainer;
095        protected boolean enableJmx;
096        protected JettyHttpBinding jettyHttpBinding;
097        protected Long continuationTimeout;
098        protected boolean useContinuation = true;
099    
100        class ConnectorRef {
101            Server server;
102            Connector connector;
103            CamelServlet servlet;
104            int refCount;
105    
106            public ConnectorRef(Server server, Connector connector, CamelServlet servlet) {
107                this.server = server;
108                this.connector = connector;
109                this.servlet = servlet;
110                increment();
111            }
112    
113            public int increment() {
114                return ++refCount;
115            }
116    
117            public int decrement() {
118                return --refCount;
119            }
120            
121            public int getRefCount() {
122                return refCount;
123            }
124        }
125    
126        @Override
127        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
128            String addressUri = uri.startsWith("jetty:") ? remaining : uri;
129            Map<String, Object> httpClientParameters = new HashMap<String, Object>(parameters);
130            
131            // must extract well known parameters before we create the endpoint
132            List<Handler> handlerList = resolveAndRemoveReferenceListParameter(parameters, "handlers", Handler.class);
133            HttpBinding binding = resolveAndRemoveReferenceParameter(parameters, "httpBindingRef", HttpBinding.class);
134            JettyHttpBinding jettyBinding = resolveAndRemoveReferenceParameter(parameters, "jettyHttpBindingRef", JettyHttpBinding.class);
135            Boolean throwExceptionOnFailure = getAndRemoveParameter(parameters, "throwExceptionOnFailure", Boolean.class);
136            Boolean transferException = getAndRemoveParameter(parameters, "transferException", Boolean.class);
137            Boolean bridgeEndpoint = getAndRemoveParameter(parameters, "bridgeEndpoint", Boolean.class);
138            Boolean matchOnUriPrefix = getAndRemoveParameter(parameters, "matchOnUriPrefix", Boolean.class);
139            Boolean enableJmx = getAndRemoveParameter(parameters, "enableJmx", Boolean.class);
140            Boolean enableMultipartFilter = getAndRemoveParameter(parameters, "enableMultipartFilter",
141                                                                  Boolean.class, true);
142            Filter multipartFilter = resolveAndRemoveReferenceParameter(parameters, "multipartFilterRef", Filter.class);
143            Long continuationTimeout = getAndRemoveParameter(parameters, "continuationTimeout", Long.class);
144            Boolean useContinuation = getAndRemoveParameter(parameters, "useContinuation", Boolean.class);
145    
146            // configure http client if we have url configuration for it
147            // http client is only used for jetty http producer (hence not very commonly used)
148            HttpClient client = null;
149            if (IntrospectionSupport.hasProperties(parameters, "httpClient.")) {
150                // set additional parameters on http client
151                // only create client when needed
152                client = getHttpClient();
153                IntrospectionSupport.setProperties(client, parameters, "httpClient.");
154                // validate that we could resolve all httpClient. parameters as this component is lenient
155                validateParameters(uri, parameters, "httpClient.");
156            }
157            // keep the configure parameters for the http client
158            for (String key : parameters.keySet()) {
159                httpClientParameters.remove(key);
160            }
161            URI endpointUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(httpClientParameters));
162            
163            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
164            URI httpUri = URISupport.createRemainingURI(new URI(addressUri), CastUtils.cast(parameters));
165         
166            // create endpoint after all known parameters have been extracted from parameters
167            JettyHttpEndpoint endpoint = new JettyHttpEndpoint(this, endpointUri.toString(), httpUri);
168            setEndpointHeaderFilterStrategy(endpoint);
169    
170            if (client != null) {
171                endpoint.setClient(client);
172            }
173            if (handlerList.size() > 0) {
174                endpoint.setHandlers(handlerList);
175            }
176            // prefer to use endpoint configured over component configured
177            if (binding == null) {
178                // fallback to component configured
179                binding = getHttpBinding();
180            }
181            if (binding != null) {
182                endpoint.setBinding(binding);
183            }
184            // prefer to use endpoint configured over component configured
185            if (jettyBinding == null) {
186                // fallback to component configured
187                jettyBinding = getJettyHttpBinding();
188            }
189            if (jettyBinding != null) {
190                endpoint.setJettyBinding(jettyBinding);
191            }
192            // should we use an exception for failed error codes?
193            if (throwExceptionOnFailure != null) {
194                endpoint.setThrowExceptionOnFailure(throwExceptionOnFailure);
195            }
196            // should we transfer exception as serialized object
197            if (transferException != null) {
198                endpoint.setTransferException(transferException);
199            }
200            if (bridgeEndpoint != null) {
201                endpoint.setBridgeEndpoint(bridgeEndpoint);
202            }
203            if (matchOnUriPrefix != null) {
204                endpoint.setMatchOnUriPrefix(matchOnUriPrefix);
205            }
206            
207            if (enableJmx != null) {
208                endpoint.setEnableJmx(enableJmx);
209            } else { 
210                // set this option based on setting of JettyHttpComponent
211                endpoint.setEnableJmx(isEnableJmx());
212            }
213            
214            endpoint.setEnableMultipartFilter(enableMultipartFilter);
215            
216            if (multipartFilter != null) {
217                endpoint.setMultipartFilter(multipartFilter);
218                endpoint.setEnableMultipartFilter(true);
219            }
220    
221            if (continuationTimeout != null) {
222                endpoint.setContinuationTimeout(continuationTimeout);
223            }
224            if (useContinuation != null) {
225                endpoint.setUseContinuation(useContinuation);
226            }
227    
228            setProperties(endpoint, parameters);
229            return endpoint;
230        }
231    
232        /**
233         * Connects the URL specified on the endpoint to the specified processor.
234         */
235        @Override
236        public void connect(HttpConsumer consumer) throws Exception {
237            // Make sure that there is a connector for the requested endpoint.
238            JettyHttpEndpoint endpoint = (JettyHttpEndpoint)consumer.getEndpoint();
239            String connectorKey = getConnectorKey(endpoint);
240    
241            synchronized (CONNECTORS) {
242                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
243                if (connectorRef == null) {
244                    Connector connector;
245                    if ("https".equals(endpoint.getProtocol())) {
246                        connector = getSslSocketConnector(endpoint.getPort());
247                    } else {
248                        connector = getSocketConnector(endpoint.getPort());
249                    }
250                    connector.setPort(endpoint.getPort());
251                    connector.setHost(endpoint.getHttpUri().getHost());
252                    if ("localhost".equalsIgnoreCase(endpoint.getHttpUri().getHost())) {
253                        LOG.warn("You use localhost interface! It means that no external connections will be available."
254                                + " Don't you want to use 0.0.0.0 instead (all network interfaces)? " + endpoint);
255                    }
256                    Server server = createServer();
257                    if (endpoint.isEnableJmx()) {
258                        enableJmx(server);
259                    }
260                    server.addConnector(connector);
261    
262                    connectorRef = new ConnectorRef(server, connector, createServletForConnector(server, connector, endpoint.getHandlers(), endpoint));
263                    // must enable session before we start
264                    if (endpoint.isSessionSupport()) {
265                        enableSessionSupport(connectorRef.server, connectorKey);
266                    }
267                    connectorRef.server.start();
268                    
269                    CONNECTORS.put(connectorKey, connectorRef);
270                    
271                } else {
272                    // ref track the connector
273                    connectorRef.increment();
274                }
275                // check the session support
276                if (endpoint.isSessionSupport()) {
277                    enableSessionSupport(connectorRef.server, connectorKey);
278                }
279                
280                if (endpoint.isEnableMultipartFilter()) {
281                    enableMultipartFilter(endpoint, connectorRef.server, connectorKey);
282                }
283                connectorRef.servlet.connect(consumer);
284            }
285        }
286        
287        private void enableJmx(Server server) {
288            MBeanContainer containerToRegister = getMbContainer();
289            if (containerToRegister != null) {
290                LOG.info("Jetty JMX Extensions is enabled");
291                server.getContainer().addEventListener(containerToRegister);
292                // Since we may have many Servers running, don't tie the MBeanContainer
293                // to a Server lifecycle or we end up closing it while it is still in use.
294                //server.addBean(mbContainer);
295            }
296        }
297    
298        private void enableSessionSupport(Server server, String connectorKey) throws Exception {
299            ServletContextHandler context = (ServletContextHandler)server.getChildHandlerByClass(ServletContextHandler.class);
300            if (context.getSessionHandler() == null) {
301                SessionHandler sessionHandler = new SessionHandler();
302                if (context.isStarted()) {
303                    throw new IllegalStateException("Server has already been started. Cannot enabled sessionSupport on " + connectorKey);
304                } else {
305                    context.setSessionHandler(sessionHandler);
306                }
307            }
308        }
309        
310        private void enableMultipartFilter(HttpEndpoint endpoint, Server server, String connectorKey) throws Exception {
311            ServletContextHandler context = (ServletContextHandler) server
312                    .getChildHandlerByClass(ServletContextHandler.class);
313            CamelContext camelContext = this.getCamelContext();
314            FilterHolder filterHolder = new FilterHolder();
315            filterHolder.setInitParameter("deleteFiles", "true");
316            if (ObjectHelper.isNotEmpty(camelContext.getProperties().get(TMP_DIR))) {
317                File file = new File(camelContext.getProperties().get(TMP_DIR));
318                if (!file.isDirectory()) {
319                    throw new RuntimeCamelException(
320                            "The temp file directory of camel-jetty is not exists, please recheck it with directory name :"
321                                    + camelContext.getProperties().get(TMP_DIR));
322                }
323                context.setAttribute("javax.servlet.context.tempdir", file);
324            }
325            // if a filter ref was provided, use it.
326            Filter filter = ((JettyHttpEndpoint) endpoint).getMultipartFilter();
327            if (filter == null) {
328                // if no filter ref was provided, use the default filter
329                filter = new MultiPartFilter();
330            }
331            filterHolder.setFilter(new CamelMultipartFilter(filter));
332            String pathSpec = endpoint.getPath();
333            if (pathSpec == null || "".equals(pathSpec)) {
334                pathSpec = "/";
335            }
336            if (endpoint.isMatchOnUriPrefix()) {
337                pathSpec = pathSpec.endsWith("/") ? pathSpec + "*" : pathSpec + "/*";
338            }
339            context.addFilter(filterHolder, pathSpec, 0);
340            LOG.debug("using multipart filter implementation " + filter.getClass().getName() + " for path " + pathSpec);
341        }
342    
343        /**
344         * Disconnects the URL specified on the endpoint from the specified processor.
345         */
346        @Override
347        public void disconnect(HttpConsumer consumer) throws Exception {
348            // If the connector is not needed anymore then stop it
349            HttpEndpoint endpoint = consumer.getEndpoint();
350            String connectorKey = getConnectorKey(endpoint);
351            
352            synchronized (CONNECTORS) {
353                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
354                if (connectorRef != null) {
355                    connectorRef.servlet.disconnect(consumer);
356                    if (connectorRef.decrement() == 0) {
357                        connectorRef.server.removeConnector(connectorRef.connector);
358                        connectorRef.connector.stop();
359                        connectorRef.server.stop();
360                        CONNECTORS.remove(connectorKey);
361                        // Camel controls the lifecycle of these entities so remove the
362                        // registered MBeans when Camel is done with the managed objects.
363                        if (mbContainer != null) {
364                            mbContainer.removeBean(connectorRef.server);
365                            mbContainer.removeBean(connectorRef.connector);
366                        }
367                    }
368                }
369            }
370        }
371        
372        private String getConnectorKey(HttpEndpoint endpoint) {
373            return endpoint.getProtocol() + ":" + endpoint.getHttpUri().getHost() + ":" + endpoint.getPort();
374        }
375    
376        // Properties
377        // -------------------------------------------------------------------------
378           
379        public String getSslKeyPassword() {
380            return sslKeyPassword;
381        }
382    
383        public void setSslKeyPassword(String sslKeyPassword) {
384            this.sslKeyPassword = sslKeyPassword;
385        }
386    
387        public String getSslPassword() {
388            return sslPassword;
389        }
390    
391        public void setSslPassword(String sslPassword) {
392            this.sslPassword = sslPassword;
393        }
394    
395        public void setKeystore(String sslKeystore) {
396            this.sslKeystore = sslKeystore;
397        }
398    
399        public String getKeystore() {
400            return sslKeystore;
401        }
402    
403        protected SslSelectChannelConnector getSslSocketConnector(int port) throws Exception {
404            SslSelectChannelConnector answer = null;
405            if (sslSocketConnectors != null) {
406                answer = sslSocketConnectors.get(port);
407            }
408            if (answer == null) {
409                answer = createSslSocketConnector();
410            }
411            return answer;
412        }
413        
414        protected SslSelectChannelConnector createSslSocketConnector() throws Exception {
415            SslSelectChannelConnector answer = new SslSelectChannelConnector();
416            // with default null values, jetty ssl system properties
417            // and console will be read by jetty implementation
418    
419            String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
420            if (keystoreProperty != null) {
421                answer.setKeystore(keystoreProperty);
422            } else if (sslKeystore != null) {
423                answer.setKeystore(sslKeystore);
424            }
425    
426            String keystorePassword = System.getProperty(JETTY_SSL_KEYPASSWORD);
427            if (keystorePassword != null) {
428                answer.setKeyPassword(keystorePassword);
429            } else if (sslKeyPassword != null) {
430                answer.setKeyPassword(sslKeyPassword);
431            }
432    
433            String password = System.getProperty(JETTY_SSL_PASSWORD);
434            if (password != null) {
435                answer.setPassword(password);
436            } else if (sslPassword != null) {
437                answer.setPassword(sslPassword);
438            }
439    
440            if (getSslSocketConnectorProperties() != null) {
441                // must copy the map otherwise it will be deleted
442                Map<String, Object> properties = new HashMap<String, Object>(getSslSocketConnectorProperties());
443                IntrospectionSupport.setProperties(answer, properties);
444                if (properties.size() > 0) {
445                    throw new IllegalArgumentException("There are " + properties.size()
446                        + " parameters that couldn't be set on the SslSocketConnector."
447                        + " Check the uri if the parameters are spelt correctly and that they are properties of the SslSocketConnector."
448                        + " Unknown parameters=[" + properties + "]");
449                }
450            }
451            return answer;
452        }
453    
454        public Map<Integer, SslSelectChannelConnector> getSslSocketConnectors() {
455            return sslSocketConnectors;
456        }
457    
458        public void setSslSocketConnectors(Map <Integer, SslSelectChannelConnector> connectors) {
459            sslSocketConnectors = connectors;
460        }
461    
462        public SelectChannelConnector getSocketConnector(int port) throws Exception {
463            SelectChannelConnector answer = null;
464            if (socketConnectors != null) {
465                answer = socketConnectors.get(port);
466            }
467            if (answer == null) {
468                answer = createSocketConnector();
469            }
470            return answer;
471        }
472    
473        protected SelectChannelConnector createSocketConnector() throws Exception {
474            SelectChannelConnector answer = new SelectChannelConnector();
475            if (getSocketConnectorProperties() != null) {
476                // must copy the map otherwise it will be deleted
477                Map<String, Object> properties = new HashMap<String, Object>(getSocketConnectorProperties());
478                IntrospectionSupport.setProperties(answer, properties);
479                if (properties.size() > 0) {
480                    throw new IllegalArgumentException("There are " + properties.size()
481                        + " parameters that couldn't be set on the SocketConnector."
482                        + " Check the uri if the parameters are spelt correctly and that they are properties of the SelectChannelConnector."
483                        + " Unknown parameters=[" + properties + "]");
484                }
485            }
486            return answer;
487        }
488    
489        public void setSocketConnectors(Map<Integer, SelectChannelConnector> socketConnectors) {
490            this.socketConnectors = socketConnectors;
491        }
492    
493        public synchronized HttpClient getHttpClient() {
494            if (httpClient == null) {
495                httpClient = new HttpClient();
496                httpClient.setConnectorType(HttpClient.CONNECTOR_SELECT_CHANNEL);
497    
498                if (System.getProperty("http.proxyHost") != null && System.getProperty("http.proxyPort") != null) {
499                    String host = System.getProperty("http.proxyHost");
500                    int port = Integer.parseInt(System.getProperty("http.proxyPort"));
501                    if (LOG.isDebugEnabled()) {
502                        LOG.debug("Java System Property http.proxyHost and http.proxyPort detected. Using http proxy host: "
503                                + host + " port: " + port);
504                    }
505                    httpClient.setProxy(new Address(host, port));
506                }
507    
508                // use QueueThreadPool as the default bounded is deprecated (see SMXCOMP-157)
509                if (getHttpClientThreadPool() == null) {
510                    QueuedThreadPool qtp = new QueuedThreadPool();
511                    if (httpClientMinThreads != null) {
512                        qtp.setMinThreads(httpClientMinThreads.intValue());
513                    }
514                    if (httpClientMaxThreads != null) {
515                        qtp.setMaxThreads(httpClientMaxThreads.intValue());
516                    }
517                    try {
518                        qtp.start();
519                    } catch (Exception e) {
520                        throw new RuntimeCamelException("Error starting JettyHttpClient thread pool: " + qtp, e);
521                    }
522                    setHttpClientThreadPool(qtp);
523                }
524                httpClient.setThreadPool(getHttpClientThreadPool());
525            }
526            return httpClient;
527        }
528    
529        public void setHttpClient(HttpClient httpClient) {
530            this.httpClient = httpClient;
531        }
532    
533        public ThreadPool getHttpClientThreadPool() {
534            return httpClientThreadPool;
535        }
536    
537        public void setHttpClientThreadPool(ThreadPool httpClientThreadPool) {
538            this.httpClientThreadPool = httpClientThreadPool;
539        }
540    
541        public Integer getHttpClientMinThreads() {
542            return httpClientMinThreads;
543        }
544    
545        public void setHttpClientMinThreads(Integer httpClientMinThreads) {
546            this.httpClientMinThreads = httpClientMinThreads;
547        }
548    
549        public Integer getHttpClientMaxThreads() {
550            return httpClientMaxThreads;
551        }
552    
553        public void setHttpClientMaxThreads(Integer httpClientMaxThreads) {
554            this.httpClientMaxThreads = httpClientMaxThreads;
555        }
556    
557        public Integer getMinThreads() {
558            return minThreads;
559        }
560    
561        public void setMinThreads(Integer minThreads) {
562            this.minThreads = minThreads;
563        }
564    
565        public Integer getMaxThreads() {
566            return maxThreads;
567        }
568    
569        public void setMaxThreads(Integer maxThreads) {
570            this.maxThreads = maxThreads;
571        }
572    
573        public ThreadPool getThreadPool() {
574            return threadPool;
575        }
576    
577        public void setThreadPool(ThreadPool threadPool) {
578            this.threadPool = threadPool;
579        }
580    
581        public void setEnableJmx(boolean enableJmx) {
582            this.enableJmx = enableJmx;
583        }
584    
585        public boolean isEnableJmx() {
586            return enableJmx;
587        }
588        
589        public JettyHttpBinding getJettyHttpBinding() {
590            return jettyHttpBinding;
591        }
592    
593        public void setJettyHttpBinding(JettyHttpBinding jettyHttpBinding) {
594            this.jettyHttpBinding = jettyHttpBinding;
595        }
596    
597        public synchronized MBeanContainer getMbContainer() {
598            // If null, provide the default implementation.
599            if (mbContainer == null) {
600                MBeanServer mbs = null;
601                
602                final ManagementStrategy mStrategy = this.getCamelContext().getManagementStrategy();
603                final ManagementAgent mAgent = mStrategy.getManagementAgent();
604                if (mAgent != null) {
605                    mbs = mAgent.getMBeanServer();
606                }
607                
608                if (mbs != null) {
609                    mbContainer = new MBeanContainer(mbs);
610                    startMbContainer();
611                } else {
612                    LOG.warn("JMX disabled in CamelContext. Jetty JMX extensions will remain disabled.");
613                }
614            }
615            
616            return this.mbContainer;
617        }
618    
619        public void setMbContainer(MBeanContainer mbContainer) {
620            this.mbContainer = mbContainer;
621        }
622    
623        public Map<String, Object> getSslSocketConnectorProperties() {
624            return sslSocketConnectorProperties;
625        }
626    
627        public void setSslSocketConnectorProperties(Map<String, Object> sslSocketConnectorProperties) {
628            this.sslSocketConnectorProperties = sslSocketConnectorProperties;
629        }
630    
631        public Map<String, Object> getSocketConnectorProperties() {
632            return socketConnectorProperties;
633        }
634    
635        public void setSocketConnectorProperties(Map<String, Object> socketConnectorProperties) {
636            this.socketConnectorProperties = socketConnectorProperties;
637        }
638    
639        public void addSocketConnectorProperty(String key, Object value) {
640            if (socketConnectorProperties == null) {
641                socketConnectorProperties = new HashMap<String, Object>();
642            }
643            socketConnectorProperties.put(key, value);
644        }
645    
646        public void addSslSocketConnectorProperty(String key, Object value) {
647            if (sslSocketConnectorProperties == null) {
648                sslSocketConnectorProperties = new HashMap<String, Object>();
649            }
650            sslSocketConnectorProperties.put(key, value);
651        }
652    
653        public Long getContinuationTimeout() {
654            return continuationTimeout;
655        }
656    
657        public void setContinuationTimeout(Long continuationTimeout) {
658            this.continuationTimeout = continuationTimeout;
659        }
660    
661        public boolean isUseContinuation() {
662            return useContinuation;
663        }
664    
665        public void setUseContinuation(boolean useContinuation) {
666            this.useContinuation = useContinuation;
667        }
668    
669        // Implementation methods
670        // -------------------------------------------------------------------------
671        protected CamelServlet createServletForConnector(Server server, Connector connector,
672                                                         List<Handler> handlers, JettyHttpEndpoint endpoint) throws Exception {
673            ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
674            context.setConnectorNames(new String[] {connector.getName()});
675    
676            if (handlers != null && !handlers.isEmpty()) {
677                for (Handler handler : handlers) {
678                    if (handler instanceof HandlerWrapper) {
679                        ((HandlerWrapper) handler).setHandler(server.getHandler());
680                        server.setHandler(handler);
681                    } else {
682                        HandlerCollection handlerCollection = new HandlerCollection();
683                        handlerCollection.addHandler(server.getHandler());
684                        handlerCollection.addHandler(handler);
685                        server.setHandler(handlerCollection);
686                    }
687                }
688            }
689    
690            CamelServlet camelServlet;
691            boolean jetty = endpoint.getUseContinuation() != null ? endpoint.getUseContinuation() : isUseContinuation();
692            if (jetty) {
693                // use Jetty continuations
694                CamelContinuationServlet jettyServlet = new CamelContinuationServlet();
695                // configure timeout and log it so end user know what we are using
696                Long timeout = endpoint.getContinuationTimeout() != null ? endpoint.getContinuationTimeout() : getContinuationTimeout();
697                if (timeout != null) {
698                    LOG.info("Using Jetty continuation timeout: " + timeout + " millis for: " + endpoint);
699                    jettyServlet.setContinuationTimeout(timeout);
700                } else {
701                    LOG.info("Using default Jetty continuation timeout for: " + endpoint);
702                }
703    
704                // use the jetty servlet
705                camelServlet = jettyServlet;
706            } else {
707                // do not use jetty so use a plain servlet
708                camelServlet = new CamelServlet();
709                LOG.info("Jetty continuation is disabled for: " + endpoint);
710            }
711    
712            ServletHolder holder = new ServletHolder();
713            holder.setServlet(camelServlet);
714            context.addServlet(holder, "/*");
715    
716            return camelServlet;
717        }
718        
719        protected Server createServer() throws Exception {
720            Server server = new Server();
721            ContextHandlerCollection collection = new ContextHandlerCollection();
722            server.setHandler(collection);
723    
724            // configure thread pool if min/max given
725            if (minThreads != null || maxThreads != null) {
726                if (getThreadPool() != null) {
727                    throw new IllegalArgumentException("You cannot configure both minThreads/maxThreads and a custom threadPool on JettyHttpComponent: " + this);
728                }
729                QueuedThreadPool qtp = new QueuedThreadPool();
730                if (minThreads != null) {
731                    qtp.setMinThreads(minThreads.intValue());
732                }
733                if (maxThreads != null) {
734                    qtp.setMaxThreads(maxThreads.intValue());
735                }
736                try {
737                    qtp.start();
738                } catch (Exception e) {
739                    throw new RuntimeCamelException("Error starting JettyServer thread pool: " + qtp, e);
740                }
741                server.setThreadPool(qtp);
742            }
743    
744            if (getThreadPool() != null) {
745                server.setThreadPool(getThreadPool());
746            }
747    
748            return server;
749        }
750        
751        /**
752         * Starts {@link #mbContainer} and registers the container with itself as a managed bean
753         * logging an error if there is a problem starting the container.
754         * Does nothing if {@link #mbContainer} is {@code null}.
755         */
756        protected void startMbContainer() {
757            if (mbContainer != null && !mbContainer.isStarted()) {
758                try {
759                    mbContainer.start();
760                    // Publish the container itself for consistency with
761                    // traditional embedded Jetty configurations.
762                    mbContainer.addBean(mbContainer);
763                } catch (Throwable e) {
764                    LOG.warn("Could not start Jetty MBeanContainer. Jetty JMX extensions will remain disabled.", e);
765                }
766            }
767        }
768    
769        @Override
770        protected void doStart() throws Exception {
771            super.doStart();
772            if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
773                LifeCycle lc = (LifeCycle) httpClientThreadPool;
774                lc.start();
775            }
776            if (httpClient != null && !httpClient.isStarted()) {
777                httpClient.start();
778            }
779            
780            startMbContainer();
781        }
782    
783        @Override
784        protected void doStop() throws Exception {
785            super.doStop();
786            if (CONNECTORS.size() > 0) {
787                for (String connectorKey : CONNECTORS.keySet()) {
788                    ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
789                    if (connectorRef != null && connectorRef.getRefCount() == 0) {
790                        connectorRef.server.removeConnector(connectorRef.connector);
791                        connectorRef.connector.stop();
792                        connectorRef.server.stop();
793                        // Camel controls the lifecycle of these entities so remove the
794                        // registered MBeans when Camel is done with the managed objects.
795                        if (mbContainer != null) {
796                            mbContainer.removeBean(connectorRef.server);
797                            mbContainer.removeBean(connectorRef.connector);
798                        }
799                        CONNECTORS.remove(connectorKey);
800                    }
801                }
802            }
803            if (httpClient != null) {
804                httpClient.stop();
805            }
806            if (httpClientThreadPool != null && httpClientThreadPool instanceof LifeCycle) {
807                LifeCycle lc = (LifeCycle) httpClientThreadPool;
808                lc.stop();
809            }
810            if (mbContainer != null) {
811                mbContainer.stop();
812            }
813        }
814    }