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    }