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 }