001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.geronimo.tomcat;
018
019 import java.io.File;
020 import java.net.URL;
021 import java.net.URLStreamHandlerFactory;
022 import java.util.HashMap;
023 import java.util.Map;
024
025 import javax.management.ObjectName;
026
027 import org.apache.catalina.Container;
028 import org.apache.catalina.Context;
029 import org.apache.catalina.Engine;
030 import org.apache.catalina.LifecycleListener;
031 import org.apache.catalina.Realm;
032 import org.apache.catalina.connector.Connector;
033 import org.apache.catalina.realm.JAASRealm;
034 import org.apache.commons.logging.Log;
035 import org.apache.commons.logging.LogFactory;
036 import org.apache.geronimo.gbean.GBeanInfo;
037 import org.apache.geronimo.gbean.GBeanInfoBuilder;
038 import org.apache.geronimo.gbean.GBeanLifecycle;
039 import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
040 import org.apache.geronimo.management.geronimo.NetworkConnector;
041 import org.apache.geronimo.management.geronimo.WebManager;
042 import org.apache.geronimo.system.serverinfo.ServerInfo;
043 import org.apache.geronimo.tomcat.realm.TomcatGeronimoRealm;
044 import org.apache.geronimo.tomcat.realm.TomcatJAASRealm;
045 import org.apache.geronimo.tomcat.util.SecurityHolder;
046 import org.apache.geronimo.webservices.SoapHandler;
047 import org.apache.geronimo.webservices.WebServiceContainer;
048 import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
049
050
051 /**
052 * Apache Tomcat GBean
053 * http://wiki.apache.org/geronimo/Tomcat
054 * http://nagoya.apache.org/jira/browse/GERONIMO-215
055 *
056 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
057 */
058 public class TomcatContainer implements SoapHandler, GBeanLifecycle, TomcatWebContainer {
059
060 private static final Log log = LogFactory.getLog(TomcatContainer.class);
061
062 /**
063 * The default value of CATALINA_HOME variable
064 */
065 private static final String DEFAULT_CATALINA_HOME = "var/catalina";
066
067 /**
068 * Reference to the org.apache.catalina.Embedded embedded.
069 */
070 private TomcatGeronimoEmbedded embedded;
071
072 /**
073 * Tomcat Engine that will contain the host
074 */
075 private Engine engine;
076
077 /**
078 * Geronimo class loader
079 */
080 private ClassLoader classLoader;
081
082 private final Map webServices = new HashMap();
083 private final String objectName;
084 private final String[] applicationListeners;
085 private final WebManager manager;
086 private static boolean first = true;
087 private final LifecycleListenerGBean listenerChain;
088
089 // Required as it's referenced by deployed webapps
090 public TomcatContainer() {
091 this.objectName = null; // is this OK??
092 this.applicationListeners = null;
093 setCatalinaHome(DEFAULT_CATALINA_HOME);
094 manager = null;
095 listenerChain=null;
096 }
097
098 /**
099 * GBean constructor (invoked dynamically when the gbean is declared in a plan)
100 */
101 public TomcatContainer(
102 ClassLoader classLoader,
103 String catalinaHome,
104 String[] applicationListeners,
105 ObjectRetriever engineGBean,
106 LifecycleListenerGBean listenerChain,
107 ServerInfo serverInfo,
108 String objectName,
109 WebManager manager) {
110 // Register a stream handler factory for the JNDI protocol
111 URLStreamHandlerFactory streamHandlerFactory =
112 new DirContextURLStreamHandlerFactory();
113 if (first) {
114 first = false;
115 try {
116 URL.setURLStreamHandlerFactory(streamHandlerFactory);
117 } catch (Exception e) {
118 // Log and continue anyway, this is not critical
119 log.error("Error registering jndi stream handler", e);
120 } catch (Throwable t) {
121 // This is likely a dual registration
122 log.info("Dual registration of jndi stream handler: "
123 + t.getMessage());
124 }
125 }
126
127
128 if (catalinaHome == null)
129 catalinaHome = DEFAULT_CATALINA_HOME;
130
131 setCatalinaHome(serverInfo.resolveServerPath(catalinaHome));
132
133 if (classLoader == null) {
134 throw new IllegalArgumentException("classLoader cannot be null.");
135 }
136
137 if (engineGBean == null) {
138 throw new IllegalArgumentException("engineGBean cannot be null.");
139 }
140
141
142 this.classLoader = classLoader;
143
144 this.engine = (Engine) engineGBean.getInternalObject();
145 this.listenerChain = listenerChain;
146 this.objectName = objectName;
147 this.applicationListeners = applicationListeners;
148 this.manager = manager;
149 }
150
151 public String getObjectName() {
152 return objectName;
153 }
154
155 public boolean isStateManageable() {
156 return true;
157 }
158
159 public boolean isStatisticsProvider() {
160 return false; // todo: return true once stats are integrated
161 }
162
163 public boolean isEventProvider() {
164 return true;
165 }
166
167 public NetworkConnector[] getConnectors() {
168 return manager.getConnectorsForContainer(this);
169 }
170
171 public NetworkConnector[] getConnectors(String protocol) {
172 return manager.getConnectorsForContainer(this, protocol);
173 }
174
175 public void doFail() {
176 try {
177 doStop();
178 } catch (Exception ignored) {
179 }
180 }
181
182 /**
183 * Instantiate and start up Tomcat's Embedded class
184 * <p/>
185 * See org.apache.catalina.startup.Embedded for details (TODO: provide the link to the javadoc)
186 */
187 public void doStart() throws Exception {
188 if (log.isDebugEnabled()) {
189 log.debug("doStart()");
190 log.debug("Java Endorsed Dirs set to:" + System.getProperty("java.endorsed.dirs"));
191 log.debug("Java Ext Dirs set to:" + System.getProperty("java.ext.dirs"));
192 }
193
194 // The comments are from the javadoc of the Embedded class
195
196 // 1. Instantiate a new instance of this class.
197 if (embedded == null) {
198 embedded = new TomcatGeronimoEmbedded();
199 }
200
201 // Assemble FileLogger as a gbean
202 /*
203 * FileLogger fileLog = new FileLogger(); fileLog.setDirectory("."); fileLog.setPrefix("vsjMbedTC5");
204 * fileLog.setSuffix(".log"); fileLog.setTimestamp(true);
205 */
206
207 // 2. Set the relevant properties of this object itself. In particular,
208 // you will want to establish the default Logger to be used, as well as
209 // the default Realm if you are using container-managed security.
210 embedded.setUseNaming(false);
211
212 //Add default contexts
213 File rootContext = new File(System.getProperty("catalina.home") + "/ROOT");
214
215 String docBase = "";
216 if (rootContext.exists()) {
217 docBase = "ROOT";
218 }
219
220 Container[] hosts = engine.findChildren();
221 Context defaultContext;
222 ObjectName objName = objectName == null ? null : ObjectName.getInstance(objectName);
223 for (int i = 0; i < hosts.length; i++) {
224 defaultContext = embedded.createContext("", docBase, classLoader);
225 if(objName != null) {
226 defaultContext.setName(objName.getKeyProperty(NameFactory.J2EE_NAME));
227 }
228 if (defaultContext instanceof GeronimoStandardContext) {
229 GeronimoStandardContext ctx = (GeronimoStandardContext) defaultContext;
230 // Without this the Tomcat FallBack Application is left behind,
231 // MBean - ...J2EEApplication=none,J2EEServer=none,..........
232 ctx.setJ2EEApplication(null);
233 // if objectName != null extract J2EEServer from objectName/host
234 ctx.setJ2EEServer(objName == null ? "geronimo" : objName.getKeyProperty(NameFactory.J2EE_SERVER));
235 ctx.setJavaVMs(new String[]{});
236 ctx.setServer(objName == null ? "geronimo" : objName.getKeyProperty(NameFactory.J2EE_SERVER));
237 }
238 hosts[i].addChild(defaultContext);
239 }
240
241 // 6. Call addEngine() to attach this Engine to the set of defined
242 // Engines for this object.
243 embedded.addEngine(engine);
244
245 if (listenerChain != null){
246 LifecycleListenerGBean listenerGBean = listenerChain;
247 while(listenerGBean != null){
248 embedded.addLifecycleListener((LifecycleListener)listenerGBean.getInternalObject());
249 listenerGBean = listenerGBean.getNextListener();
250 }
251 }
252
253 // 9. Call start() to initiate normal operations of all the attached
254 // components.
255 embedded.start();
256 }
257
258 public void doStop() throws Exception {
259 if (embedded != null) {
260 embedded.stop();
261 embedded = null;
262 }
263
264 }
265
266 /**
267 * Creates and adds the context to the running host
268 * <p/>
269 * It simply delegates the call to Tomcat's Embedded and Host classes
270 *
271 * @param ctx the context to be added
272 * @see org.apache.catalina.startup.Embedded
273 * @see org.apache.catalina.Host
274 */
275 public void addContext(TomcatContext ctx) throws Exception {
276 Context anotherCtxObj = embedded.createContext(ctx.getContextPath(), ctx.getDocBase(), ctx.getClassLoader());
277
278 // Set the context for the Tomcat implementation
279 ctx.setContext(anotherCtxObj);
280
281 // Have the context to set its properties if its a GeronimoStandardContext
282 if (anotherCtxObj instanceof GeronimoStandardContext) {
283 ((GeronimoStandardContext) anotherCtxObj).setContextProperties(ctx);
284 }
285 //Was a virtual server defined?
286 String virtualServer = ctx.getVirtualServer();
287 if (virtualServer == null) {
288 virtualServer = engine.getDefaultHost();
289 }
290 Container host = engine.findChild(virtualServer);
291 if (host == null) {
292 throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'. Do you have a matching Host entry in the plan?");
293 }
294
295 //Get the security-realm-name if there is one
296 String securityRealmName = null;
297 SecurityHolder secHolder = ctx.getSecurityHolder();
298 if (secHolder != null)
299 securityRealmName = secHolder.getSecurityRealm();
300
301 //Did we declare a GBean at the context level?
302 if (ctx.getRealm() != null) {
303 Realm realm = ctx.getRealm();
304
305 //Allow for the <security-realm-name> override from the
306 //geronimo-web.xml file to be used if our Realm is a JAAS type
307 if (securityRealmName != null) {
308 if (realm instanceof JAASRealm) {
309 ((JAASRealm) realm).setAppName(securityRealmName);
310 }
311 }
312 anotherCtxObj.setRealm(realm);
313 } else {
314 Realm realm = host.getRealm();
315 //Check and see if we have a declared realm name and no match to a parent name
316 if (securityRealmName != null) {
317 String parentRealmName = null;
318 if (realm instanceof JAASRealm) {
319 parentRealmName = ((JAASRealm) realm).getAppName();
320 }
321
322 //Do we have a match to a parent?
323 if (!securityRealmName.equals(parentRealmName)) {
324 //No...we need to create a default adapter
325
326 //Is the context requiring JACC?
327 if (secHolder.isSecurity()) {
328 //JACC
329 realm = new TomcatGeronimoRealm();
330 } else {
331 //JAAS
332 realm = new TomcatJAASRealm();
333 }
334
335 if (log.isDebugEnabled()) {
336 log.debug("The security-realm-name '" + securityRealmName +
337 "' was specified and a parent (Engine/Host) is not named the same or no RealmGBean was configured for this context. " +
338 "Creating a default " + realm.getClass().getName() +
339 " adapter for this context.");
340 }
341
342 ((JAASRealm) realm).setUserClassNames("org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal");
343 ((JAASRealm) realm).setRoleClassNames("org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal");
344 ((JAASRealm) realm).setAppName(securityRealmName);
345
346 anotherCtxObj.setRealm(realm);
347 } else {
348 //Use the parent since a name matches
349 anotherCtxObj.setRealm(realm);
350 }
351 } else {
352 anotherCtxObj.setRealm(realm);
353 }
354 }
355
356 // add application listeners to the new context
357 if (applicationListeners != null) {
358 for (String listener : applicationListeners) {
359 anotherCtxObj.addApplicationListener(listener);
360 }
361 }
362
363 try {
364 host.addChild(anotherCtxObj);
365 } catch (IllegalArgumentException ex) {
366 log.error("Unable to add the child container: " + anotherCtxObj.getName()
367 + " . Please check if your project's context-root is unique.", ex);
368 }
369 }
370
371 public void removeContext(TomcatContext ctx) {
372 Context context = ctx.getContext();
373
374 if (context != null) {
375 if (context instanceof GeronimoStandardContext) {
376 GeronimoStandardContext stdctx = (GeronimoStandardContext) context;
377
378 try {
379 stdctx.kill();
380 } catch (Exception e) {
381 throw new RuntimeException(e);
382 }
383
384 }
385 if (context.getParent() != null) {
386 context.getParent().removeChild(context);
387 }
388 }
389
390 }
391
392 public void setCatalinaHome(String catalinaHome) {
393 System.setProperty("catalina.home", catalinaHome);
394 }
395
396 public void addConnector(Connector connector) {
397 embedded.addConnector(connector);
398 }
399
400 public void removeConnector(Connector connector) {
401 embedded.removeConnector(connector);
402 }
403
404 public void addWebService(String contextPath, String[] virtualHosts, WebServiceContainer webServiceContainer, String securityRealmName, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader) throws Exception {
405 Context webServiceContext = embedded.createEJBWebServiceContext(contextPath, webServiceContainer, securityRealmName, realmName, transportGuarantee, authMethod, classLoader);
406
407 String virtualServer;
408 if (virtualHosts != null && virtualHosts.length > 0) {
409 virtualServer = virtualHosts[0];
410 } else {
411 virtualServer = engine.getDefaultHost();
412 }
413
414 Container host = engine.findChild(virtualServer);
415 if (host == null) {
416 throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'. Do you have a matchiing Host entry in the plan?");
417 }
418
419 host.addChild(webServiceContext);
420 webServices.put(contextPath, webServiceContext);
421 }
422
423 public void removeWebService(String contextPath) {
424 TomcatEJBWebServiceContext context = (TomcatEJBWebServiceContext) webServices.get(contextPath);
425 try {
426 context.stop();
427 context.destroy();
428 } catch (Exception e) {
429 throw new RuntimeException(e);
430 }
431 context.getParent().removeChild(context);
432 webServices.remove(contextPath);
433 }
434
435 public static final GBeanInfo GBEAN_INFO;
436
437 static {
438 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Tomcat Web Container", TomcatContainer.class);
439
440 infoFactory.setConstructor(new String[]{
441 "classLoader",
442 "catalinaHome",
443 "applicationListeners",
444 "EngineGBean",
445 "LifecycleListenerChain",
446 "ServerInfo",
447 "objectName",
448 "WebManager"});
449
450 infoFactory.addAttribute("classLoader", ClassLoader.class, false);
451
452 infoFactory.addAttribute("catalinaHome", String.class, true);
453
454 infoFactory.addAttribute("applicationListeners", String[].class, true);
455
456 infoFactory.addAttribute("objectName", String.class, false);
457
458 infoFactory.addReference("EngineGBean", ObjectRetriever.class, NameFactory.GERONIMO_SERVICE);
459 infoFactory.addReference("LifecycleListenerChain", LifecycleListenerGBean.class, LifecycleListenerGBean.J2EE_TYPE);
460
461 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
462 infoFactory.addReference("WebManager", WebManager.class);
463
464 infoFactory.addOperation("addContext", new Class[]{TomcatContext.class});
465 infoFactory.addOperation("removeContext", new Class[]{TomcatContext.class});
466
467 infoFactory.addOperation("addConnector", new Class[]{Connector.class});
468 infoFactory.addOperation("removeConnector", new Class[]{Connector.class});
469
470 infoFactory.addInterface(SoapHandler.class);
471 infoFactory.addInterface(TomcatWebContainer.class);
472
473 GBEAN_INFO = infoFactory.getBeanInfo();
474 }
475
476 public static GBeanInfo getGBeanInfo() {
477 return GBEAN_INFO;
478 }
479
480 }