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 }