/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tuscany.sca.http.jetty; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; import java.security.AccessController; import java.security.KeyStore; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Logger; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.core.LifeCycleListener; import org.apache.tuscany.sca.core.UtilityExtensionPoint; import org.apache.tuscany.sca.host.http.DefaultResourceServlet; import org.apache.tuscany.sca.host.http.SecurityContext; import org.apache.tuscany.sca.host.http.ServletHost; import org.apache.tuscany.sca.host.http.ServletMappingException; import org.apache.tuscany.sca.work.WorkScheduler; import org.mortbay.jetty.Connector; import org.mortbay.jetty.Server; import org.mortbay.jetty.handler.ContextHandler; import org.mortbay.jetty.nio.SelectChannelConnector; import org.mortbay.jetty.security.SslSocketConnector; import org.mortbay.jetty.servlet.DefaultServlet; import org.mortbay.jetty.servlet.ServletHandler; import org.mortbay.jetty.servlet.ServletHolder; import org.mortbay.jetty.servlet.ServletMapping; import org.mortbay.jetty.servlet.SessionHandler; import org.mortbay.log.Log; import org.mortbay.thread.ThreadPool; /** * Implements an HTTP transport service using Jetty. * * @version $Rev$ $Date$ */ public class JettyServer implements ServletHost, LifeCycleListener { private static final Logger logger = Logger.getLogger(JettyServer.class.getName()); private final Object joinLock = new Object(); private String trustStore; private String trustStorePassword; private String keyStore; private String keyStorePassword; private String keyStoreType; private String trustStoreType; private boolean sendServerVersion; private WorkScheduler workScheduler; // TODO - this static seems to be set by the JSORPC binding unit test // doesn't look to be a great way of doing things public static int portDefault = 8080; private int defaultPort = portDefault; private int defaultSSLPort = 8443; /** * Represents a port and the server that serves it. */ private class Port { private Server server; private ServletHandler servletHandler; private Port(Server server, ServletHandler servletHandler) { this.server = server; this.servletHandler = servletHandler; } public Server getServer() { return server; } public ServletHandler getServletHandler() { return servletHandler; } } private Map<Integer, Port> ports = new HashMap<Integer, Port>(); private String contextPath = "/"; private org.mortbay.log.Logger jettyLogger; public JettyServer(ExtensionPointRegistry registry) { this(registry.getExtensionPoint(UtilityExtensionPoint.class).getUtility(WorkScheduler.class)); } protected JettyServer(WorkScheduler workScheduler) { this.defaultPort = portDefault; this.workScheduler = workScheduler; AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { trustStore = System.getProperty("javax.net.ssl.trustStore"); trustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword"); keyStore = System.getProperty("javax.net.ssl.keyStore"); keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword"); keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); trustStoreType = System.getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType()); System.setProperty("JETTY_NO_SHUTDOWN_HOOK", "true"); return null; } }); } public String getName() { return "jetty"; } public void setDefaultPort(int port) { defaultPort = port; } public int getDefaultPort() { return defaultPort; } public void setSendServerVersion(boolean sendServerVersion) { this.sendServerVersion = sendServerVersion; } /** * Stop all the started servers. */ public void stop() { synchronized (joinLock) { joinLock.notifyAll(); } try { Set<Entry<Integer, Port>> entries = new HashSet<Entry<Integer, Port>>(ports.entrySet()); for (Entry<Integer, Port> entry : entries) { Port port = entry.getValue(); Server server = port.getServer(); server.stop(); server.setStopAtShutdown(false); ports.remove(entry.getKey()); } } catch (Exception e) { throw new ServletMappingException(e); } finally { if (jettyLogger != null) { Log.setLog(jettyLogger); jettyLogger = null; } } } private void configureSSL(SslSocketConnector connector, SecurityContext securityContext) { connector.setProtocol("TLS"); if (securityContext != null) { keyStoreType = securityContext.getSSLProperties().getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); keyStore = securityContext.getSSLProperties().getProperty("javax.net.ssl.keyStore"); keyStorePassword = securityContext.getSSLProperties().getProperty("javax.net.ssl.keyStorePassword"); trustStoreType = securityContext.getSSLProperties().getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType()); trustStore = securityContext.getSSLProperties().getProperty("javax.net.ssl.trustStore"); trustStorePassword = securityContext.getSSLProperties().getProperty("javax.net.ssl.trustStorePassword"); } connector.setKeystore(keyStore); connector.setKeyPassword(keyStorePassword); connector.setKeystoreType(keyStoreType); connector.setTruststore(trustStore); connector.setTrustPassword(trustStorePassword); connector.setTruststoreType(trustStoreType); connector.setPassword(keyStorePassword); if (trustStore != null) { connector.setNeedClientAuth(true); } } public String addServletMapping(String suri, Servlet servlet) throws ServletMappingException { return addServletMapping(suri, servlet, null); } public String addServletMapping(String suri, Servlet servlet, final SecurityContext securityContext) throws ServletMappingException { URI uri = URI.create(suri); // Get the URI scheme and port String scheme = null; if (securityContext != null && securityContext.isSSLEnabled()) { scheme = "https"; } else { scheme = uri.getScheme(); if (scheme == null) { scheme = "http"; } } String host = uri.getHost(); if ("0.0.0.0".equals(host)) { host = null; } int portNumber = uri.getPort(); if (portNumber == -1) { if ("http".equals(scheme)) { portNumber = defaultPort; } else { portNumber = defaultSSLPort; } } // Get the port object associated with the given port number Port port = ports.get(portNumber); if (port == null) { // Create and start a new server try { Server server = new Server(); server.setThreadPool(new WorkSchedulerThreadPool()); if ("https".equals(scheme)) { // Connector httpConnector = new SelectChannelConnector(); // httpConnector.setPort(portNumber); SslSocketConnector sslConnector = new SslSocketConnector(); sslConnector.setPort(portNumber); // FIXME: [rfeng] We should set the host to be bound but binding-ws-axis2 is passing // in an absolute URI with host set to one of the ip addresses sslConnector.setHost(host); configureSSL(sslConnector, securityContext); server.setConnectors(new Connector[] {sslConnector}); } else { SelectChannelConnector selectConnector = new SelectChannelConnector(); selectConnector.setPort(portNumber); // FIXME: [rfeng] We should set the host to be bound but binding-ws-axis2 is passing // in an absolute URI with host set to one of the ip addresses selectConnector.setHost(host); server.setConnectors(new Connector[] {selectConnector}); } ContextHandler contextHandler = new ContextHandler(); //contextHandler.setContextPath(contextPath); contextHandler.setContextPath("/"); server.setHandler(contextHandler); SessionHandler sessionHandler = new SessionHandler(); ServletHandler servletHandler = new ServletHandler(); sessionHandler.addHandler(servletHandler); contextHandler.setHandler(sessionHandler); server.setStopAtShutdown(true); server.setSendServerVersion(sendServerVersion); server.start(); // Keep track of the new server and Servlet handler port = new Port(server, servletHandler); ports.put(portNumber, port); } catch (Exception e) { throw new ServletMappingException(e); } } // Register the Servlet mapping ServletHandler servletHandler = port.getServletHandler(); ServletHolder holder; if (servlet instanceof DefaultResourceServlet) { // Optimize the handling of resource requests, use the Jetty default Servlet // instead of our default resource Servlet String servletPath = uri.getPath(); if (servletPath.endsWith("*")) { servletPath = servletPath.substring(0, servletPath.length() - 1); } if (servletPath.endsWith("/")) { servletPath = servletPath.substring(0, servletPath.length() - 1); } if (!servletPath.startsWith("/")) { servletPath = '/' + servletPath; } DefaultResourceServlet resourceServlet = (DefaultResourceServlet)servlet; DefaultServlet defaultServlet = new JettyDefaultServlet(servletPath, resourceServlet.getDocumentRoot()); holder = new ServletHolder(defaultServlet); } else { holder = new ServletHolder(servlet); } servletHandler.addServlet(holder); ServletMapping mapping = new ServletMapping(); mapping.setServletName(holder.getName()); String path = uri.getPath(); if (!path.startsWith("/")) { path = '/' + path; } if (!path.startsWith(contextPath)) { path = contextPath + path; } mapping.setPathSpec(path); servletHandler.addServletMapping(mapping); // Compute the complete URL if (host == null) { try { host = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { host = "localhost"; } } URL addedURL; try { addedURL = new URL(scheme, host, portNumber, path); } catch (MalformedURLException e) { throw new ServletMappingException(e); } logger.info("Added Servlet mapping: " + addedURL); return addedURL.toString(); } public URL getURLMapping(String suri, SecurityContext securityContext) throws ServletMappingException { return map(suri, securityContext, true); } private URL map(String suri, SecurityContext securityContext, boolean resolve) throws ServletMappingException { URI uri = URI.create(suri); // Get the URI scheme and port String scheme = null; if (securityContext != null && securityContext.isSSLEnabled()) { scheme = "https"; } else { scheme = uri.getScheme(); if (scheme == null) { scheme = "http"; } } int portNumber = uri.getPort(); if (portNumber == -1) { if ("http".equals(scheme)) { portNumber = defaultPort; } else { portNumber = defaultSSLPort; } } // Get the host String host = uri.getHost(); if (host == null) { host = "0.0.0.0"; if (resolve) { try { host = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { host = "localhost"; } } } // Construct the URL String path = uri.getPath(); if (!path.startsWith("/")) { path = '/' + path; } if (!path.startsWith(contextPath)) { path = contextPath + path; } URL url; try { url = new URL(scheme, host, portNumber, path); } catch (MalformedURLException e) { throw new ServletMappingException(e); } return url; } public Servlet getServletMapping(String suri) throws ServletMappingException { if (suri == null) { return null; } URI uri = URI.create(suri); // Get the URI port int portNumber = uri.getPort(); if (portNumber == -1) { portNumber = defaultPort; } // Get the port object associated with the given port number Port port = ports.get(portNumber); if (port == null) { return null; } // Remove the Servlet mapping for the given Servlet ServletHandler servletHandler = port.getServletHandler(); Servlet servlet = null; List<ServletMapping> mappings = new ArrayList<ServletMapping>(Arrays.asList(servletHandler.getServletMappings())); String path = uri.getPath(); if (!path.startsWith("/")) { path = '/' + path; } if (!path.startsWith(contextPath)) { path = contextPath + path; } for (ServletMapping mapping : mappings) { if (Arrays.asList(mapping.getPathSpecs()).contains(path)) { try { servlet = servletHandler.getServlet(mapping.getServletName()).getServlet(); } catch (ServletException e) { throw new IllegalStateException(e); } break; } } return servlet; } public Servlet removeServletMapping(String suri) { URI uri = URI.create(suri); // Get the URI port int portNumber = uri.getPort(); if (portNumber == -1) { portNumber = defaultPort; } // Get the port object associated with the given port number Port port = ports.get(portNumber); if (port == null) { // TODO - EPR - SL commented out exception temporarily as the runtime is shared // between multiple nodes in a VM and shutting down one node blows // up any other nodes when they shut down. //throw new IllegalStateException("No servlet registered at this URI: " + suri); logger.warning("No servlet registered at this URI: " + suri); return null; } // Remove the Servlet mapping for the given Servlet ServletHandler servletHandler = port.getServletHandler(); Servlet removedServlet = null; List<ServletMapping> mappings = new ArrayList<ServletMapping>(Arrays.asList(servletHandler.getServletMappings())); String path = uri.getPath(); if (!path.startsWith("/")) { path = '/' + path; } if (!path.startsWith(contextPath)) { path = contextPath + path; } for (ServletMapping mapping : mappings) { if (Arrays.asList(mapping.getPathSpecs()).contains(path)) { try { removedServlet = servletHandler.getServlet(mapping.getServletName()).getServlet(); } catch (ServletException e) { throw new IllegalStateException(e); } mappings.remove(mapping); logger.info("Removed Servlet mapping: " + path); break; } } if (removedServlet != null) { servletHandler.setServletMappings(mappings.toArray(new ServletMapping[mappings.size()])); // Stop the port if there are no servlet mappings on it anymore if (mappings.size() == 0) { try { Server server = port.getServer(); server.stop(); server.setStopAtShutdown(false); } catch (Exception e) { throw new IllegalStateException(e); } ports.remove(portNumber); } } else { logger.warning("Trying to Remove servlet mapping: " + path + " where mapping is not registered"); } return removedServlet; } public RequestDispatcher getRequestDispatcher(String suri) throws ServletMappingException { //FIXME implement this later return null; } public String getContextPath() { return contextPath; } public void setContextPath(String path) { this.contextPath = path; } /** * A wrapper to enable use of a WorkScheduler with Jetty */ private class WorkSchedulerThreadPool implements ThreadPool { public boolean dispatch(Runnable work) { workScheduler.scheduleWork(work); return true; } public void join() throws InterruptedException { synchronized (joinLock) { joinLock.wait(); } } public int getThreads() { throw new UnsupportedOperationException(); } public int getIdleThreads() { throw new UnsupportedOperationException(); } public boolean isLowOnThreads() { return false; } } public void setAttribute(String name, Object value) { throw new UnsupportedOperationException(); } public void start() { try { jettyLogger = new JettyLogger(logger); Log.setLog(jettyLogger); } catch (Throwable e) { // Ignore } } @Override public ServletContext getServletContext() { if (ports.size() > 0) { return ports.values().iterator().next().getServletHandler().getServletContext(); } else { return null; } } }