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