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.servicemix.http.jetty;
018    
019    import java.io.IOException;
020    import java.io.OutputStream;
021    import java.net.InetAddress;
022    import java.net.URL;
023    import java.net.UnknownHostException;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.Map;
027    
028    import javax.jbi.JBIException;
029    import javax.management.MBeanServer;
030    import javax.servlet.ServletException;
031    import javax.servlet.http.HttpServletRequest;
032    import javax.servlet.http.HttpServletResponse;
033    
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.servicemix.http.ContextManager;
037    import org.apache.servicemix.http.HttpBridgeServlet;
038    import org.apache.servicemix.http.HttpConfiguration;
039    import org.apache.servicemix.http.HttpProcessor;
040    import org.apache.servicemix.http.SslParameters;
041    import org.apache.servicemix.common.security.KeystoreManager;
042    import org.apache.servicemix.common.security.AuthenticationService;
043    import org.mortbay.component.AbstractLifeCycle;
044    import org.mortbay.jetty.AbstractConnector;
045    import org.mortbay.jetty.Connector;
046    import org.mortbay.jetty.Handler;
047    import org.mortbay.jetty.HttpConnection;
048    import org.mortbay.jetty.HttpMethods;
049    import org.mortbay.jetty.MimeTypes;
050    import org.mortbay.jetty.Server;
051    import org.mortbay.jetty.handler.AbstractHandler;
052    import org.mortbay.jetty.handler.ContextHandler;
053    import org.mortbay.jetty.handler.ContextHandlerCollection;
054    import org.mortbay.jetty.handler.HandlerCollection;
055    import org.mortbay.jetty.security.Constraint;
056    import org.mortbay.jetty.security.ConstraintMapping;
057    import org.mortbay.jetty.security.SecurityHandler;
058    import org.mortbay.jetty.security.SslSocketConnector;
059    import org.mortbay.jetty.servlet.ServletHandler;
060    import org.mortbay.jetty.servlet.ServletHolder;
061    import org.mortbay.jetty.servlet.ServletMapping;
062    import org.mortbay.management.MBeanContainer;
063    import org.mortbay.thread.BoundedThreadPool;
064    import org.mortbay.thread.ThreadPool;
065    import org.mortbay.util.ByteArrayISO8859Writer;
066    import org.mortbay.util.LazyList;
067    import org.mortbay.util.StringUtil;
068    import org.springframework.core.io.ClassPathResource;
069    
070    public class JettyContextManager implements ContextManager {
071    
072        private static final Log LOGGER = LogFactory.getLog(JettyContextManager.class);
073    
074        private Map<String, Server> servers;
075        private HttpConfiguration configuration;
076        private BoundedThreadPool threadPool;
077        private Map<String, SslParameters> sslParams;
078        private MBeanServer mBeanServer;
079        private MBeanContainer mbeanContainer;
080    
081        /**
082         * @return the mbeanServer
083         */
084        public MBeanServer getMBeanServer() {
085            return mBeanServer;
086        }
087    
088        /**
089         * @param mBeanServer
090         *            the mbeanServer to set
091         */
092        public void setMBeanServer(MBeanServer mBeanServer) {
093            this.mBeanServer = mBeanServer;
094        }
095    
096        public void init() throws Exception {
097            if (configuration == null) {
098                configuration = new HttpConfiguration();
099            }
100            if (mBeanServer != null && !configuration.isManaged() && configuration.isJettyManagement()) {
101                mbeanContainer = new MBeanContainer(mBeanServer);
102            }
103            servers = new HashMap<String, Server>();
104            sslParams = new HashMap<String, SslParameters>();
105            BoundedThreadPool btp = new BoundedThreadPool();
106            btp.setMaxThreads(this.configuration.getJettyThreadPoolSize());
107            threadPool = btp;
108        }
109    
110        public void shutDown() throws Exception {
111            stop();
112        }
113    
114        public void start() throws Exception {
115            threadPool.start();
116            for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
117                Server server = it.next();
118                server.start();
119            }
120        }
121    
122        public void stop() throws Exception {
123            for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
124                Server server = it.next();
125                server.stop();
126            }
127            for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
128                Server server = it.next();
129                server.join();
130                Connector[] connectors = server.getConnectors();
131                for (int i = 0; i < connectors.length; i++) {
132                    if (connectors[i] instanceof AbstractConnector) {
133                        ((AbstractConnector) connectors[i]).join();
134                    }
135                }
136            }
137            threadPool.stop();
138        }
139    
140        public synchronized Object createContext(String strUrl, HttpProcessor processor) throws Exception {
141            URL url = new URL(strUrl);
142            Server server = getServer(url);
143            if (server == null) {
144                server = createServer(url, processor.getSsl());
145            } else {
146                // Check ssl params
147                SslParameters ssl = sslParams.get(getKey(url));
148                if (ssl != null && !ssl.equals(processor.getSsl())) {
149                    throw new Exception("An https server is already created on port " + url.getPort()
150                                    + " but SSL parameters do not match");
151                }
152            }
153            String path = url.getPath();
154            if (!path.startsWith("/")) {
155                path = "/" + path;
156            }
157            if (path.endsWith("/")) {
158                path = path.substring(0, path.length() - 1);
159            }
160            String pathSlash = path + "/";
161            // Check that context does not exist yet
162            HandlerCollection handlerCollection = (HandlerCollection) server.getHandler();
163            ContextHandlerCollection contexts = (ContextHandlerCollection) handlerCollection.getHandlers()[0];
164            Handler[] handlers = contexts.getHandlers();
165            if (handlers != null) {
166                for (int i = 0; i < handlers.length; i++) {
167                    if (handlers[i] instanceof ContextHandler) {
168                        ContextHandler h = (ContextHandler) handlers[i];
169                        String handlerPath = h.getContextPath() + "/";
170                        if (handlerPath.startsWith(pathSlash) || pathSlash.startsWith(handlerPath)) {
171                            throw new Exception("The requested context for path '" + path
172                                            + "' overlaps with an existing context for path: '" + h.getContextPath() + "'");
173                        }
174                    }
175                }
176            }
177            // Create context
178            ContextHandler context = new ContextHandler();
179            context.setContextPath(path);
180            ServletHolder holder = new ServletHolder();
181            holder.setName("jbiServlet");
182            holder.setClassName(HttpBridgeServlet.class.getName());
183            ServletHandler handler = new ServletHandler();
184            handler.setServlets(new ServletHolder[] {holder});
185            ServletMapping mapping = new ServletMapping();
186            mapping.setServletName("jbiServlet");
187            mapping.setPathSpec("/*");
188            handler.setServletMappings(new ServletMapping[] {mapping});
189            if (processor.getAuthMethod() != null) {
190                SecurityHandler secHandler = new SecurityHandler();
191                ConstraintMapping constraintMapping = new ConstraintMapping();
192                Constraint constraint = new Constraint();
193                constraint.setAuthenticate(true);
194                constraint.setRoles(new String[] {"*"});
195                constraintMapping.setConstraint(constraint);
196                constraintMapping.setPathSpec("/");
197                secHandler.setConstraintMappings(new ConstraintMapping[] {constraintMapping});
198                secHandler.setHandler(handler);
199                secHandler.setAuthMethod(processor.getAuthMethod());
200                JaasUserRealm realm = new JaasUserRealm();
201                if (configuration.getAuthenticationService() != null) {
202                    realm.setAuthenticationService(AuthenticationService.Proxy.create(configuration.getAuthenticationService()));
203                }
204                secHandler.setUserRealm(realm);
205                context.setHandler(secHandler);
206            } else {
207                context.setHandler(handler);
208            }
209            context.setAttribute("processor", processor);
210            // add context
211            contexts.addHandler(context);
212            handler.initialize();
213            context.start();
214            return context;
215        }
216    
217        public synchronized void remove(Object context) throws Exception {
218            ((ContextHandler) context).stop();
219            for (Iterator<Server> it = servers.values().iterator(); it.hasNext();) {
220                Server server = it.next();
221                HandlerCollection handlerCollection = (HandlerCollection) server.getHandler();
222                ContextHandlerCollection contexts = (ContextHandlerCollection) handlerCollection.getHandlers()[0];
223                Handler[] handlers = contexts.getHandlers();
224                if (handlers != null && handlers.length > 0) {
225                    contexts.setHandlers((Handler[]) LazyList.removeFromArray(handlers, context));
226                }
227            }
228        }
229    
230        protected Server getServer(URL url) {
231            return servers.get(getKey(url));
232        }
233    
234        protected String getKey(URL url) {
235            String host = url.getHost();
236            try {
237                InetAddress addr = InetAddress.getByName(host);
238                if (addr.isAnyLocalAddress()) {
239                    host = InetAddress.getLocalHost().getHostName();
240                }
241            } catch (UnknownHostException e) {
242                //unable to lookup host name, using IP address instead
243            }
244            return url.getProtocol() + "://" + host + ":" + url.getPort();
245        }
246    
247        protected Server createServer(URL url, SslParameters ssl) throws Exception {
248            boolean isSsl = false;
249            if (url.getProtocol().equals("https")) {
250                // TODO: put ssl default information on HttpConfiguration
251                if (ssl == null) {
252                    throw new IllegalArgumentException("https protocol required but no ssl parameters found");
253                }
254                isSsl = true;
255            } else if (!url.getProtocol().equals("http")) {
256                throw new UnsupportedOperationException("Protocol " + url.getProtocol() + " is not supported");
257            }
258            // Create a new server
259            Connector connector;
260            if (isSsl && ssl.isManaged()) {
261                connector = setupManagerSslConnector(url, ssl);
262            } else if (isSsl) {
263                connector = setupSslConnector(url, ssl);
264            } else {
265                String connectorClassName = configuration.getJettyConnectorClassName();
266                try {
267                    connector = (Connector) Class.forName(connectorClassName).newInstance();
268                } catch (Exception e) {
269                    LOGGER.warn("Could not create a jetty connector of class '" + connectorClassName + "'. Defaulting to "
270                                    + HttpConfiguration.DEFAULT_JETTY_CONNECTOR_CLASS_NAME);
271                    if (LOGGER.isDebugEnabled()) {
272                        LOGGER.debug("Reason: " + e.getMessage(), e);
273                    }
274                    connector = (Connector) Class.forName(HttpConfiguration.DEFAULT_JETTY_CONNECTOR_CLASS_NAME)
275                                    .newInstance();
276                }
277            }
278            connector.setHost(url.getHost());
279            connector.setPort(url.getPort());
280            connector.setMaxIdleTime(this.configuration.getConnectorMaxIdleTime());
281            Server server = new Server();
282            server.setThreadPool(new ThreadPoolWrapper());
283            server.setConnectors(new Connector[] {connector});
284            ContextHandlerCollection contexts = new ContextHandlerCollection();
285            HandlerCollection handlers = new HandlerCollection();
286            handlers.setHandlers(new Handler[] {contexts, new DisplayServiceHandler()});
287            server.setHandler(handlers);
288            server.start();
289            servers.put(getKey(url), server);
290            sslParams.put(getKey(url), isSsl ? ssl : null);
291            if (mbeanContainer != null) {
292                server.getContainer().addEventListener(mbeanContainer);
293            }
294            return server;
295        }
296    
297        private Connector setupSslConnector(URL url, SslParameters ssl) throws JBIException {
298            Connector connector;
299            String keyStore = ssl.getKeyStore();
300            if (keyStore == null) {
301                keyStore = System.getProperty("javax.net.ssl.keyStore", "");
302                if (keyStore == null) {
303                    throw new IllegalArgumentException(
304                                   "keyStore or system property javax.net.ssl.keyStore must be set");
305                }
306            }
307            if (keyStore.startsWith("classpath:")) {
308                try {
309                    String res = keyStore.substring(10);
310                    URL resurl = new ClassPathResource(res).getURL();
311                    keyStore = resurl.toString();
312                } catch (IOException e) {
313                    throw new JBIException("Unable to find keystore " + keyStore, e);
314                }
315            }
316            String keyStorePassword = ssl.getKeyStorePassword();
317            if (keyStorePassword == null) {
318                keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
319                if (keyStorePassword == null) {
320                    throw new IllegalArgumentException(
321                        "keyStorePassword or system property javax.net.ssl.keyStorePassword must be set");
322                }
323            }
324            SslSocketConnector sslConnector = new SslSocketConnector();
325            sslConnector.setSslKeyManagerFactoryAlgorithm(ssl.getKeyManagerFactoryAlgorithm());
326            sslConnector.setSslTrustManagerFactoryAlgorithm(ssl.getTrustManagerFactoryAlgorithm());
327            sslConnector.setProtocol(ssl.getProtocol());
328            sslConnector.setConfidentialPort(url.getPort());
329            sslConnector.setPassword(ssl.getKeyStorePassword());
330            sslConnector.setKeyPassword(ssl.getKeyPassword() != null ? ssl.getKeyPassword() : keyStorePassword);
331            sslConnector.setKeystore(keyStore);
332            sslConnector.setKeystoreType(ssl.getKeyStoreType());
333            sslConnector.setNeedClientAuth(ssl.isNeedClientAuth());
334            sslConnector.setWantClientAuth(ssl.isWantClientAuth());
335            // important to set this values for selfsigned keys
336            // otherwise the standard truststore of the jre is used
337            sslConnector.setTruststore(ssl.getTrustStore());
338            if (ssl.getTrustStorePassword() != null) {
339                // check is necessary because if a null password is set
340                // jetty would ask for a password on the comandline
341                sslConnector.setTrustPassword(ssl.getTrustStorePassword());
342            }
343            sslConnector.setTruststoreType(ssl.getTrustStoreType());
344            connector = sslConnector;
345            return connector;
346        }
347    
348        private Connector setupManagerSslConnector(URL url, SslParameters ssl) {
349            Connector connector;
350            String keyStore = ssl.getKeyStore();
351            if (keyStore == null) {
352                throw new IllegalArgumentException("keyStore must be set");
353            }
354            ServiceMixSslSocketConnector sslConnector = new ServiceMixSslSocketConnector();
355            sslConnector.setSslKeyManagerFactoryAlgorithm(ssl.getKeyManagerFactoryAlgorithm());
356            sslConnector.setSslTrustManagerFactoryAlgorithm(ssl.getTrustManagerFactoryAlgorithm());
357            sslConnector.setProtocol(ssl.getProtocol());
358            sslConnector.setConfidentialPort(url.getPort());
359            sslConnector.setKeystore(keyStore);
360            sslConnector.setKeyAlias(ssl.getKeyAlias());
361            sslConnector.setNeedClientAuth(ssl.isNeedClientAuth());
362            sslConnector.setWantClientAuth(ssl.isWantClientAuth());
363            sslConnector.setKeystoreManager(KeystoreManager.Proxy.create(getConfiguration().getKeystoreManager()));
364            // important to set this values for selfsigned keys
365            // otherwise the standard truststore of the jre is used
366            sslConnector.setTruststore(ssl.getTrustStore());
367            if (ssl.getTrustStorePassword() != null) {
368                // check is necessary because if a null password is set
369                // jetty would ask for a password on the comandline
370                sslConnector.setTrustPassword(ssl.getTrustStorePassword());
371            }
372            sslConnector.setTruststoreType(ssl.getTrustStoreType());
373            connector = sslConnector;
374            return connector;
375        }
376    
377        public HttpConfiguration getConfiguration() {
378            return configuration;
379        }
380    
381        public void setConfiguration(HttpConfiguration configuration) {
382            this.configuration = configuration;
383        }
384    
385        public ThreadPool getThreadPool() {
386            return threadPool;
387        }
388    
389        protected class DisplayServiceHandler extends AbstractHandler {
390    
391            public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch)
392                throws IOException, ServletException {
393                if (response.isCommitted() || HttpConnection.getCurrentConnection().getRequest().isHandled()) {
394                    return;
395                }
396                    
397                String method = request.getMethod();
398    
399                if (!method.equals(HttpMethods.GET) || !request.getRequestURI().equals("/")) {
400                    response.sendError(404);
401                    return;
402                }
403    
404                response.setStatus(404);
405                response.setContentType(MimeTypes.TEXT_HTML);
406    
407                ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
408    
409                String uri = request.getRequestURI();
410                uri = StringUtil.replace(uri, "<", "&lt;");
411                uri = StringUtil.replace(uri, ">", "&gt;");
412    
413                writer.write("<HTML>\n<HEAD>\n<TITLE>Error 404 - Not Found");
414                writer.write("</TITLE>\n<BODY>\n<H2>Error 404 - Not Found.</H2>\n");
415                writer.write("No service matched or handled this request.<BR>");
416                writer.write("Known services are: <ul>");
417    
418                for (String serverUri : servers.keySet()) {
419                    Server server = JettyContextManager.this.servers.get(serverUri);
420                    Handler[] handlers = server.getChildHandlersByClass(ContextHandler.class);
421                    for (int i = 0; handlers != null && i < handlers.length; i++) {
422                        if (!(handlers[i] instanceof ContextHandler)) {
423                            continue;
424                        }
425                        ContextHandler context = (ContextHandler) handlers[i];
426                        StringBuffer sb = new StringBuffer();
427                        sb.append(serverUri);
428                        if (!context.getContextPath().startsWith("/")) {
429                            sb.append("/");
430                        }
431                        sb.append(context.getContextPath());
432                        if (!context.getContextPath().endsWith("/")) {
433                            sb.append("/");
434                        }
435                        if (context.isStarted()) {
436                            writer.write("<li><a href=\"");
437                            writer.write(sb.toString());
438                            writer.write("?wsdl\">");
439                            writer.write(sb.toString());
440                            writer.write("</a></li>\n");
441                        } else {
442                            writer.write("<li>");
443                            writer.write(sb.toString());
444                            writer.write(" [Stopped]</li>\n");
445                        }
446                    }
447                }
448    
449                for (int i = 0; i < 10; i++) {
450                    writer.write("\n<!-- Padding for IE                  -->");
451                }
452    
453                writer.write("\n</BODY>\n</HTML>\n");
454                writer.flush();
455                response.setContentLength(writer.size());
456                OutputStream out = response.getOutputStream();
457                writer.writeTo(out);
458                out.close();
459            }
460    
461        }
462    
463        protected class ThreadPoolWrapper extends AbstractLifeCycle implements ThreadPool {
464    
465            public boolean dispatch(Runnable job) {
466                if (LOGGER.isDebugEnabled()) {
467                    LOGGER.debug("Dispatching job: " + job);
468                }
469                return threadPool.dispatch(job);
470            }
471    
472            public int getIdleThreads() {
473                return threadPool.getIdleThreads();
474            }
475    
476            public int getThreads() {
477                return threadPool.getThreads();
478            }
479    
480            public void join() throws InterruptedException {
481            }
482    
483            public boolean isLowOnThreads() {
484                return threadPool.isLowOnThreads();
485            }
486        }
487    
488        public HttpProcessor getMainProcessor() {
489            throw new IllegalStateException("ServerManager is not managed");
490        }
491    
492    }