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 }