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 }