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    
018    package org.apache.geronimo.tomcat;
019    
020    import java.net.MalformedURLException;
021    import java.net.URI;
022    import java.net.URL;
023    import java.util.ArrayList;
024    import java.util.HashMap;
025    import java.util.Hashtable;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Set;
030    
031    import javax.management.MalformedObjectNameException;
032    import javax.management.ObjectName;
033    import javax.management.j2ee.statistics.Stats;
034    import javax.naming.directory.DirContext;
035    import javax.transaction.TransactionManager;
036    import javax.transaction.UserTransaction;
037    
038    import org.apache.InstanceManager;
039    import org.apache.catalina.Context;
040    import org.apache.catalina.LifecycleListener;
041    import org.apache.catalina.Manager;
042    import org.apache.catalina.Realm;
043    import org.apache.catalina.Valve;
044    import org.apache.catalina.core.StandardContext;
045    import org.apache.catalina.ha.CatalinaCluster;
046    import org.apache.commons.logging.Log;
047    import org.apache.commons.logging.LogFactory;
048    import org.apache.geronimo.connector.outbound.connectiontracking.TrackedConnectionAssociator;
049    import org.apache.geronimo.gbean.AbstractName;
050    import org.apache.geronimo.gbean.GBeanInfo;
051    import org.apache.geronimo.gbean.GBeanInfoBuilder;
052    import org.apache.geronimo.gbean.GBeanLifecycle;
053    import org.apache.geronimo.j2ee.RuntimeCustomizer;
054    import org.apache.geronimo.j2ee.annotation.Holder;
055    import org.apache.geronimo.j2ee.j2eeobjectnames.NameFactory;
056    import org.apache.geronimo.j2ee.management.impl.InvalidObjectNameException;
057    import org.apache.geronimo.kernel.Kernel;
058    import org.apache.geronimo.kernel.ObjectNameUtil;
059    import org.apache.geronimo.management.J2EEApplication;
060    import org.apache.geronimo.management.J2EEServer;
061    import org.apache.geronimo.management.StatisticsProvider;
062    import org.apache.geronimo.management.geronimo.WebConnector;
063    import org.apache.geronimo.management.geronimo.WebContainer;
064    import org.apache.geronimo.management.geronimo.WebModule;
065    import org.apache.geronimo.naming.enc.EnterpriseNamingContext;
066    import org.apache.geronimo.security.jacc.RunAsSource;
067    import org.apache.geronimo.tomcat.cluster.CatalinaClusterGBean;
068    import org.apache.geronimo.tomcat.connector.TomcatWebConnector;
069    import org.apache.geronimo.tomcat.stats.ModuleStats;
070    import org.apache.geronimo.tomcat.util.SecurityHolder;
071    import org.apache.geronimo.transaction.GeronimoUserTransaction;
072    import org.apache.geronimo.webservices.WebServiceContainer;
073    import org.apache.geronimo.webservices.WebServiceContainerFactory;
074    import org.apache.naming.resources.DirContextURLStreamHandler;
075    
076    /**
077     * Wrapper for a WebApplicationContext that sets up its J2EE environment.
078     *
079     * @version $Rev: 562021 $ $Date: 2007-08-02 01:51:18 -0400 (Thu, 02 Aug 2007) $
080     */
081    public class TomcatWebAppContext implements GBeanLifecycle, TomcatContext, WebModule, StatisticsProvider {
082    
083        private static Log log = LogFactory.getLog(TomcatWebAppContext.class);
084    
085        protected final TomcatContainer container;
086    
087        private final ClassLoader classLoader;
088    
089        protected Context context = null;
090    
091        private String path = null;
092    
093        private String docBase = null;
094    
095        private String virtualServer = null;
096    
097        private final Realm realm;
098    
099        private final List valveChain;
100        
101        private final List listenerChain;
102    
103        private final CatalinaCluster catalinaCluster;
104    
105        private final Manager manager;
106    
107        private final boolean crossContext;
108    
109        private final boolean disableCookies;
110    
111        private final UserTransaction userTransaction;
112    
113        private final javax.naming.Context componentContext;
114    
115        private final Kernel kernel;
116    
117        private final Set unshareableResources;
118    
119        private final Set applicationManagedSecurityResources;
120    
121        private final TrackedConnectionAssociator trackedConnectionAssociator;
122    
123        private final SecurityHolder securityHolder;
124    
125        private final RunAsSource runAsSource;
126    
127        private final J2EEServer server;
128    
129        private final Map webServices;
130    
131        private final String objectName;
132    
133        private final String originalSpecDD;
134    
135        private final URL configurationBaseURL;
136    
137        private final Holder holder;
138    
139        private final RuntimeCustomizer contextCustomizer;
140    
141        // JSR 77
142        
143        private final String j2EEServer;
144        
145        private final String j2EEApplication;
146        
147    //  statistics
148        private ModuleStats statsProvider;
149        private boolean reset = true;
150        public TomcatWebAppContext(
151                ClassLoader classLoader,
152                String objectName,
153                String originalSpecDD,
154                URL configurationBaseUrl,
155                SecurityHolder securityHolder,
156                String virtualServer,
157                Map componentContext,
158                Set unshareableResources,
159                Set applicationManagedSecurityResources,
160                TransactionManager transactionManager,
161                TrackedConnectionAssociator trackedConnectionAssociator,
162                TomcatContainer container,
163                RunAsSource runAsSource,
164                ObjectRetriever tomcatRealm,
165                ValveGBean tomcatValveChain,
166                LifecycleListenerGBean lifecycleListenerChain,
167                CatalinaClusterGBean cluster,
168                ManagerGBean manager,
169                boolean crossContext,
170                boolean disableCookies,
171                Map webServices,
172                Holder holder,
173                RuntimeCustomizer contextCustomizer,
174                J2EEServer server,
175                J2EEApplication application,
176                Kernel kernel)
177                throws Exception {
178    
179            assert classLoader != null;
180            assert configurationBaseUrl != null;
181            assert transactionManager != null;
182            assert trackedConnectionAssociator != null;
183            assert componentContext != null;
184            assert container != null;
185    
186    
187            this.objectName = objectName;
188            URI root;
189    //        TODO is there a simpler way to do this?
190            if (configurationBaseUrl.getProtocol().equalsIgnoreCase("file")) {
191                root = new URI("file", configurationBaseUrl.getPath(), null);
192            } else {
193                root = URI.create(configurationBaseUrl.toString());
194            }
195            this.setDocBase(root.getPath());
196            this.container = container;
197            this.originalSpecDD = originalSpecDD;
198    
199            this.virtualServer = virtualServer;
200            this.securityHolder = securityHolder;
201    
202            userTransaction = new GeronimoUserTransaction(transactionManager);
203            this.componentContext = EnterpriseNamingContext.createEnterpriseNamingContext(componentContext, userTransaction, kernel, classLoader);
204    
205            this.unshareableResources = unshareableResources;
206            this.applicationManagedSecurityResources = applicationManagedSecurityResources;
207            this.trackedConnectionAssociator = trackedConnectionAssociator;
208    
209            this.server = server;
210            this.runAsSource = runAsSource == null? RunAsSource.NULL: runAsSource;
211            if (securityHolder != null) {
212                securityHolder.setDefaultSubject(this.runAsSource.getDefaultSubject());
213                securityHolder.setRunAsSource(this.runAsSource);
214            }
215    
216    
217            this.configurationBaseURL = configurationBaseUrl;
218    
219            this.holder = holder == null? new Holder(): holder;
220            this.contextCustomizer = contextCustomizer;
221    
222            if (tomcatRealm != null){
223                realm = (Realm)tomcatRealm.getInternalObject();
224                if (realm == null){
225                    throw new IllegalArgumentException("tomcatRealm must be an instance of org.apache.catalina.Realm.");
226                }
227            } else{
228                realm = null;
229            }
230    
231            //Add the valve list
232            if (tomcatValveChain != null){
233                ArrayList<Valve> chain = new ArrayList<Valve>();
234                ValveGBean valveGBean = tomcatValveChain;
235                while(valveGBean != null){
236                    chain.add((Valve)valveGBean.getInternalObject());
237                    valveGBean = valveGBean.getNextValve();
238                }
239                valveChain = chain;
240            } else {
241                valveChain = null;
242            }
243            
244            //Add the Lifecycle Listener list
245            if (lifecycleListenerChain != null){
246                ArrayList<LifecycleListener> chain = new ArrayList<LifecycleListener>();
247                LifecycleListenerGBean listenerGBean = lifecycleListenerChain;
248                while(listenerGBean != null){
249                    chain.add((LifecycleListener)listenerGBean.getInternalObject());
250                    listenerGBean = listenerGBean.getNextListener();
251                }
252                listenerChain = chain;
253            } else {
254                listenerChain = null;
255            }
256    
257            //Add the cluster
258            if (cluster != null) {
259                catalinaCluster = (CatalinaCluster) cluster.getInternalObject();
260            } else {
261                catalinaCluster = null;
262            }
263    
264            //Add the manager
265            if (manager != null) {
266                this.manager = (Manager) manager.getInternalObject();
267            } else {
268                this.manager = null;
269            }
270    
271            this.crossContext = crossContext;
272    
273            this.disableCookies = disableCookies;
274    
275            this.webServices = createWebServices(webServices, kernel);
276    
277            this.classLoader = classLoader;
278    
279            this.kernel = kernel;
280            
281            if (objectName != null) {
282                ObjectName myObjectName = ObjectNameUtil.getObjectName(objectName);
283                verifyObjectName(myObjectName);
284                j2EEServer = myObjectName.getKeyProperty(NameFactory.J2EE_SERVER);
285                j2EEApplication = myObjectName.getKeyProperty(NameFactory.J2EE_APPLICATION);
286            } else {
287                // StandardContext uses default value of these as "none"
288                j2EEServer = null;
289                j2EEApplication = null;
290            }       
291        }
292    
293        private Map createWebServices(Map webServiceFactoryMap, Kernel kernel) throws Exception {
294            Map webServices = new HashMap();
295            if (webServiceFactoryMap != null) {
296                for (Iterator iterator = webServiceFactoryMap.entrySet().iterator(); iterator.hasNext();) {
297                    Map.Entry entry = (Map.Entry) iterator.next();
298                    String servletName = (String) entry.getKey();
299                    AbstractName factoryName = (AbstractName) entry.getValue();
300                    WebServiceContainerFactory webServiceContainerFactory = (WebServiceContainerFactory) kernel.getGBean(factoryName);
301                    WebServiceContainer webServiceContainer = webServiceContainerFactory.getWebServiceContainer();
302                    webServices.put(servletName, webServiceContainer);
303                }
304            }
305            return webServices;
306        }
307    
308        public String getObjectName() {
309            return objectName;
310        }
311    
312        public String getJ2EEApplication() {
313            return j2EEApplication;
314        }
315    
316        public String getJ2EEServer() {
317            return j2EEServer;
318        }
319    
320        public boolean isStateManageable() {
321            return true;
322        }
323    
324        public boolean isStatisticsProvider() {
325            return true;
326        }
327    
328        public boolean isEventProvider() {
329            return true;
330        }
331    
332        public URL getWARDirectory() {
333            return configurationBaseURL;
334        }
335    
336        public String getWARName() {
337            //todo: make this return something more consistent
338            try {
339                return ObjectName.getInstance(objectName).getKeyProperty(NameFactory.J2EE_NAME);
340            } catch (MalformedObjectNameException e) {
341                return null;
342            }
343        }
344    
345        public WebContainer getContainer() {
346            return container;
347        }
348    
349        public String getServer() {
350            return server == null? null: server.getObjectName();
351        }
352    
353        public String getDocBase() {
354            return docBase;
355        }
356    
357        public void setDocBase(String docBase) {
358            this.docBase = docBase;
359        }
360    
361        public UserTransaction getUserTransaction() {
362            return userTransaction;
363        }
364    
365        public javax.naming.Context getJndiContext() {
366            return componentContext;
367        }
368    
369        public String getVirtualServer() {
370            return virtualServer;
371        }
372    
373        public ClassLoader getClassLoader() {
374            return classLoader;
375        }
376    
377        public Kernel getKernel() {
378            return kernel;
379        }
380    
381        public boolean isDisableCookies() {
382            return disableCookies;
383        }
384    
385        public Context getContext() {
386            return context;
387        }
388    
389        public void setContext(Context context) {
390            this.context = context;
391        }
392    
393        public String getContextPath() {
394            return path;
395        }
396    
397        public void setContextPath(String path) {
398            this.path = path.startsWith("/") ? path : "/" + path;
399        }
400    
401        public SecurityHolder getSecurityHolder() {
402            return securityHolder;
403        }
404    
405    
406        public Set getApplicationManagedSecurityResources() {
407            return applicationManagedSecurityResources;
408        }
409    
410        public TrackedConnectionAssociator getTrackedConnectionAssociator() {
411            return trackedConnectionAssociator;
412        }
413    
414        public Set getUnshareableResources() {
415            return unshareableResources;
416        }
417    
418        public Realm getRealm() {
419            return realm;
420        }
421    
422        public List getValveChain() {
423            return valveChain;
424        }
425    
426        public List getLifecycleListenerChain() {
427            return listenerChain;
428        }
429    
430        public CatalinaCluster getCluster() {
431            return catalinaCluster;
432        }
433    
434        public Manager getManager() {
435            return manager;
436        }
437    
438        public boolean isCrossContext() {
439            return crossContext;
440        }
441    
442        public Map getWebServices(){
443            return webServices;
444        }
445    
446        public InstanceManager getInstanceManager() {
447            return new TomcatInstanceManager(holder, classLoader, componentContext);
448        }
449    
450        public RuntimeCustomizer getRuntimeCustomizer() {
451            return contextCustomizer;
452        }
453    
454        public String[] getServlets(){
455            String[] result = null;
456            if ((context != null) && (context instanceof StandardContext)) {
457                result = ((StandardContext) context).getServlets();
458            }
459    
460            return result;
461        }
462    
463        /**
464         * ObjectName must match this pattern: <p/>
465         * domain:j2eeType=WebModule,name=MyName,J2EEServer=MyServer,J2EEApplication=MyApplication
466         */
467        private void verifyObjectName(ObjectName objectName) {
468            if (objectName.isPattern()) {
469                throw new InvalidObjectNameException(
470                        "ObjectName can not be a pattern", objectName);
471            }
472            Hashtable keyPropertyList = objectName.getKeyPropertyList();
473            if (!NameFactory.WEB_MODULE.equals(keyPropertyList.get("j2eeType"))) {
474                throw new InvalidObjectNameException(
475                        "WebModule object name j2eeType property must be 'WebModule'",
476                        objectName);
477            }
478            if (!keyPropertyList.containsKey(NameFactory.J2EE_NAME)) {
479                throw new InvalidObjectNameException(
480                        "WebModule object must contain a name property", objectName);
481            }
482            if (!keyPropertyList.containsKey(NameFactory.J2EE_SERVER)) {
483                throw new InvalidObjectNameException(
484                        "WebModule object name must contain a J2EEServer property",
485                        objectName);
486            }
487            if (!keyPropertyList.containsKey(NameFactory.J2EE_APPLICATION)) {
488                throw new InvalidObjectNameException(
489                        "WebModule object name must contain a J2EEApplication property",
490                        objectName);
491            }
492            if (keyPropertyList.size() != 4) {
493                throw new InvalidObjectNameException(
494                        "WebModule object name can only have j2eeType, name, J2EEApplication, and J2EEServer properties",
495                        objectName);
496            }
497        }
498    
499        public String[] getJavaVMs() {
500            return server == null? new String[0]: server.getJavaVMs();
501        }
502    
503        public String getDeploymentDescriptor() {
504            return originalSpecDD;
505        }
506        
507    //  JSR 77 statistics - The static values are initialized at the time of 
508        // creration, getStats return fresh value everytime
509        public Stats getStats() {
510            if (reset) {
511                reset = false;
512                return statsProvider.getStats();
513            }
514            else return statsProvider.updateStats();
515        }
516            
517        public void resetStats() {
518            reset = true;
519        }
520    
521        public void doStart() throws Exception {
522    
523            // See the note of TomcatContainer::addContext
524            container.addContext(this);
525            // Is it necessary - doesn't Tomcat Embedded take care of it?
526            // super.start();
527            //register the classloader <> dir context association so that tomcat's jndi based getResources works.
528            DirContext resources = context.getResources();
529            if (resources == null) {
530                throw new IllegalStateException("JNDI environment was not set up correctly due to previous error");
531            }
532            DirContextURLStreamHandler.bind(classLoader, resources);
533            if (context instanceof StandardContext)
534                statsProvider =  new ModuleStats((StandardContext)context);
535    
536            log.debug("TomcatWebAppContext started for " + path);
537        }
538    
539        public void doStop() throws Exception {
540            statsProvider = null;
541            container.removeContext(this);
542            DirContextURLStreamHandler.unbind(classLoader);
543    
544            // No more logging will occur for this ClassLoader. Inform the LogFactory to avoid a memory leak.
545    //        LogFactory.release(classLoader);
546    
547            log.debug("TomcatWebAppContext stopped");
548        }
549    
550        public void doFail() {
551            statsProvider = null;
552            container.removeContext(this);
553    
554            // No more logging will occur for this ClassLoader. Inform the LogFactory to avoid a memory leak.
555    //        LogFactory.release(classLoader);
556    
557            log.warn("TomcatWebAppContext failed");
558        }
559    
560        public static final GBeanInfo GBEAN_INFO;
561    
562        static {
563            GBeanInfoBuilder infoBuilder = GBeanInfoBuilder.createStatic("Tomcat WebApplication Context", TomcatWebAppContext.class, NameFactory.WEB_MODULE);
564    
565            infoBuilder.addAttribute("classLoader", ClassLoader.class, false);
566            infoBuilder.addAttribute("objectName", String.class, false);
567            infoBuilder.addAttribute("deploymentDescriptor", String.class, true);
568            infoBuilder.addAttribute("configurationBaseUrl", URL.class, true);
569    
570            infoBuilder.addAttribute("contextPath", String.class, true);
571    
572            infoBuilder.addAttribute("securityHolder", SecurityHolder.class, true);
573            infoBuilder.addAttribute("virtualServer", String.class, true);
574            infoBuilder.addAttribute("componentContext", Map.class, true);
575            infoBuilder.addAttribute("unshareableResources", Set.class, true);
576            infoBuilder.addAttribute("applicationManagedSecurityResources", Set.class, true);
577            infoBuilder.addReference("TransactionManager", TransactionManager.class, NameFactory.TRANSACTION_MANAGER);
578            infoBuilder.addReference("TrackedConnectionAssociator", TrackedConnectionAssociator.class, NameFactory.JCA_CONNECTION_TRACKER);
579    
580            infoBuilder.addReference("Container", TomcatContainer.class, NameFactory.GERONIMO_SERVICE);
581            infoBuilder.addReference("RunAsSource", RunAsSource.class, NameFactory.JACC_MANAGER);
582            infoBuilder.addReference("TomcatRealm", ObjectRetriever.class);
583            infoBuilder.addReference("TomcatValveChain", ValveGBean.class);
584            infoBuilder.addReference("LifecycleListenerChain", LifecycleListenerGBean.class, LifecycleListenerGBean.J2EE_TYPE);
585            infoBuilder.addReference("Cluster", CatalinaClusterGBean.class, CatalinaClusterGBean.J2EE_TYPE);
586            infoBuilder.addReference("Manager", ManagerGBean.class);
587            infoBuilder.addAttribute("crossContext", boolean.class, true);
588            infoBuilder.addAttribute("disableCookies", boolean.class, true);
589            infoBuilder.addAttribute("webServices", Map.class, true);
590            infoBuilder.addAttribute("holder", Holder.class, true);
591            infoBuilder.addReference("ContextCustomizer", RuntimeCustomizer.class, NameFactory.GERONIMO_SERVICE);
592            infoBuilder.addReference("J2EEServer", J2EEServer.class);
593            infoBuilder.addReference("J2EEApplication", J2EEApplication.class);
594            infoBuilder.addAttribute("kernel", Kernel.class, false);
595    
596            infoBuilder.addInterface(WebModule.class);
597    
598            infoBuilder.setConstructor(new String[] {
599                    "classLoader",
600                    "objectName",
601                    "deploymentDescriptor",
602                    "configurationBaseUrl",
603                    "securityHolder",
604                    "virtualServer",
605                    "componentContext",
606                    "unshareableResources",
607                    "applicationManagedSecurityResources",
608                    "TransactionManager",
609                    "TrackedConnectionAssociator",
610                    "Container",
611                    "RunAsSource",
612                    "TomcatRealm",
613                    "TomcatValveChain",
614                    "LifecycleListenerChain",
615                    "Cluster",
616                    "Manager",
617                    "crossContext",
618                    "disableCookies",
619                    "webServices",
620                    "holder",
621                    "ContextCustomizer",
622                    "J2EEServer",
623                    "J2EEApplication",
624                    "kernel"
625                    }
626            );
627    
628            GBEAN_INFO = infoBuilder.getBeanInfo();
629        }
630    
631        public static GBeanInfo getGBeanInfo() {
632            return GBEAN_INFO;
633        }
634    }