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.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.CamelContextHelper;
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.httpclient.params.HttpClientParams;
035    import org.apache.commons.logging.Log;
036    import org.apache.commons.logging.LogFactory;
037    import org.mortbay.jetty.Connector;
038    import org.mortbay.jetty.Handler;
039    import org.mortbay.jetty.Server;
040    import org.mortbay.jetty.handler.ContextHandlerCollection;
041    import org.mortbay.jetty.nio.SelectChannelConnector;
042    import org.mortbay.jetty.security.SslSocketConnector;
043    import org.mortbay.jetty.servlet.Context;
044    import org.mortbay.jetty.servlet.ServletHolder;
045    import org.mortbay.jetty.servlet.SessionHandler;
046    
047    /**
048     * An HttpComponent which starts an embedded Jetty for to handle consuming from
049     * the http endpoints.
050     *
051     * @version $Revision: 792899 $
052     */
053    public class JettyHttpComponent extends HttpComponent {
054        
055        protected static final HashMap<String, ConnectorRef> CONNECTORS = new HashMap<String, ConnectorRef>();
056       
057        private static final transient Log LOG = LogFactory.getLog(JettyHttpComponent.class);
058        private static final String JETTY_SSL_KEYSTORE = "jetty.ssl.keystore";
059        
060        protected String sslKeyPassword;
061        protected String sslPassword;
062        protected String sslKeystore;
063        protected SslSocketConnector sslSocketConnector;
064    
065        class ConnectorRef {
066            Server server;
067            Connector connector;
068            CamelServlet servlet;
069            int refCount;
070    
071            public ConnectorRef(Server server, Connector connector, CamelServlet servlet) {
072                this.server = server;
073                this.connector = connector;
074                this.servlet = servlet;
075                increment();
076            }
077    
078            public int increment() {
079                return ++refCount;
080            }
081    
082            public int decrement() {
083                return --refCount;
084            }
085        }
086        
087        
088        @Override
089        protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception {
090            uri = uri.startsWith("jetty:") ? remaining : uri;
091    
092            HttpClientParams params = new HttpClientParams();
093            IntrospectionSupport.setProperties(params, parameters, "httpClient.");
094    
095            // handlers
096            List<Handler> handlerList = new ArrayList<Handler>();
097            String handlers = getAndRemoveParameter(parameters, "handlers", String.class);
098            if (handlers != null) {
099                // remove any leading # for reference lookup as we know its a reference lookup
100                handlers = handlers.replaceAll("#", "");
101                // lookup each individual handler and add it to the list
102                for (String key : handlers.split(",")) {
103                    handlerList.add(CamelContextHelper.mandatoryLookup(getCamelContext(), key, Handler.class));
104                }
105            }
106    
107            // configure regular parameters
108            configureParameters(parameters);
109    
110            // restructure uri to be based on the parameters left as we dont want to include the Camel internal options
111            URI httpUri = URISupport.createRemainingURI(new URI(UnsafeUriCharactersEncoder.encode(uri)), parameters);
112            uri = httpUri.toString();
113    
114            JettyHttpEndpoint result = new JettyHttpEndpoint(this, uri, httpUri, params, getHttpConnectionManager(), httpClientConfigurer);
115            if (httpBinding != null) {
116                result.setBinding(httpBinding);
117            }
118            setEndpointHeaderFilterStrategy(result);
119            if (handlerList.size() > 0) {
120                result.setHandlers(handlerList);
121            }
122            setProperties(result, parameters);
123            return result;
124        }
125    
126        /**
127         * Connects the URL specified on the endpoint to the specified processor.
128         */
129        @Override
130        public void connect(HttpConsumer consumer) throws Exception {
131            // Make sure that there is a connector for the requested endpoint.
132            JettyHttpEndpoint endpoint = (JettyHttpEndpoint)consumer.getEndpoint();
133            String connectorKey = getConnectorKey(endpoint);
134    
135            synchronized (CONNECTORS) {
136                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
137                if (connectorRef == null) {
138                    Connector connector;
139                    if ("https".equals(endpoint.getProtocol())) {
140                        connector = getSslSocketConnector();
141                    } else {
142                        connector = new SelectChannelConnector();
143                    }
144                    connector.setPort(endpoint.getPort());
145                    connector.setHost(endpoint.getHttpUri().getHost());
146                    if ("localhost".equalsIgnoreCase(endpoint.getHttpUri().getHost())) {
147                        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)?");
148                    }
149                    Server server = createServer();
150                    server.addConnector(connector);
151    
152                    connectorRef = new ConnectorRef(server, connector, createServletForConnector(server, connector, endpoint.getHandlers()));
153                    connector.start();
154                    
155                    CONNECTORS.put(connectorKey, connectorRef);
156                    
157                } else {
158                    // ref track the connector
159                    connectorRef.increment();
160                }
161                // check the session support
162                if (endpoint.isSessionSupport()) {                
163                    enableSessionSupport(connectorRef.server);
164                }
165                connectorRef.servlet.connect(consumer);
166            }
167        }
168    
169        private void enableSessionSupport(Server server) throws Exception {
170            Context context = (Context)server.getChildHandlerByClass(Context.class);
171            if (context.getSessionHandler() == null) {
172                SessionHandler sessionHandler = new SessionHandler();
173                context.setSessionHandler(sessionHandler);
174                if (context.isStarted()) {
175                    // restart the context
176                    context.stop();
177                    context.start();
178                }
179            }
180    
181        }
182    
183        /**
184         * Disconnects the URL specified on the endpoint from the specified
185         * processor.
186         */
187        @Override
188        public void disconnect(HttpConsumer consumer) throws Exception {
189            // If the connector is not needed anymore then stop it
190            HttpEndpoint endpoint = consumer.getEndpoint();
191            String connectorKey = getConnectorKey(endpoint);
192            
193            synchronized (CONNECTORS) {
194                ConnectorRef connectorRef = CONNECTORS.get(connectorKey);
195                if (connectorRef != null) {
196                    connectorRef.servlet.disconnect(consumer);
197                    if (connectorRef.decrement() == 0) {
198                        connectorRef.server.removeConnector(connectorRef.connector);
199                        connectorRef.connector.stop();
200                        connectorRef.server.stop();
201                        CONNECTORS.remove(connectorKey);
202                    }
203                }
204            }
205        }
206        
207        private String getConnectorKey(HttpEndpoint endpoint) {
208            return endpoint.getProtocol() + ":" + endpoint.getHttpUri().getHost() + ":" + endpoint.getPort();
209        }
210    
211        // Properties
212        // -------------------------------------------------------------------------
213           
214        public String getSslKeyPassword() {
215            return sslKeyPassword;
216        }
217    
218        public void setSslKeyPassword(String sslKeyPassword) {
219            this.sslKeyPassword = sslKeyPassword;
220        }
221    
222        public String getSslPassword() {
223            return sslPassword;
224        }
225    
226        public void setSslPassword(String sslPassword) {
227            this.sslPassword = sslPassword;
228        }
229    
230        public void setKeystore(String sslKeystore) {
231            this.sslKeystore = sslKeystore;
232        }
233    
234        public String getKeystore() {
235            return sslKeystore;
236        }
237    
238        public synchronized SslSocketConnector getSslSocketConnector() {
239            if (sslSocketConnector == null) {
240                sslSocketConnector = new SslSocketConnector();
241                // with default null values, jetty ssl system properties
242                // and console will be read by jetty implementation
243                sslSocketConnector.setPassword(sslPassword);
244                sslSocketConnector.setKeyPassword(sslKeyPassword);
245                if (sslKeystore != null) {
246                    sslSocketConnector.setKeystore(sslKeystore);
247                } else {
248                    // try the keystore system property as a backup, jetty doesn't seem
249                    // to read this property anymore
250                    String keystoreProperty = System.getProperty(JETTY_SSL_KEYSTORE);
251                    if (keystoreProperty != null) {
252                        sslSocketConnector.setKeystore(keystoreProperty);
253                    }
254                }
255            }
256            return sslSocketConnector;
257        }
258    
259        public void setSslSocketConnector(SslSocketConnector connector) {
260            sslSocketConnector = connector;
261        }
262    
263        protected CamelServlet createServletForConnector(Server server, Connector connector, List<Handler> handlers) throws Exception {
264            CamelServlet camelServlet = new CamelServlet(isMatchOnUriPrefix());
265    
266            Context context = new Context(server, "/", Context.NO_SECURITY | Context.NO_SESSIONS);
267            context.setConnectorNames(new String[] {connector.getName()});
268    
269            if (handlers != null) {
270                for (Handler handler : handlers) {
271                    context.addHandler(handler);
272                }
273            }
274    
275            ServletHolder holder = new ServletHolder();
276            holder.setServlet(camelServlet);
277            context.addServlet(holder, "/*");
278            connector.start();
279            context.start();
280    
281            return camelServlet;
282        }
283        
284        // Implementation methods
285        // -------------------------------------------------------------------------
286        protected Server createServer() throws Exception {
287            Server server = new Server();
288            ContextHandlerCollection collection = new ContextHandlerCollection();
289            collection.setServer(server);
290            server.addHandler(collection);
291            server.start();
292            return server;
293        }
294       
295    }