+/*\r
+ * Copyright 2006-2008 the original author or authors.\r
+ * \r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ * \r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.springframework.osgi.web.tomcat.internal;\r
+\r
+import java.io.File;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.net.URL;\r
+import java.net.URLConnection;\r
+import java.net.URLStreamHandler;\r
+import java.util.Properties;\r
+\r
+import javax.management.MBeanRegistration;\r
+\r
+import org.apache.catalina.Lifecycle;\r
+import org.apache.catalina.Server;\r
+import org.apache.catalina.Service;\r
+import org.apache.catalina.connector.Connector;\r
+import org.apache.catalina.core.StandardService;\r
+import org.apache.catalina.util.ServerInfo;\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.apache.naming.resources.DirContextURLStreamHandler;\r
+import org.osgi.framework.Bundle;\r
+import org.osgi.framework.BundleActivator;\r
+import org.osgi.framework.BundleContext;\r
+import org.osgi.framework.Constants;\r
+import org.osgi.framework.ServiceRegistration;\r
+import org.osgi.service.url.AbstractURLStreamHandlerService;\r
+import org.osgi.service.url.URLConstants;\r
+import org.osgi.service.url.URLStreamHandlerService;\r
+\r
+/**\r
+ * Simple activator for starting Apache Tomcat Catalina container inside OSGi\r
+ * using Tomcat's XML configuration files.\r
+ * \r
+ * <p/> This activator looks initially for a <code>conf/server.xml</code> file\r
+ * falling back to <code>conf/default-server.xml</code>. This allows the\r
+ * default configuration to be tweaked through fragments for example.\r
+ * \r
+ * @author Costin Leau\r
+ */\r
+public class Activator implements BundleActivator {\r
+\r
+ /** logger */\r
+ private static final Log log = LogFactory.getLog(Activator.class);\r
+\r
+ /** default XML configuration */\r
+ private static final String DEFAULT_XML_CONF_LOCATION = "conf/default-server.xml";\r
+\r
+ /** user-configurable XML configuration */\r
+ private static final String XML_CONF_LOCATION = "conf/server.xml";\r
+\r
+ private BundleContext bundleContext;\r
+\r
+ private StandardService server;\r
+\r
+ private ServiceRegistration registration, urlRegistration;\r
+\r
+ private Thread startupThread;\r
+\r
+\r
+ public void start(BundleContext context) throws Exception {\r
+ this.bundleContext = context;\r
+ // do the initialization on a different thread\r
+ // so the activator finishes fast\r
+ startupThread = new Thread(new Runnable() {\r
+\r
+ public void run() {\r
+ log.info("Starting " + ServerInfo.getServerInfo() + " ...");\r
+\r
+ // default startup procedure\r
+ ClassLoader cl = Activator.class.getClassLoader();\r
+ Thread current = Thread.currentThread();\r
+ ClassLoader old = current.getContextClassLoader();\r
+\r
+ try {\r
+ current.setContextClassLoader(cl);\r
+\r
+ server = createCatalinaServer(bundleContext.getBundle());\r
+\r
+ server.start();\r
+\r
+ Connector[] connectors = server.findConnectors();\r
+ for (int i = 0; i < connectors.length; i++) {\r
+ Connector conn = connectors[i];\r
+ log.info("Succesfully started " + ServerInfo.getServerInfo() + " @ " + conn.getDomain() + ":"\r
+ + conn.getPort());\r
+ }\r
+\r
+ // register URL service\r
+ urlRegistration = registerTomcatJNDIUrlService();\r
+ // publish server as an OSGi service\r
+ registration = publishServerAsAService(server);\r
+ log.info("Published " + ServerInfo.getServerInfo() + " as an OSGi service");\r
+ }\r
+ catch (Exception ex) {\r
+ String msg = "Cannot start " + ServerInfo.getServerInfo();\r
+ log.error(msg, ex);\r
+ throw new RuntimeException(msg, ex);\r
+ }\r
+ finally {\r
+ current.setContextClassLoader(old);\r
+ }\r
+ }\r
+ }, "Tomcat Catalina Start Thread");\r
+\r
+ startupThread.start();\r
+ }\r
+\r
+ public void stop(BundleContext context) throws Exception {\r
+ // unpublish service first\r
+ registration.unregister();\r
+ urlRegistration.unregister();\r
+\r
+ log.info("Unpublished " + ServerInfo.getServerInfo() + " OSGi service");\r
+\r
+ // default startup procedure\r
+ ClassLoader cl = Activator.class.getClassLoader();\r
+ Thread current = Thread.currentThread();\r
+ ClassLoader old = current.getContextClassLoader();\r
+\r
+ try {\r
+ current.setContextClassLoader(cl);\r
+ //reset CCL \r
+ // current.setContextClassLoader(null);\r
+ log.info("Stopping " + ServerInfo.getServerInfo() + " ...");\r
+ server.stop();\r
+ log.info("Succesfully stopped " + ServerInfo.getServerInfo());\r
+ }\r
+ catch (Exception ex) {\r
+ log.error("Cannot stop " + ServerInfo.getServerInfo(), ex);\r
+ throw ex;\r
+ }\r
+ finally {\r
+ current.setContextClassLoader(old);\r
+ }\r
+ }\r
+\r
+ private StandardService createCatalinaServer(Bundle bundle) throws Exception {\r
+ // first try to use the XML file\r
+ URL xmlConfiguration = bundle.getResource(XML_CONF_LOCATION);\r
+\r
+ if (xmlConfiguration != null) {\r
+ log.info("Using custom XML configuration " + xmlConfiguration);\r
+ }\r
+ else {\r
+ xmlConfiguration = bundle.getResource(DEFAULT_XML_CONF_LOCATION);\r
+ if (xmlConfiguration == null)\r
+ log.error("No XML configuration found; bailing out...");\r
+ else\r
+ log.info("Using default XML configuration " + xmlConfiguration);\r
+ }\r
+\r
+ return createServerFromXML(xmlConfiguration);\r
+ }\r
+\r
+ private StandardService createServerFromXML(URL xmlConfiguration) throws IOException {\r
+ OsgiCatalina catalina = new OsgiCatalina();\r
+ catalina.setAwait(false);\r
+ catalina.setUseShutdownHook(false);\r
+ catalina.setName("Catalina");\r
+ catalina.setParentClassLoader(Thread.currentThread().getContextClassLoader());\r
+\r
+ // copy the URL file to a local temporary file (since Catalina doesn't use URL unfortunately)\r
+ File configTempFile = File.createTempFile("dm.catalina", ".cfg.xml");\r
+ configTempFile.deleteOnExit();\r
+\r
+ // copy URL to temporary file\r
+ copyURLToFile(xmlConfiguration.openStream(), new FileOutputStream(configTempFile));\r
+ log.debug("Copied configuration " + xmlConfiguration + " to temporary file " + configTempFile);\r
+\r
+ catalina.setConfigFile(configTempFile.getAbsolutePath());\r
+\r
+ catalina.load();\r
+\r
+ Server server = catalina.getServer();\r
+\r
+ return (StandardService) server.findServices()[0];\r
+ }\r
+\r
+ private void copyURLToFile(InputStream inStream, FileOutputStream outStream) {\r
+\r
+ int bytesRead;\r
+ byte[] buf = new byte[4096];\r
+ try {\r
+ while ((bytesRead = inStream.read(buf)) >= 0) {\r
+ outStream.write(buf, 0, bytesRead);\r
+ }\r
+ }\r
+ catch (IOException ex) {\r
+ throw (RuntimeException) new IllegalStateException("Cannot copy URL to file").initCause(ex);\r
+ }\r
+ finally {\r
+ try {\r
+ inStream.close();\r
+ }\r
+ catch (IOException ignore) {\r
+ }\r
+ try {\r
+ outStream.close();\r
+ }\r
+ catch (IOException ignore) {\r
+ }\r
+ }\r
+ }\r
+\r
+ private ServiceRegistration publishServerAsAService(StandardService server) {\r
+ Properties props = new Properties();\r
+ // put some extra properties to easily identify the service\r
+ props.put(Constants.SERVICE_VENDOR, "Spring Dynamic Modules");\r
+ props.put(Constants.SERVICE_DESCRIPTION, ServerInfo.getServerInfo());\r
+ props.put(Constants.BUNDLE_VERSION, ServerInfo.getServerNumber());\r
+ props.put(Constants.BUNDLE_NAME, bundleContext.getBundle().getSymbolicName());\r
+\r
+ // spring-dm specific property\r
+ props.put("org.springframework.osgi.bean.name", "tomcat-server");\r
+\r
+ // publish just the interfaces and the major classes (server/handlerWrapper)\r
+ String[] classes = new String[] { StandardService.class.getName(), Service.class.getName(),\r
+ MBeanRegistration.class.getName(), Lifecycle.class.getName() };\r
+\r
+ return bundleContext.registerService(classes, server, props);\r
+ }\r
+\r
+ private ServiceRegistration registerTomcatJNDIUrlService() {\r
+ Properties properties = new Properties();\r
+ properties.put(URLConstants.URL_HANDLER_PROTOCOL, "jndi");\r
+ final URLStreamHandler handler = new DirContextURLStreamHandler();\r
+\r
+ return bundleContext.registerService(URLStreamHandlerService.class.getName(),\r
+ new AbstractURLStreamHandlerService() {\r
+\r
+ private final static String EMPTY_STRING = "";\r
+\r
+\r
+ public URLConnection openConnection(URL u) throws IOException {\r
+ return new URL(u, EMPTY_STRING, handler).openConnection();\r
+ }\r
+ }, properties);\r
+ }\r
+}
\ No newline at end of file