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    }