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.List; 024 import java.util.Map; 025 026 import org.apache.catalina.Container; 027 import org.apache.catalina.Context; 028 import org.apache.catalina.Engine; 029 import org.apache.catalina.LifecycleListener; 030 import org.apache.catalina.Realm; 031 import org.apache.catalina.connector.Connector; 032 import org.apache.catalina.core.StandardEngine; 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: 551692 $ $Date: 2007-06-28 16:36:09 -0400 (Thu, 28 Jun 2007) $ 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 log.debug("doStart()"); 189 190 log.debug("Endorsed Dirs set to:" + System.getProperty("java.endorsed.dirs")); 191 192 // The comments are from the javadoc of the Embedded class 193 194 // 1. Instantiate a new instance of this class. 195 if (embedded == null) { 196 embedded = new TomcatGeronimoEmbedded(); 197 } 198 199 // Assemble FileLogger as a gbean 200 /* 201 * FileLogger fileLog = new FileLogger(); fileLog.setDirectory("."); fileLog.setPrefix("vsjMbedTC5"); 202 * fileLog.setSuffix(".log"); fileLog.setTimestamp(true); 203 */ 204 205 // 2. Set the relevant properties of this object itself. In particular, 206 // you will want to establish the default Logger to be used, as well as 207 // the default Realm if you are using container-managed security. 208 embedded.setUseNaming(false); 209 210 //Add default contexts 211 File rootContext = new File(System.getProperty("catalina.home") + "/ROOT"); 212 213 String docBase = ""; 214 if (rootContext.exists()) { 215 docBase = "ROOT"; 216 } 217 218 Container[] hosts = engine.findChildren(); 219 Context defaultContext; 220 for (int i = 0; i < hosts.length; i++) { 221 defaultContext = embedded.createContext("", docBase, classLoader); 222 if (defaultContext instanceof GeronimoStandardContext) { 223 GeronimoStandardContext ctx = (GeronimoStandardContext) defaultContext; 224 // Without this the Tomcat FallBack Application is left behind, 225 // MBean - ...J2EEApplication=none,J2EEServer=none,.......... 226 ctx.setJ2EEApplication(null); 227 // TODO if objectName != null extract J2EEServer from objectName/host 228 ctx.setJ2EEServer("geronimo"); 229 ctx.setJavaVMs(new String[]{}); 230 ctx.setServer("geronimo"); 231 } 232 hosts[i].addChild(defaultContext); 233 } 234 235 // 6. Call addEngine() to attach this Engine to the set of defined 236 // Engines for this object. 237 embedded.addEngine(engine); 238 239 if (listenerChain != null){ 240 LifecycleListenerGBean listenerGBean = listenerChain; 241 while(listenerGBean != null){ 242 embedded.addLifecycleListener((LifecycleListener)listenerGBean.getInternalObject()); 243 listenerGBean = listenerGBean.getNextListener(); 244 } 245 } 246 247 // 9. Call start() to initiate normal operations of all the attached 248 // components. 249 embedded.start(); 250 } 251 252 public void doStop() throws Exception { 253 if (embedded != null) { 254 embedded.stop(); 255 embedded = null; 256 } 257 258 } 259 260 /** 261 * Creates and adds the context to the running host 262 * <p/> 263 * It simply delegates the call to Tomcat's Embedded and Host classes 264 * 265 * @param ctx the context to be added 266 * @see org.apache.catalina.startup.Embedded 267 * @see org.apache.catalina.Host 268 */ 269 public void addContext(TomcatContext ctx) throws Exception { 270 Context anotherCtxObj = embedded.createContext(ctx.getContextPath(), ctx.getDocBase(), ctx.getClassLoader()); 271 272 // Set the context for the Tomcat implementation 273 ctx.setContext(anotherCtxObj); 274 275 // Have the context to set its properties if its a GeronimoStandardContext 276 if (anotherCtxObj instanceof GeronimoStandardContext) { 277 ((GeronimoStandardContext) anotherCtxObj).setContextProperties(ctx); 278 } 279 //Was a virtual server defined? 280 String virtualServer = ctx.getVirtualServer(); 281 if (virtualServer == null) { 282 virtualServer = engine.getDefaultHost(); 283 } 284 Container host = engine.findChild(virtualServer); 285 if (host == null) { 286 throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'. Do you have a matching Host entry in the plan?"); 287 } 288 289 //Get the security-realm-name if there is one 290 String securityRealmName = null; 291 SecurityHolder secHolder = ctx.getSecurityHolder(); 292 if (secHolder != null) 293 securityRealmName = secHolder.getSecurityRealm(); 294 295 //Did we declare a GBean at the context level? 296 if (ctx.getRealm() != null) { 297 Realm realm = ctx.getRealm(); 298 299 //Allow for the <security-realm-name> override from the 300 //geronimo-web.xml file to be used if our Realm is a JAAS type 301 if (securityRealmName != null) { 302 if (realm instanceof JAASRealm) { 303 ((JAASRealm) realm).setAppName(securityRealmName); 304 } 305 } 306 anotherCtxObj.setRealm(realm); 307 } else { 308 Realm realm = host.getRealm(); 309 //Check and see if we have a declared realm name and no match to a parent name 310 if (securityRealmName != null) { 311 String parentRealmName = null; 312 if (realm instanceof JAASRealm) { 313 parentRealmName = ((JAASRealm) realm).getAppName(); 314 } 315 316 //Do we have a match to a parent? 317 if (!securityRealmName.equals(parentRealmName)) { 318 //No...we need to create a default adapter 319 320 //Is the context requiring JACC? 321 if (secHolder.isSecurity()) { 322 //JACC 323 realm = new TomcatGeronimoRealm(); 324 } else { 325 //JAAS 326 realm = new TomcatJAASRealm(); 327 } 328 329 log.debug("The security-realm-name '" + securityRealmName + 330 "' was specified and a parent (Engine/Host) is not named the same or no RealmGBean was configured for this context. " + 331 "Creating a default " + realm.getClass().getName() + 332 " adapter for this context."); 333 334 ((JAASRealm) realm).setUserClassNames("org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal"); 335 ((JAASRealm) realm).setRoleClassNames("org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal"); 336 ((JAASRealm) realm).setAppName(securityRealmName); 337 338 anotherCtxObj.setRealm(realm); 339 } else { 340 //Use the parent since a name matches 341 anotherCtxObj.setRealm(realm); 342 } 343 } else { 344 anotherCtxObj.setRealm(realm); 345 } 346 } 347 348 // add application listeners to the new context 349 if (applicationListeners != null) { 350 for (String listener : applicationListeners) { 351 anotherCtxObj.addApplicationListener(listener); 352 } 353 } 354 355 host.addChild(anotherCtxObj); 356 } 357 358 public void removeContext(TomcatContext ctx) { 359 Context context = ctx.getContext(); 360 361 if (context != null) { 362 if (context instanceof GeronimoStandardContext) { 363 GeronimoStandardContext stdctx = (GeronimoStandardContext) context; 364 365 try { 366 stdctx.stop(); 367 stdctx.destroy(); 368 } catch (Exception e) { 369 throw new RuntimeException(e); 370 } 371 372 } 373 context.getParent().removeChild(context); 374 } 375 376 } 377 378 public void setCatalinaHome(String catalinaHome) { 379 System.setProperty("catalina.home", catalinaHome); 380 } 381 382 public void addConnector(Connector connector) { 383 embedded.addConnector(connector); 384 } 385 386 public void removeConnector(Connector connector) { 387 embedded.removeConnector(connector); 388 } 389 390 public void addWebService(String contextPath, String[] virtualHosts, WebServiceContainer webServiceContainer, String securityRealmName, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader) throws Exception { 391 Context webServiceContext = embedded.createEJBWebServiceContext(contextPath, webServiceContainer, securityRealmName, realmName, transportGuarantee, authMethod, classLoader); 392 393 String virtualServer; 394 if (virtualHosts != null && virtualHosts.length > 0) { 395 virtualServer = virtualHosts[0]; 396 } else { 397 virtualServer = engine.getDefaultHost(); 398 } 399 400 Container host = engine.findChild(virtualServer); 401 if (host == null) { 402 throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'. Do you have a matchiing Host entry in the plan?"); 403 } 404 405 host.addChild(webServiceContext); 406 webServices.put(contextPath, webServiceContext); 407 } 408 409 public void removeWebService(String contextPath) { 410 TomcatEJBWebServiceContext context = (TomcatEJBWebServiceContext) webServices.get(contextPath); 411 try { 412 context.stop(); 413 context.destroy(); 414 } catch (Exception e) { 415 throw new RuntimeException(e); 416 } 417 context.getParent().removeChild(context); 418 webServices.remove(contextPath); 419 } 420 421 public static final GBeanInfo GBEAN_INFO; 422 423 static { 424 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Tomcat Web Container", TomcatContainer.class); 425 426 infoFactory.setConstructor(new String[]{ 427 "classLoader", 428 "catalinaHome", 429 "applicationListeners", 430 "EngineGBean", 431 "LifecycleListenerChain", 432 "ServerInfo", 433 "objectName", 434 "WebManager"}); 435 436 infoFactory.addAttribute("classLoader", ClassLoader.class, false); 437 438 infoFactory.addAttribute("catalinaHome", String.class, true); 439 440 infoFactory.addAttribute("applicationListeners", String[].class, true); 441 442 infoFactory.addAttribute("objectName", String.class, false); 443 444 infoFactory.addReference("EngineGBean", ObjectRetriever.class, NameFactory.GERONIMO_SERVICE); 445 infoFactory.addReference("LifecycleListenerChain", LifecycleListenerGBean.class, LifecycleListenerGBean.J2EE_TYPE); 446 447 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean"); 448 infoFactory.addReference("WebManager", WebManager.class); 449 450 infoFactory.addOperation("addContext", new Class[]{TomcatContext.class}); 451 infoFactory.addOperation("removeContext", new Class[]{TomcatContext.class}); 452 453 infoFactory.addOperation("addConnector", new Class[]{Connector.class}); 454 infoFactory.addOperation("removeConnector", new Class[]{Connector.class}); 455 456 infoFactory.addInterface(SoapHandler.class); 457 infoFactory.addInterface(TomcatWebContainer.class); 458 459 GBEAN_INFO = infoFactory.getBeanInfo(); 460 } 461 462 public static GBeanInfo getGBeanInfo() { 463 return GBEAN_INFO; 464 } 465 466 }