| /* |
| * 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.tomcat; |
| |
| import java.beans.PropertyChangeListener; |
| 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.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Logger; |
| |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.Servlet; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| |
| import org.apache.catalina.Container; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Engine; |
| import org.apache.catalina.Host; |
| import org.apache.catalina.Lifecycle; |
| import org.apache.catalina.LifecycleException; |
| import org.apache.catalina.Loader; |
| import org.apache.catalina.connector.Connector; |
| import org.apache.catalina.core.StandardEngine; |
| import org.apache.catalina.startup.ContextConfig; |
| import org.apache.catalina.startup.Embedded; |
| import org.apache.coyote.http11.Http11Protocol; |
| import org.apache.tomcat.util.buf.MessageBytes; |
| import org.apache.tomcat.util.http.mapper.MappingData; |
| 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; |
| |
| /** |
| * A Tomcat based implementation of ServletHost. |
| * |
| * @version $Rev$ $Date$ |
| */ |
| public class TomcatServer implements ServletHost, LifeCycleListener { |
| private static final Logger logger = Logger.getLogger(TomcatServer.class.getName()); |
| |
| private WorkScheduler workScheduler; |
| private Embedded embedded; |
| private Map<Integer, Port> ports = new HashMap<Integer, Port>(); |
| private String contextPath = "/"; |
| private int defaultPort = 8080; |
| private int defaultSSLPort = 8443; |
| |
| private final class TuscanyLoader implements Loader { |
| private final ClassLoader tccl; |
| private boolean delegate; |
| private boolean reloadable; |
| private Container container; |
| private List<String> repos = new ArrayList<String>(); |
| private List<PropertyChangeListener> listeners = new ArrayList<PropertyChangeListener>(); |
| |
| private TuscanyLoader(ClassLoader tccl) { |
| this.tccl = tccl; |
| } |
| |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| listeners.add(listener); |
| } |
| |
| public void addRepository(String repository) { |
| repos.add(repository); |
| } |
| |
| public void backgroundProcess() { |
| } |
| |
| public String[] findRepositories() { |
| return repos.toArray(new String[repos.size()]); |
| } |
| |
| public Container getContainer() { |
| return container; |
| } |
| |
| public boolean getDelegate() { |
| return delegate; |
| } |
| |
| public String getInfo() { |
| return "Tuscany Loader for Embedded Tomcat"; |
| } |
| |
| public boolean getReloadable() { |
| return reloadable; |
| } |
| |
| public boolean modified() { |
| return false; |
| } |
| |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| listeners.remove(listener); |
| } |
| |
| public void setContainer(Container container) { |
| this.container = container; |
| } |
| |
| public void setDelegate(boolean delegate) { |
| this.delegate = delegate; |
| } |
| |
| public void setReloadable(boolean reloadable) { |
| this.reloadable = reloadable; |
| } |
| |
| public ClassLoader getClassLoader() { |
| return tccl; |
| } |
| } |
| |
| /** |
| * Represents a port and the server that serves it. |
| */ |
| private class Port { |
| private Engine engine; |
| private Host host; |
| private Connector connector; |
| |
| private Port(Engine engine, Host host, Connector connector) { |
| this.engine = engine; |
| this.host = host; |
| this.connector = connector; |
| } |
| |
| public Engine getEngine() { |
| return engine; |
| } |
| |
| public Host getHost() { |
| return host; |
| } |
| |
| public Connector getConnector() { |
| return connector; |
| } |
| } |
| |
| /** |
| * Constructs a new embedded Tomcat server. |
| * |
| */ |
| public TomcatServer(ExtensionPointRegistry registry, Map<String, String> attributes) { |
| UtilityExtensionPoint utilities = registry.getExtensionPoint(UtilityExtensionPoint.class); |
| this.workScheduler = utilities.getUtility(WorkScheduler.class); |
| String port = attributes.get("defaultPort"); |
| if (port != null) { |
| defaultPort = Integer.parseInt(port); |
| } |
| String sslPort = attributes.get("defaultSSLPort"); |
| if (sslPort != null) { |
| defaultPort = Integer.parseInt(sslPort); |
| } |
| if (attributes.containsKey("contextPath")) { |
| contextPath = attributes.get("contextPath"); |
| } |
| } |
| |
| protected TomcatServer(WorkScheduler workScheduler) { |
| this.workScheduler = workScheduler; |
| } |
| |
| public void setDefaultPort(int port) { |
| defaultPort = port; |
| } |
| |
| public int getDefaultPort() { |
| return defaultPort; |
| } |
| |
| /** |
| * Stop all the started servers. |
| */ |
| public void stop() { |
| if (embedded != null) { |
| try { |
| // embedded.stop(); |
| embedded.destroy(); |
| for (Port port : ports.values()) { |
| port.connector.stop(); |
| } |
| } catch (LifecycleException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| } |
| |
| public synchronized String addServletMapping(String suri, Servlet servlet) { |
| init(); |
| URI uri = URI.create(suri); |
| |
| // Get the URI scheme and port |
| String scheme = uri.getScheme(); |
| if (scheme == null) { |
| scheme = "http"; |
| } |
| final int portNumber = (uri.getPort() == -1 ? defaultPort : uri.getPort()); |
| |
| // Get the port object associated with the given port number |
| Port port = ports.get(portNumber); |
| if (port == null) { |
| |
| port = createInstance(scheme, portNumber); |
| ports.put(portNumber, port); |
| } |
| |
| // Register the Servlet mapping |
| String path = uri.getPath(); |
| |
| if (!path.startsWith("/")) { |
| path = '/' + path; |
| } |
| |
| if (!path.startsWith(contextPath)) { |
| path = contextPath + path; |
| } |
| |
| ServletWrapper wrapper; |
| if (servlet instanceof DefaultResourceServlet) { |
| String defaultServletPath = path; |
| |
| // Optimize the handling of resource requests, use the Tomcat default Servlet |
| // instead of our default resource Servlet |
| if (defaultServletPath.endsWith("*")) { |
| defaultServletPath = defaultServletPath.substring(0, defaultServletPath.length() - 1); |
| } |
| if (defaultServletPath.endsWith("/")) { |
| defaultServletPath = defaultServletPath.substring(0, defaultServletPath.length() - 1); |
| } |
| DefaultResourceServlet resourceServlet = (DefaultResourceServlet)servlet; |
| TomcatDefaultServlet defaultServlet = |
| new TomcatDefaultServlet(defaultServletPath, resourceServlet.getDocumentRoot()); |
| wrapper = new ServletWrapper(defaultServlet); |
| |
| } else { |
| wrapper = new ServletWrapper(servlet); |
| } |
| Context context = port.getHost().map(path); |
| wrapper.setName(path); |
| wrapper.addMapping(path); |
| context.addChild(wrapper); |
| context.addServletMapping(path, path); |
| port.getConnector().getMapper().addWrapper("localhost", "", path, wrapper); |
| |
| // Initialize the Servlet |
| try { |
| wrapper.initServlet(); |
| } catch (ServletException e) { |
| throw new ServletMappingException(e); |
| } |
| |
| // Compute the complete URL |
| String host; |
| try { |
| host = InetAddress.getLocalHost().getHostName(); |
| } 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(); |
| } |
| |
| private Port createInstance(String scheme, final int portNumber) { |
| Port port; |
| // Create an engine |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| final Engine engine = AccessController.doPrivileged(new PrivilegedAction<Engine>() { |
| public Engine run() { |
| return embedded.createEngine(); |
| } |
| }); |
| |
| ((StandardEngine)engine).setBaseDir(""); |
| engine.setDefaultHost("localhost"); |
| engine.setName("engine/" + portNumber); |
| |
| // Create a host |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| final Host host = AccessController.doPrivileged(new PrivilegedAction<Host>() { |
| public Host run() { |
| Host host = embedded.createHost("localhost", ""); |
| engine.addChild(host); |
| return host; |
| } |
| }); |
| |
| // Create the root context |
| Context context = embedded.createContext("", ""); |
| final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); |
| context.setLoader(new TuscanyLoader(tccl)); |
| // context.setParentClassLoader(tccl.getParent()); |
| ContextConfig config = new ContextConfig(); |
| ((Lifecycle)context).addLifecycleListener(config); |
| host.addChild(context); |
| |
| embedded.addEngine(engine); |
| |
| // Install an HTTP connector |
| |
| Connector connector; |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| try { |
| final String protocol = scheme; |
| connector = AccessController.doPrivileged(new PrivilegedExceptionAction<Connector>() { |
| public Connector run() throws Exception { |
| Connector customConnector = new Connector(); |
| customConnector.setPort(portNumber); |
| |
| if ("https".equalsIgnoreCase(protocol)) { |
| configureSSL(customConnector); |
| ((Http11Protocol)customConnector.getProtocolHandler()).setSSLEnabled(true); |
| } |
| return customConnector; |
| } |
| |
| private void configureSSL(Connector customConnector) { |
| String trustStore = System.getProperty("javax.net.ssl.trustStore"); |
| String trustStorePass = System.getProperty("javax.net.ssl.trustStorePassword"); |
| String keyStore = System.getProperty("javax.net.ssl.keyStore"); |
| String keyStorePass = System.getProperty("javax.net.ssl.keyStorePassword"); |
| |
| customConnector.setProperty("protocol", "TLS"); |
| |
| customConnector.setProperty("keystore", keyStore); |
| customConnector.setProperty("keypass", keyStorePass); |
| String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType()); |
| String trustStoreType = |
| System.getProperty("javax.net.ssl.trustStoreType", KeyStore.getDefaultType()); |
| customConnector.setProperty("keytype", keyStoreType); |
| customConnector.setProperty("trusttype", trustStoreType); |
| customConnector.setProperty("truststore", trustStore); |
| customConnector.setProperty("trustpass", trustStorePass); |
| |
| customConnector.setProperty("clientauth", "false"); |
| customConnector.setProtocol("HTTP/1.1"); |
| customConnector.setScheme(protocol); |
| customConnector.setProperty("backlog", "10"); |
| customConnector.setSecure(true); |
| } |
| }); |
| } catch (Exception e) { |
| throw new ServletMappingException(e); |
| } |
| |
| embedded.addConnector(connector); |
| try { |
| connector.start(); |
| } catch (LifecycleException e) { |
| throw new ServletMappingException(e); |
| } |
| |
| // Keep track of the running server |
| port = new Port(engine, host, connector); |
| return port; |
| } |
| |
| private synchronized void init() { |
| if (embedded != null) { |
| return; |
| } |
| embedded = new Embedded(); |
| embedded.setAwait(true); |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| try { |
| AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| public Object run() throws LifecycleException { |
| embedded.start(); |
| return null; |
| } |
| }); |
| } catch (PrivilegedActionException e) { |
| // throw (LifecycleException)e.getException(); |
| throw new ServletMappingException(e); |
| } |
| } |
| |
| public URL getURLMapping(String suri) throws ServletMappingException { |
| URI uri = URI.create(suri); |
| |
| // Get the URI scheme and port |
| String scheme = uri.getScheme(); |
| if (scheme == null) { |
| scheme = "http"; |
| } |
| int portNumber = uri.getPort(); |
| if (portNumber == -1) { |
| portNumber = defaultPort; |
| } |
| |
| // Get the host |
| String host; |
| try { |
| host = InetAddress.getLocalHost().getHostName(); |
| } 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; |
| } |
| |
| String mapping = uri.getPath(); |
| |
| if (!mapping.startsWith("/")) { |
| mapping = '/' + mapping; |
| } |
| |
| if (!mapping.startsWith(contextPath)) { |
| mapping = contextPath + mapping; |
| } |
| |
| final Context context = port.getHost().map(mapping); |
| final MappingData md = new MappingData(); |
| final MessageBytes mb = MessageBytes.newInstance(); |
| mb.setString(mapping); |
| try { |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| public Object run() throws Exception { |
| context.getMapper().map(mb, md); |
| return null; |
| } |
| }); |
| } catch (Exception e) { |
| return null; |
| } |
| if (md.wrapper instanceof ServletWrapper) { |
| ServletWrapper servletWrapper = (ServletWrapper)md.wrapper; |
| return servletWrapper.getServlet(); |
| } else { |
| return null; |
| } |
| } |
| |
| public synchronized 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) { |
| throw new IllegalStateException("No servlet registered at this URI: " + suri); |
| } |
| |
| String mapping = uri.getPath(); |
| |
| if (!mapping.startsWith("/")) { |
| mapping = '/' + mapping; |
| } |
| |
| if (!mapping.startsWith(contextPath)) { |
| mapping = contextPath + mapping; |
| } |
| |
| final Context context = port.getHost().map(mapping); |
| final MappingData md = new MappingData(); |
| final MessageBytes mb = MessageBytes.newInstance(); |
| mb.setString(mapping); |
| try { |
| // Allow privileged access to read properties. Requires PropertiesPermission read in |
| // security policy. |
| AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { |
| public Object run() throws Exception { |
| context.getMapper().map(mb, md); |
| return null; |
| } |
| }); |
| } catch (Exception e) { |
| return null; |
| } |
| if (md.wrapper instanceof ServletWrapper) { |
| ServletWrapper servletWrapper = (ServletWrapper)md.wrapper; |
| |
| port.getConnector().getMapper().removeWrapper("localhost", "", mapping); |
| |
| try { |
| context.removeServletMapping(mapping); |
| } catch (NegativeArraySizeException e) { |
| // JIRA TUSCANY-1599 |
| // FIXME Looks like a bug in Tomcat when removing the last |
| // Servlet in the list, catch the exception for now as it doesn't |
| // seem harmful, will find a better solution for the next release |
| } |
| context.removeChild(servletWrapper); |
| try { |
| servletWrapper.destroyServlet(); |
| } catch (Exception ex) { |
| // Hack to handle destruction of Servlets without Servlet context |
| } |
| |
| logger.info("Removed Servlet mapping: " + suri); |
| |
| // Stop the port if there's no servlets on it anymore |
| String[] contextNames = port.getConnector().getMapper().getContextNames(); |
| if (contextNames == null || contextNames.length == 0) { |
| try { |
| port.getConnector().stop(); |
| ((StandardEngine)port.getEngine()).stop(); |
| embedded.removeEngine(port.getEngine()); |
| embedded.removeConnector(port.getConnector()); |
| ports.remove(portNumber); |
| } catch (LifecycleException e) { |
| throw new IllegalStateException(e); |
| } |
| } |
| |
| return servletWrapper.getServlet(); |
| } else { |
| logger.warning("Trying to Remove servlet mapping: " + mapping + " where mapping is not registered"); |
| return null; |
| } |
| } |
| |
| 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; |
| } |
| |
| public void start() { |
| |
| } |
| |
| public String addServletMapping(String uri, Servlet servlet, SecurityContext securityContext) |
| throws ServletMappingException { |
| return addServletMapping(uri, servlet); |
| } |
| |
| public String getName() { |
| return "tomcat"; |
| } |
| |
| public URL getURLMapping(String arg0, SecurityContext arg1) { |
| return null; |
| } |
| |
| public void setAttribute(String arg0, Object arg1) { |
| } |
| |
| 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; |
| } |
| |
| @Override |
| public ServletContext getServletContext() { |
| if (ports.isEmpty()) { |
| return null; |
| } else { |
| Port port = ports.values().iterator().next(); |
| return port.getHost().map(getContextPath()).getServletContext(); |
| } |
| } |
| |
| } |