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.beans.PropertyChangeListener;
020    import java.io.IOException;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import javax.security.auth.Subject;
026    import javax.security.jacc.PolicyContext;
027    import javax.servlet.Servlet;
028    import javax.servlet.ServletException;
029    
030    import org.apache.catalina.Container;
031    import org.apache.catalina.LifecycleException;
032    import org.apache.catalina.LifecycleListener;
033    import org.apache.catalina.Loader;
034    import org.apache.catalina.Manager;
035    import org.apache.catalina.Valve;
036    import org.apache.catalina.Wrapper;
037    import org.apache.catalina.connector.Request;
038    import org.apache.catalina.connector.Response;
039    import org.apache.catalina.core.StandardContext;
040    import org.apache.catalina.ha.CatalinaCluster;
041    import org.apache.catalina.valves.ValveBase;
042    import org.apache.geronimo.common.DeploymentException;
043    import org.apache.geronimo.common.GeronimoSecurityException;
044    import org.apache.geronimo.security.ContextManager;
045    import org.apache.geronimo.security.jacc.RunAsSource;
046    import org.apache.geronimo.tomcat.interceptor.BeforeAfter;
047    import org.apache.geronimo.tomcat.interceptor.ComponentContextBeforeAfter;
048    import org.apache.geronimo.tomcat.interceptor.InstanceContextBeforeAfter;
049    import org.apache.geronimo.tomcat.interceptor.PolicyContextBeforeAfter;
050    import org.apache.geronimo.tomcat.interceptor.UserTransactionBeforeAfter;
051    import org.apache.geronimo.tomcat.listener.RunAsInstanceListener;
052    import org.apache.geronimo.tomcat.util.SecurityHolder;
053    import org.apache.geronimo.tomcat.valve.DefaultSubjectValve;
054    import org.apache.geronimo.tomcat.valve.GeronimoBeforeAfterValve;
055    import org.apache.geronimo.webservices.POJOWebServiceServlet;
056    import org.apache.geronimo.webservices.WebServiceContainer;
057    import org.apache.geronimo.webservices.WebServiceContainerInvoker;
058    
059    
060    /**
061     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
062     */
063    public class GeronimoStandardContext extends StandardContext {
064    
065        private static final long serialVersionUID = 3834587716552831032L;
066    
067        private Subject defaultSubject = null;
068        private RunAsSource runAsSource = RunAsSource.NULL;
069    
070        private Map webServiceMap = null;
071    
072        private boolean pipelineInitialized;
073    
074        private BeforeAfter beforeAfter = null;
075        private int contextCount = 0;
076        
077        private static final boolean allowLinking;
078        
079        static {
080            allowLinking = new Boolean(System.getProperty("org.apache.geronimo.tomcat.GeronimoStandardContext.allowLinking", "false"));
081        }
082    
083        public void setContextProperties(TomcatContext ctx) throws DeploymentException {
084    
085            // Create ReadOnlyContext
086            javax.naming.Context enc = ctx.getJndiContext();
087            setInstanceManager(ctx.getInstanceManager());
088    
089            //try to make sure this mbean properties match those of the TomcatWebAppContext
090            if (ctx instanceof TomcatWebAppContext) {
091                TomcatWebAppContext tctx = (TomcatWebAppContext) ctx;
092                setJavaVMs(tctx.getJavaVMs());
093                setServer(tctx.getServer());
094                setJ2EEApplication(tctx.getJ2EEApplication());
095                setJ2EEServer(tctx.getJ2EEServer());
096                //install jasper injection support if required
097                if (tctx.getRuntimeCustomizer() != null) {
098                    Map<String, Object> servletContext = new HashMap<String, Object>();
099                    Map<Class, Object> customizerContext = new HashMap<Class, Object>();
100                    customizerContext.put(Map.class, servletContext);
101                    customizerContext.put(javax.naming.Context.class, enc);
102                    tctx.getRuntimeCustomizer().customize(customizerContext);
103                    for (Map.Entry<String, Object> entry: servletContext.entrySet()) {
104                        getServletContext().setAttribute(entry.getKey(), entry.getValue());
105                    }
106                }
107            }
108    
109            int index = 0;
110            BeforeAfter interceptor = new InstanceContextBeforeAfter(null,
111                    index++,
112                    index++, ctx.getUnshareableResources(),
113                    ctx.getApplicationManagedSecurityResources(),
114                    ctx.getTrackedConnectionAssociator());
115    
116            // Set ComponentContext BeforeAfter
117            if (enc != null) {
118                interceptor = new ComponentContextBeforeAfter(interceptor, index++, enc);
119            }
120    
121            //Set a PolicyContext BeforeAfter
122            SecurityHolder securityHolder = ctx.getSecurityHolder();
123            if (securityHolder != null) {
124    
125                // save the role designates for mapping servlets to their run-as roles
126                runAsSource = securityHolder.getRunAsSource();
127                
128                if (securityHolder.getPolicyContextID() != null) {
129    
130                    PolicyContext.setContextID(securityHolder.getPolicyContextID());
131    
132                    /**
133                     * Register our default subject with the ContextManager
134                     */
135                    defaultSubject = securityHolder.getDefaultSubject();
136    
137                    if (defaultSubject == null) {
138                        defaultSubject = ContextManager.EMPTY;
139                    }
140    
141                    interceptor = new PolicyContextBeforeAfter(interceptor, index++, index++, index++, securityHolder.getPolicyContextID(), defaultSubject);
142    
143                }
144            }
145            
146            //Set a UserTransactionBeforeAfter
147            interceptor = new UserTransactionBeforeAfter(interceptor, index++, ctx.getUserTransaction());
148    
149            Valve clusteredValve = ctx.getClusteredValve();
150            if (null != clusteredValve) {
151                addValve(clusteredValve);
152            }
153            
154            //Set the BeforeAfters as a valve
155            GeronimoBeforeAfterValve geronimoBAValve = new GeronimoBeforeAfterValve(interceptor, index);
156            addValve(geronimoBAValve);
157            beforeAfter = interceptor;
158            contextCount = index;
159    
160            //Not clear if user defined valves should be involved in init processing.  Probably not since
161            //request and response are null.
162    
163            addValve(new SystemMethodValve());
164    
165            // Add User Defined Valves
166            List valveChain = ctx.getValveChain();
167            if (valveChain != null) {
168                for (Object valve : valveChain) {
169                    addValve((Valve)valve);
170                }
171            }
172            
173            // Add User Defined Listeners
174            List listenerChain = ctx.getLifecycleListenerChain();
175            if (listenerChain != null) {
176                for (Object listener : listenerChain) {
177                    addLifecycleListener((LifecycleListener)listener);
178                }
179            }
180    
181            CatalinaCluster cluster = ctx.getCluster();
182            if (cluster != null)
183                this.setCluster(cluster);
184    
185            Manager manager = ctx.getManager();
186            if (manager != null)
187                this.setManager(manager);
188    
189            pipelineInitialized = true;
190            this.webServiceMap = ctx.getWebServices();
191    
192            this.setCrossContext(ctx.isCrossContext());
193    
194            this.setWorkDir(ctx.getWorkDir());
195    
196            super.setAllowLinking(allowLinking);
197    
198            this.setCookies(!ctx.isDisableCookies());
199    
200            //Set the Dispatch listener
201            this.addInstanceListener("org.apache.geronimo.tomcat.listener.DispatchListener");
202            
203            //Set the run-as listener. listeners must be added before start() is called
204            if (runAsSource != null) {
205                this.addInstanceListener(RunAsInstanceListener.class.getName());
206            }
207        }
208    
209        /* This method is called by a background thread to destroy sessions (among other things)
210         * so we need to apply appropriate context to the thread to expose JNDI, etc.
211         */
212        public void backgroundProcess() {
213            Object context[] = null;
214            
215            if (beforeAfter != null){
216                context = new Object[contextCount];
217                beforeAfter.before(context, null, null, BeforeAfter.EDGE_SERVLET);
218            }
219            
220            try {
221                super.backgroundProcess();
222            } finally {
223                if (beforeAfter != null){
224                    beforeAfter.after(context, null, null, 0);
225                }
226            }
227        }
228        
229        public void kill() throws Exception {
230            Object context[] = null;
231            
232            if (beforeAfter != null){
233                context = new Object[contextCount];
234                beforeAfter.before(context, null, null, BeforeAfter.EDGE_SERVLET);
235            }
236            
237            try {
238                stop();
239                destroy();
240            } finally {
241                if (beforeAfter != null){
242                    beforeAfter.after(context, null, null, 0);
243                }
244            }
245        }
246        
247        public synchronized void start() throws LifecycleException {
248            if (pipelineInitialized) {
249                try {
250                    Valve valve = getFirst();
251                    valve.invoke(null, null);
252                    //Install the DefaultSubjectValve after the authentication valve so the default subject is supplied
253                    //only if no real subject is authenticated.
254    
255                    Valve defaultSubjectValve = new DefaultSubjectValve(defaultSubject);
256                    addValve(defaultSubjectValve);
257    
258                    // if a servlet uses run-as then make sure role desgnates have been provided
259                    if (hasRunAsServlet()) {
260                        if (runAsSource == null) {
261                            throw new GeronimoSecurityException("web.xml or annotation specifies a run-as role but no subject configuration supplied for run-as roles");
262                        }
263                    } else {
264                        // optimization
265                        this.removeInstanceListener(RunAsInstanceListener.class.getName());
266                    }
267                    
268                } catch (IOException e) {
269                    if (e.getCause() instanceof LifecycleException) {
270                        throw (LifecycleException) e.getCause();
271                    }
272                    throw new LifecycleException(e);
273                } catch (ServletException e) {
274                    throw new LifecycleException(e);
275                }
276            } else
277                super.start();
278        }
279    
280        public void addChild(Container child) {
281            Wrapper wrapper = (Wrapper) child;
282    
283            String servletClassName = wrapper.getServletClass();
284            if (servletClassName == null) {
285                super.addChild(child);
286                return;
287            }
288    
289            ClassLoader cl = this.getParentClassLoader();
290    
291            Class baseServletClass;
292            Class servletClass;
293            try {
294                baseServletClass = cl.loadClass(Servlet.class.getName());
295                servletClass = cl.loadClass(servletClassName);
296                //Check if the servlet is of type Servlet class
297                if (!baseServletClass.isAssignableFrom(servletClass)) {
298                    //Nope - its probably a webservice, so lets see...
299                    if (webServiceMap != null) {
300                        WebServiceContainer webServiceContainer = (WebServiceContainer) webServiceMap.get(wrapper.getName());
301    
302                        if (webServiceContainer != null) {
303                            //Yep its a web service
304                            //So swap it out with a POJOWebServiceServlet
305                            wrapper.setServletClass("org.apache.geronimo.webservices.POJOWebServiceServlet");
306    
307                            //Set the WebServiceContainer stuff
308                            String webServicecontainerID = wrapper.getName() + WebServiceContainerInvoker.WEBSERVICE_CONTAINER + webServiceContainer.hashCode();
309                            getServletContext().setAttribute(webServicecontainerID, webServiceContainer);
310                            wrapper.addInitParameter(WebServiceContainerInvoker.WEBSERVICE_CONTAINER, webServicecontainerID);
311    
312                            //Set the SEI Class in the attribute
313                            String pojoClassID = wrapper.getName() + POJOWebServiceServlet.POJO_CLASS + servletClass.hashCode();
314                            getServletContext().setAttribute(pojoClassID, servletClass);
315                            wrapper.addInitParameter(POJOWebServiceServlet.POJO_CLASS, pojoClassID);
316                        }
317                    }
318                }
319            } catch (ClassNotFoundException e) {
320                throw new RuntimeException(e.getMessage(), e);
321            }
322    
323            super.addChild(child);
324        }
325    
326        public synchronized void setLoader(final Loader delegate) {
327            Loader loader = new Loader() {
328    
329                public void backgroundProcess() {
330                    delegate.backgroundProcess();
331                }
332    
333                public ClassLoader getClassLoader() {
334                    // Implementation Note: the actual CL to be used by this 
335                    // context is the Geronimo one and not the Tomcat one.
336                    return parentClassLoader;
337                }
338    
339                public Container getContainer() {
340                    return delegate.getContainer();
341                }
342    
343                public void setContainer(Container container) {
344                    delegate.setContainer(container);
345                }
346    
347                public boolean getDelegate() {
348                    return delegate.getDelegate();
349                }
350    
351                public void setDelegate(boolean delegateBoolean) {
352                    delegate.setDelegate(delegateBoolean);
353                }
354    
355                public String getInfo() {
356                    return delegate.getInfo();
357                }
358    
359                public boolean getReloadable() {
360                    return false;
361                }
362    
363                public void setReloadable(boolean reloadable) {
364                    if (reloadable) {
365                        throw new UnsupportedOperationException("Reloadable context is not supported.");
366                    }
367                }
368    
369                public void addPropertyChangeListener(PropertyChangeListener listener) {
370                    delegate.addPropertyChangeListener(listener);
371                }
372    
373                public void addRepository(String repository) {
374                    delegate.addRepository(repository);
375                }
376    
377                public String[] findRepositories() {
378                    return delegate.findRepositories();
379                }
380    
381                public boolean modified() {
382                    return delegate.modified();
383                }
384    
385                public void removePropertyChangeListener(PropertyChangeListener listener) {
386                    delegate.removePropertyChangeListener(listener);
387                }
388            };
389    
390            super.setLoader(loader);
391        }
392    
393        private class SystemMethodValve extends ValveBase {
394    
395            public void invoke(Request request, Response response) throws IOException, ServletException {
396                if (request == null && response == null) {
397                    try {
398                        GeronimoStandardContext.super.start();
399                    } catch (LifecycleException e) {
400                        throw (IOException) new IOException("wrapping lifecycle exception").initCause(e);
401                    }
402                    if (GeronimoStandardContext.this.getState() != 1 || !GeronimoStandardContext.this.getAvailable()){
403                        throw new IOException("Context did not start for an unknown reason");
404                    }
405                } else {
406                    getNext().invoke(request, response);
407                }
408    
409            }
410        }
411    
412    
413        public BeforeAfter getBeforeAfter() {
414            return beforeAfter;
415        }
416    
417        public int getContextCount() {
418            return contextCount;
419        }
420    
421        /**
422         * Determine if the context has at least one servlet that specifies a run-as role
423         * @return true if at least one servlet specifies a run-as role, false otherwise
424         */
425        protected boolean hasRunAsServlet() {
426            for (Container servlet : findChildren()) { 
427                if (servlet instanceof Wrapper) {
428                    if (((Wrapper)servlet).getRunAs() != null) {
429                        return true;
430                    }
431                }
432            }
433            return false;
434        }
435        
436        /**
437         * Get the Subject for the servlet's run-as role
438         * @param runAsRole Name of run as role to get Subject for
439         * @return Subject for the servlet's run-as role, if specified.  otherwise null. 
440         */
441        public Subject getSubjectForRole(String runAsRole) {
442            return runAsSource.getSubjectForRole(runAsRole);
443        }
444    }