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