001    /**
002     *
003     *  Licensed to the Apache Software Foundation (ASF) under one or more
004     *  contributor license agreements.  See the NOTICE file distributed with
005     *  this work for additional information regarding copyright ownership.
006     *  The ASF licenses this file to You under the Apache License, Version 2.0
007     *  (the "License"); you may not use this file except in compliance with
008     *  the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing, software
013     *  distributed under the License is distributed on an "AS IS" BASIS,
014     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     *  See the License for the specific language governing permissions and
016     *  limitations under the License.
017     */
018    package org.apache.geronimo.tomcat;
019    
020    import java.io.File;
021    import java.util.HashMap;
022    import java.util.Map;
023    import java.net.URLStreamHandlerFactory;
024    import java.net.URL;
025    
026    import javax.management.ObjectName;
027    
028    import org.apache.catalina.Container;
029    import org.apache.catalina.Context;
030    import org.apache.catalina.Engine;
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.kernel.ObjectNameUtil;
041    import org.apache.geronimo.management.geronimo.NetworkConnector;
042    import org.apache.geronimo.management.geronimo.WebManager;
043    import org.apache.geronimo.system.serverinfo.ServerInfo;
044    import org.apache.geronimo.tomcat.realm.TomcatGeronimoRealm;
045    import org.apache.geronimo.tomcat.realm.TomcatJAASRealm;
046    import org.apache.geronimo.tomcat.util.SecurityHolder;
047    import org.apache.geronimo.webservices.SoapHandler;
048    import org.apache.geronimo.webservices.WebServiceContainer;
049    import org.apache.naming.resources.DirContextURLStreamHandlerFactory;
050    
051    
052    /**
053     * Apache Tomcat GBean
054     * http://wiki.apache.org/geronimo/Tomcat
055     * http://nagoya.apache.org/jira/browse/GERONIMO-215
056     *
057     * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $
058     */
059    public class TomcatContainer implements SoapHandler, GBeanLifecycle, TomcatWebContainer {
060    
061        private static final Log log = LogFactory.getLog(TomcatContainer.class);
062    
063        /**
064         * The default value of CATALINA_HOME variable
065         */
066        private static final String DEFAULT_CATALINA_HOME = "var/catalina";
067    
068        /**
069         * Reference to the org.apache.catalina.Embedded embedded.
070         */
071        private TomcatGeronimoEmbedded embedded;
072    
073        /**
074         * Tomcat Engine that will contain the host
075         */
076        private Engine engine;
077    
078        /**
079         * Geronimo class loader
080         */
081        private ClassLoader classLoader;
082    
083        private final Map webServices = new HashMap();
084        private final String objectName;
085        private final WebManager manager;
086        private static boolean first = true;
087    
088        // Required as it's referenced by deployed webapps
089        public TomcatContainer() {
090            this.objectName = null; // is this OK??
091            setCatalinaHome(DEFAULT_CATALINA_HOME);
092            manager = null;
093        }
094    
095        /**
096         * GBean constructor (invoked dynamically when the gbean is declared in a plan)
097         */
098        public TomcatContainer(ClassLoader classLoader, String catalinaHome, ObjectRetriever engineGBean, ServerInfo serverInfo, String objectName, WebManager manager) {
099            // Register a stream handler factory for the JNDI protocol
100            URLStreamHandlerFactory streamHandlerFactory =
101                new DirContextURLStreamHandlerFactory();
102            if (first) {
103                first = false;
104                try {
105                    URL.setURLStreamHandlerFactory(streamHandlerFactory);
106                } catch (Exception e) {
107                    // Log and continue anyway, this is not critical
108                    log.error("Error registering jndi stream handler", e);
109                } catch (Throwable t) {
110                    // This is likely a dual registration
111                    log.info("Dual registration of jndi stream handler: "
112                             + t.getMessage());
113                }
114            }
115    
116    
117            if (catalinaHome == null)
118                catalinaHome = DEFAULT_CATALINA_HOME;
119    
120            setCatalinaHome(serverInfo.resolveServerPath(catalinaHome));
121    
122            if (classLoader == null) {
123                throw new IllegalArgumentException("classLoader cannot be null.");
124            }
125    
126            if (engineGBean == null) {
127                throw new IllegalArgumentException("engineGBean cannot be null.");
128            }
129    
130            this.classLoader = classLoader;
131    
132            this.engine = (Engine) engineGBean.getInternalObject();
133    
134            this.objectName = objectName;
135            this.manager = manager;
136        }
137    
138        public String getObjectName() {
139            return objectName;
140        }
141    
142        public boolean isStateManageable() {
143            return true;
144        }
145    
146        public boolean isStatisticsProvider() {
147            return false; // todo: return true once stats are integrated
148        }
149    
150        public boolean isEventProvider() {
151            return true;
152        }
153    
154        public NetworkConnector[] getConnectors() {
155            return manager.getConnectorsForContainer(this);
156        }
157    
158        public NetworkConnector[] getConnectors(String protocol) {
159            return manager.getConnectorsForContainer(this, protocol);
160        }
161    
162        public void doFail() {
163            try {
164                doStop();
165            } catch (Exception ignored) {
166            }
167        }
168    
169        /**
170         * Instantiate and start up Tomcat's Embedded class
171         * <p/>
172         * See org.apache.catalina.startup.Embedded for details (TODO: provide the link to the javadoc)
173         */
174        public void doStart() throws Exception {
175            log.debug("doStart()");
176    
177            log.debug("Endorsed Dirs set to:" + System.getProperty("java.endorsed.dirs"));
178    
179            // The comments are from the javadoc of the Embedded class
180    
181            // 1. Instantiate a new instance of this class.
182            if (embedded == null) {
183                embedded = new TomcatGeronimoEmbedded();
184            }
185    
186            // Assemble FileLogger as a gbean
187            /*
188             * FileLogger fileLog = new FileLogger(); fileLog.setDirectory("."); fileLog.setPrefix("vsjMbedTC5");
189             * fileLog.setSuffix(".log"); fileLog.setTimestamp(true);
190             */
191    
192            // 2. Set the relevant properties of this object itself. In particular,
193            // you will want to establish the default Logger to be used, as well as
194            // the default Realm if you are using container-managed security.
195            embedded.setUseNaming(false);
196    
197            //Add default contexts
198            File rootContext = new File(System.getProperty("catalina.home") + "/ROOT");
199    
200            String docBase = "";
201            if (rootContext.exists()) {
202                docBase = "ROOT";
203            }
204    
205            Container[] hosts = engine.findChildren();
206            Context defaultContext;
207            for (int i = 0; i < hosts.length; i++) {
208                defaultContext = embedded.createContext("", docBase, classLoader);
209                if (defaultContext instanceof GeronimoStandardContext) {
210                    GeronimoStandardContext ctx = (GeronimoStandardContext) defaultContext;
211                    // Without this the Tomcat FallBack Application is left behind,
212                    // MBean - ...J2EEApplication=none,J2EEServer=none,..........
213                    ctx.setJ2EEApplication(null);
214                    // TODO if objectName != null extract J2EEServer from objectName/host
215                    ctx.setJ2EEServer("geronimo");
216                }
217                hosts[i].addChild(defaultContext);
218            }
219    
220            // 6. Call addEngine() to attach this Engine to the set of defined
221            // Engines for this object.
222            embedded.addEngine(engine);
223    
224            // 9. Call start() to initiate normal operations of all the attached
225            // components.
226            embedded.start();
227        }
228    
229        public void doStop() throws Exception {
230            if (embedded != null) {
231                embedded.stop();
232                embedded = null;
233            }
234    
235        }
236    
237        /**
238         * Creates and adds the context to the running host
239         * <p/>
240         * It simply delegates the call to Tomcat's Embedded and Host classes
241         *
242         * @param ctx the context to be added
243         * @see org.apache.catalina.startup.Embedded
244         * @see org.apache.catalina.Host
245         */
246        public void addContext(TomcatContext ctx) throws Exception {
247            Context anotherCtxObj = embedded.createContext(ctx.getContextPath(), ctx.getDocBase(), ctx.getClassLoader());
248    
249            // Set the context for the Tomcat implementation
250            ctx.setContext(anotherCtxObj);
251    
252            // Have the context to set its properties if its a GeronimoStandardContext
253            if (anotherCtxObj instanceof GeronimoStandardContext) {
254                ((GeronimoStandardContext) anotherCtxObj).setContextProperties(ctx);
255            }
256            //Was a virtual server defined?
257            String virtualServer = ctx.getVirtualServer();
258            if (virtualServer == null) {
259                virtualServer = engine.getDefaultHost();
260            }
261            Container host = engine.findChild(virtualServer);
262            if (host == null) {
263                throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'.  Do you have a matching Host entry in the plan?");
264            }
265    
266            //Get the security-realm-name if there is one
267            String securityRealmName = null;
268            SecurityHolder secHolder = ctx.getSecurityHolder();
269            if (secHolder != null)
270                securityRealmName = secHolder.getSecurityRealm();
271    
272            //Did we declare a GBean at the context level?
273            if (ctx.getRealm() != null) {
274                Realm realm = ctx.getRealm();
275    
276                //Allow for the <security-realm-name> override from the
277                //geronimo-web.xml file to be used if our Realm is a JAAS type
278                if (securityRealmName != null) {
279                    if (realm instanceof JAASRealm) {
280                        ((JAASRealm) realm).setAppName(securityRealmName);
281                    }
282                }
283                anotherCtxObj.setRealm(realm);
284            } else {
285                Realm realm = host.getRealm();
286                //Check and see if we have a declared realm name and no match to a parent name
287                if (securityRealmName != null) {
288                    String parentRealmName = null;
289                    if (realm instanceof JAASRealm) {
290                        parentRealmName = ((JAASRealm) realm).getAppName();
291                    }
292    
293                    //Do we have a match to a parent?
294                    if (!securityRealmName.equals(parentRealmName)) {
295                        //No...we need to create a default adapter
296    
297                        //Is the context requiring JACC?
298                        if (secHolder.isSecurity()) {
299                            //JACC
300                            realm = new TomcatGeronimoRealm();
301                        } else {
302                            //JAAS
303                            realm = new TomcatJAASRealm();
304                        }
305    
306                        log.debug("The security-realm-name '" + securityRealmName +
307                                "' was specified and a parent (Engine/Host) is not named the same or no RealmGBean was configured for this context. " +
308                                "Creating a default " + realm.getClass().getName() +
309                                " adapter for this context.");
310    
311                        ((JAASRealm) realm).setUserClassNames("org.apache.geronimo.security.realm.providers.GeronimoUserPrincipal");
312                        ((JAASRealm) realm).setRoleClassNames("org.apache.geronimo.security.realm.providers.GeronimoGroupPrincipal");
313                        ((JAASRealm) realm).setAppName(securityRealmName);
314    
315                        anotherCtxObj.setRealm(realm);
316                    } else {
317                        //Use the parent since a name matches
318                        anotherCtxObj.setRealm(realm);
319                    }
320                } else {
321                    anotherCtxObj.setRealm(realm);
322                }
323            }
324    
325            host.addChild(anotherCtxObj);
326        }
327    
328        public void removeContext(TomcatContext ctx) {
329            Context context = ctx.getContext();
330    
331            if (context != null) {
332                if (context instanceof GeronimoStandardContext) {
333                    GeronimoStandardContext stdctx = (GeronimoStandardContext) context;
334    
335                    try {
336                        stdctx.stop();
337                        stdctx.destroy();
338                    } catch (Exception e) {
339                        throw new RuntimeException(e);
340                    }
341    
342                }
343                context.getParent().removeChild(context);
344            }
345    
346        }
347    
348        public void setCatalinaHome(String catalinaHome) {
349            System.setProperty("catalina.home", catalinaHome);
350        }
351    
352        public void addConnector(Connector connector) {
353            embedded.addConnector(connector);
354        }
355    
356        public void removeConnector(Connector connector) {
357            embedded.removeConnector(connector);
358        }
359    
360        public void addWebService(String contextPath, String[] virtualHosts, WebServiceContainer webServiceContainer, String securityRealmName, String realmName, String transportGuarantee, String authMethod, ClassLoader classLoader) throws Exception {
361            Context webServiceContext = embedded.createEJBWebServiceContext(contextPath, webServiceContainer, securityRealmName, realmName, transportGuarantee, authMethod, classLoader);
362    
363            String virtualServer;
364            if (virtualHosts != null && virtualHosts.length > 0) {
365                virtualServer = virtualHosts[0];
366            } else {
367                virtualServer = engine.getDefaultHost();
368            }
369    
370            Container host = engine.findChild(virtualServer);
371            if (host == null) {
372                throw new IllegalArgumentException("Invalid virtual host '" + virtualServer + "'.  Do you have a matchiing Host entry in the plan?");
373            }
374    
375            host.addChild(webServiceContext);
376            webServices.put(contextPath, webServiceContext);
377        }
378    
379        public void removeWebService(String contextPath) {
380            TomcatEJBWebServiceContext context = (TomcatEJBWebServiceContext) webServices.get(contextPath);
381            try {
382                context.stop();
383                context.destroy();
384            } catch (Exception e) {
385                throw new RuntimeException(e);
386            }
387            context.getParent().removeChild(context);
388            webServices.remove(contextPath);
389        }
390    
391        public static final GBeanInfo GBEAN_INFO;
392    
393        static {
394            GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic("Tomcat Web Container", TomcatContainer.class);
395    
396            infoFactory.setConstructor(new String[]{"classLoader", "catalinaHome", "EngineGBean", "ServerInfo", "objectName", "WebManager"});
397    
398            infoFactory.addAttribute("classLoader", ClassLoader.class, false);
399    
400            infoFactory.addAttribute("catalinaHome", String.class, true);
401    
402            infoFactory.addAttribute("objectName", String.class, false);
403    
404            infoFactory.addReference("EngineGBean", ObjectRetriever.class, NameFactory.GERONIMO_SERVICE);
405    
406            infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
407            infoFactory.addReference("WebManager", WebManager.class);
408    
409            infoFactory.addOperation("addContext", new Class[]{TomcatContext.class});
410            infoFactory.addOperation("removeContext", new Class[]{TomcatContext.class});
411    
412            infoFactory.addOperation("addConnector", new Class[]{Connector.class});
413            infoFactory.addOperation("removeConnector", new Class[]{Connector.class});
414    
415            infoFactory.addInterface(SoapHandler.class);
416            infoFactory.addInterface(TomcatWebContainer.class);
417    
418            GBEAN_INFO = infoFactory.getBeanInfo();
419        }
420    
421        public static GBeanInfo getGBeanInfo() {
422            return GBEAN_INFO;
423        }
424    
425    }