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