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