2 * Copyright 2006-2008 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org
.springframework
.osgi
.web
.tomcat
.internal
;
20 import java
.io
.FileOutputStream
;
21 import java
.io
.IOException
;
22 import java
.io
.InputStream
;
23 import java
.net
.MalformedURLException
;
25 import java
.net
.URLConnection
;
26 import java
.net
.URLStreamHandler
;
27 import java
.util
.Properties
;
29 import javax
.management
.MBeanRegistration
;
31 import org
.apache
.catalina
.Lifecycle
;
32 import org
.apache
.catalina
.Server
;
33 import org
.apache
.catalina
.Service
;
34 import org
.apache
.catalina
.connector
.Connector
;
35 import org
.apache
.catalina
.core
.StandardService
;
36 import org
.apache
.catalina
.util
.ServerInfo
;
37 import org
.apache
.commons
.logging
.Log
;
38 import org
.apache
.commons
.logging
.LogFactory
;
39 import org
.apache
.naming
.resources
.DirContextURLStreamHandler
;
40 import org
.argeo
.catalina
.start
.CatalinaActivator
;
41 import org
.osgi
.framework
.Bundle
;
42 import org
.osgi
.framework
.BundleActivator
;
43 import org
.osgi
.framework
.BundleContext
;
44 import org
.osgi
.framework
.Constants
;
45 import org
.osgi
.framework
.ServiceRegistration
;
46 import org
.osgi
.service
.url
.AbstractURLStreamHandlerService
;
47 import org
.osgi
.service
.url
.URLConstants
;
48 import org
.osgi
.service
.url
.URLStreamHandlerService
;
51 * Simple activator for starting Apache Tomcat Catalina container inside OSGi
52 * using Tomcat's XML configuration files.
55 * This activator looks initially for a <code>conf/server.xml</code> file
56 * falling back to <code>conf/default-server.xml</code>. This allows the default
57 * configuration to be tweaked through fragments for example.
61 public class Activator
implements BundleActivator
{
64 private static final Log log
= LogFactory
.getLog(Activator
.class);
66 /** default XML configuration */
67 private static final String DEFAULT_XML_CONF_LOCATION
= "conf/default-server.xml";
69 /** user-configurable XML configuration */
70 private static final String XML_CONF_LOCATION
= "conf/server.xml";
72 private BundleContext bundleContext
;
74 private StandardService server
;
76 private ServiceRegistration registration
, urlRegistration
;
78 private Thread startupThread
;
80 public void start(BundleContext context
) throws Exception
{
81 this.bundleContext
= context
;
82 // do the initialization on a different thread
83 // so the activator finishes fast
84 startupThread
= new Thread(new Runnable() {
87 log
.info("Starting " + ServerInfo
.getServerInfo() + " ...");
89 // default startup procedure
90 ClassLoader cl
= Activator
.class.getClassLoader();
91 Thread current
= Thread
.currentThread();
92 ClassLoader old
= current
.getContextClassLoader();
95 current
.setContextClassLoader(cl
);
97 server
= createCatalinaServer(bundleContext
.getBundle());
101 Connector
[] connectors
= server
.findConnectors();
102 for (int i
= 0; i
< connectors
.length
; i
++) {
103 Connector conn
= connectors
[i
];
104 log
.info("Succesfully started "
105 + ServerInfo
.getServerInfo() + " @ "
106 + conn
.getDomain() + ":" + conn
.getPort());
109 // register URL service
110 urlRegistration
= registerTomcatJNDIUrlService();
111 // publish server as an OSGi service
112 registration
= publishServerAsAService(server
);
113 log
.info("Published " + ServerInfo
.getServerInfo()
114 + " as an OSGi service");
115 } catch (Exception ex
) {
116 String msg
= "Cannot start " + ServerInfo
.getServerInfo();
118 throw new RuntimeException(msg
, ex
);
120 current
.setContextClassLoader(old
);
123 }, "Tomcat Catalina Start Thread");
125 startupThread
.start();
128 public void stop(BundleContext context
) throws Exception
{
129 // unpublish service first
130 registration
.unregister();
131 urlRegistration
.unregister();
133 log
.info("Unpublished " + ServerInfo
.getServerInfo() + " OSGi service");
135 // default startup procedure
136 ClassLoader cl
= Activator
.class.getClassLoader();
137 Thread current
= Thread
.currentThread();
138 ClassLoader old
= current
.getContextClassLoader();
141 current
.setContextClassLoader(cl
);
143 // current.setContextClassLoader(null);
144 log
.info("Stopping " + ServerInfo
.getServerInfo() + " ...");
146 log
.info("Succesfully stopped " + ServerInfo
.getServerInfo());
147 } catch (Exception ex
) {
148 log
.error("Cannot stop " + ServerInfo
.getServerInfo(), ex
);
151 current
.setContextClassLoader(old
);
155 private StandardService
createCatalinaServer(Bundle bundle
)
157 URL xmlConfiguration
= null;
159 if (System
.getProperty(CatalinaActivator
.ARGEO_SERVER_TOMCAT_CONFIG
) != null) {
160 String customConfig
= System
161 .getProperty(CatalinaActivator
.ARGEO_SERVER_TOMCAT_CONFIG
);
163 xmlConfiguration
= new URL(customConfig
);
164 } catch (MalformedURLException e
) {
165 // within this bundle
166 // typically 'default-server-ssl.xml'
167 xmlConfiguration
= bundle
.getResource(customConfig
);
171 xmlConfiguration
= bundle
.getResource(XML_CONF_LOCATION
);
174 if (xmlConfiguration
!= null) {
175 log
.info("Using custom XML configuration " + xmlConfiguration
);
177 xmlConfiguration
= bundle
.getResource(DEFAULT_XML_CONF_LOCATION
);
178 if (xmlConfiguration
== null)
179 log
.error("No XML configuration found; bailing out...");
181 log
.info("Using default XML configuration " + xmlConfiguration
);
184 return createServerFromXML(xmlConfiguration
);
187 private StandardService
createServerFromXML(URL xmlConfiguration
)
189 OsgiCatalina catalina
= new OsgiCatalina();
190 catalina
.setAwait(false);
191 catalina
.setUseShutdownHook(false);
192 catalina
.setName("Catalina");
193 catalina
.setParentClassLoader(Thread
.currentThread()
194 .getContextClassLoader());
196 // copy the URL file to a local temporary file (since Catalina doesn't
197 // use URL unfortunately)
198 File configTempFile
= File
.createTempFile("dm.catalina", ".cfg.xml");
199 configTempFile
.deleteOnExit();
201 // copy URL to temporary file
202 copyURLToFile(xmlConfiguration
.openStream(), new FileOutputStream(
204 log
.debug("Copied configuration " + xmlConfiguration
205 + " to temporary file " + configTempFile
);
207 catalina
.setConfigFile(configTempFile
.getAbsolutePath());
211 Server server
= catalina
.getServer();
213 return (StandardService
) server
.findServices()[0];
216 private void copyURLToFile(InputStream inStream
, FileOutputStream outStream
) {
219 byte[] buf
= new byte[4096];
221 while ((bytesRead
= inStream
.read(buf
)) >= 0) {
222 outStream
.write(buf
, 0, bytesRead
);
224 } catch (IOException ex
) {
225 throw (RuntimeException
) new IllegalStateException(
226 "Cannot copy URL to file").initCause(ex
);
230 } catch (IOException ignore
) {
234 } catch (IOException ignore
) {
239 private ServiceRegistration
publishServerAsAService(StandardService server
) {
240 Properties props
= new Properties();
241 // put some extra properties to easily identify the service
242 props
.put(Constants
.SERVICE_VENDOR
, "Spring Dynamic Modules");
243 props
.put(Constants
.SERVICE_DESCRIPTION
, ServerInfo
.getServerInfo());
244 props
.put(Constants
.BUNDLE_VERSION
, ServerInfo
.getServerNumber());
245 props
.put(Constants
.BUNDLE_NAME
, bundleContext
.getBundle()
248 // spring-dm specific property
249 props
.put("org.springframework.osgi.bean.name", "tomcat-server");
251 // publish just the interfaces and the major classes
252 // (server/handlerWrapper)
253 String
[] classes
= new String
[] { StandardService
.class.getName(),
254 Service
.class.getName(), MBeanRegistration
.class.getName(),
255 Lifecycle
.class.getName() };
257 return bundleContext
.registerService(classes
, server
, props
);
260 private ServiceRegistration
registerTomcatJNDIUrlService() {
261 Properties properties
= new Properties();
262 properties
.put(URLConstants
.URL_HANDLER_PROTOCOL
, "jndi");
263 final URLStreamHandler handler
= new DirContextURLStreamHandler();
265 return bundleContext
.registerService(
266 URLStreamHandlerService
.class.getName(),
267 new AbstractURLStreamHandlerService() {
269 private final static String EMPTY_STRING
= "";
271 public URLConnection
openConnection(URL u
)
273 return new URL(u
, EMPTY_STRING
, handler
)