1 /*
2 * JBoss, the OpenSource J2EE WebOS
3 *
4 * Distributable under LGPL license.
5 * See terms of license at gnu.org.
6 */
7
8 package org.jboss.web;
9
10 import java.io.ByteArrayInputStream;
11 import java.io.ByteArrayOutputStream;
12 import java.io.File;
13 import java.io.FileInputStream;
14 import java.io.InputStream;
15 import java.io.InputStream;
16 import java.lang.reflect.Method;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.net.URLClassLoader;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.Set;
25 import javax.naming.Context;
26 import javax.naming.InitialContext;
27 import javax.naming.LinkRef;
28 import javax.naming.NamingException;
29
30 import org.jboss.deployment.DeploymentException;
31 import org.jboss.deployment.DeploymentInfo;
32 import org.jboss.deployment.SubDeployerSupport;
33 import org.jboss.ejb.EjbUtil;
34 import org.jboss.metadata.EjbLocalRefMetaData;
35 import org.jboss.metadata.EjbRefMetaData;
36 import org.jboss.metadata.EnvEntryMetaData;
37 import org.jboss.metadata.ResourceEnvRefMetaData;
38 import org.jboss.metadata.ResourceRefMetaData;
39 import org.jboss.metadata.WebMetaData;
40 import org.jboss.metadata.XmlFileLoader;
41 import org.jboss.metadata.MetaData;
42 import org.jboss.mx.loading.LoaderRepositoryFactory;
43 import org.jboss.mx.loading.LoaderRepositoryFactory.LoaderRepositoryConfig;
44 import org.jboss.naming.Util;
45 import org.jboss.security.plugins.NullSecurityManager;
46 import org.jboss.util.file.JarUtils;
47
48 import org.w3c.dom.Document;
49 import org.w3c.dom.Element;
50
51 /** A template pattern class for web container integration into JBoss. This class
52 should be subclasses by web container providers wishing to integrate their
53 container into a JBoss server.
54
55 It provides support for mapping the following web-app.xml/jboss-web.xml elements
56 into the JBoss server JNDI namespace:
57 - env-entry
58 - resource-ref
59 - resource-env-ref
60 - ejb-ref
61 - ejb-local-ref
62 - security-domain
63
64 Subclasses need to implement the {@link #performDeploy(WebApplication, String,
65 WebDescriptorParser) performDeploy()}
66 and {@link #performUndeploy(String) performUndeploy()} methods to perform the
67 container specific steps and return the web application info required by the
68 AbstractWebContainer class.
69
70 Integration with the JBossSX security framework is based on the establishment
71 of a java:comp/env/security context as described in the
72 {@link linkSecurityDomain(String, Context) linkSecurityDomain } comments.
73 The security context provides access to the JBossSX security mgr interface
74 implementations for use by subclass request interceptors. A outline of the
75 steps for authenticating a user is:
76 <code>
77 // Get the username & password from the request context...
78 String username = f(request);
79 String password = f(request);
80 // Get the JBoss security manager from the ENC context
81 InitialContext iniCtx = new InitialContext();
82 SecurityManager securityMgr = (SecurityManager) iniCtx.lookup("java:comp/env/security/securityMgr");
83 SimplePrincipal principal = new SimplePrincipal(username);
84 if( securityMgr.isValid(principal, password) )
85 {
86 // Indicate the user is allowed access to the web content...
87
88 // Propagate the user info to JBoss for any calls into made by the servlet
89 SecurityAssociation.setPrincipal(principal);
90 SecurityAssociation.setCredential(password.toCharArray());
91 }
92 else
93 {
94 // Deny access...
95 }
96 </code>
97
98 An outline of the steps for authorizing the user is:
99 <code>
100 // Get the username & required roles from the request context...
101 String username = f(request);
102 String[] roles = f(request);
103 // Get the JBoss security manager from the ENC context
104 InitialContext iniCtx = new InitialContext();
105 RealmMapping securityMgr = (RealmMapping) iniCtx.lookup("java:comp/env/security/realmMapping");
106 SimplePrincipal principal = new SimplePrincipal(username);
107 Set requiredRoles = new HashSet(Arrays.asList(roles));
108 if( securityMgr.doesUserHaveRole(principal, requiredRoles) )
109 {
110 // Indicate the user has the required roles for the web content...
111 }
112 else
113 {
114 // Deny access...
115 }
116 </code>
117
118 The one thing to be aware of is the relationship between the thread context
119 class loader and the JNDI ENC context. Any method that attempts to access
120 the JNDI ENC context must have the ClassLoader in the WebApplication returned
121 from the {@link #performDeploy(String, String) performDeploy} as its thread
122 context ClassLoader or else the lookup for java:comp/env will fail with a
123 name not found exception, or worse, it will receive some other web application
124 ENC context. If your adapting a web container that is trying be compatible with
125 both 1.1 and 1.2 Java VMs this is something you need to pay special attention
126 to. For example, I have seen problems a request interceptor that was handling
127 the authentication/authorization callouts in tomcat3.2.1 not having the same
128 thread context ClassLoader as was used to dispatch the http service request.
129
130 For a complete example see the
131 {@link org.jboss.web.catalina.EmbeddedCatalinaServiceSX EmbeddedCatalinaServiceSX}
132 in the catalina module.
133
134 @see #performDeploy(WebApplication webApp, String warUrl,
135 WebDescriptorParser webAppParser)
136 @see #performUndeploy(String)
137 @see #parseWebAppDescriptors(ClassLoader, WebMetaData)
138 @see #linkSecurityDomain(String, Context)
139 @see org.jboss.security.SecurityManager;
140 @see org.jboss.security.RealmMapping;
141 @see org.jboss.security.SimplePrincipal;
142 @see org.jboss.security.SecurityAssociation;
143
144 @jmx:mbean extends="org.jboss.deployment.SubDeployerMBean"
145
146 @author Scott.Stark@jboss.org
147 @version $Revision: 1.51.2.25 $
148 */
149 public abstract class AbstractWebContainer
150 extends SubDeployerSupport
151 implements AbstractWebContainerMBean
152 {
153 public static interface WebDescriptorParser
154 {
155 /** This method is called as part of subclass performDeploy() method implementations
156 to parse the web-app.xml and jboss-web.xml deployment descriptors from a
157 war deployment. The method creates the ENC(java:comp/env) env-entry,
158 resource-ref, ejb-ref, etc element values. The creation of the env-entry
159 values does not require a jboss-web.xml descriptor. The creation of the
160 resource-ref and ejb-ref elements does require a jboss-web.xml descriptor
161 for the JNDI name of the deployed resources/EJBs.
162
163 Because the ENC context is private to the web application, the web
164 application class loader is used to identify the ENC. The class loader
165 is used because each war typically requires a unique class loader to
166 isolate the web application classes/resources. This means that the
167 ClassLoader passed to this method must be the thread context ClassLoader
168 seen by the server/jsp pages during init/destroy/service/etc. method
169 invocations if these methods interace with the JNDI ENC context.
170
171 @param loader, the ClassLoader for the web application. May not be null.
172 @param metaData, the WebMetaData from the WebApplication object passed to
173 the performDeploy method.
174 */
175 public void parseWebAppDescriptors(ClassLoader loader, WebMetaData metaData) throws Exception;
176
177 /** Get the DeploymentInfo for the war the triggered the deployment process.
178 * The returned reference may be updated to affect the state of the
179 * JBoss DeploymentInfo object. This can be used to assign ObjectNames
180 * of MBeans created by the container.
181 * @return The DeploymentInfo for the war being deployed.
182 */
183 public DeploymentInfo getDeploymentInfo();
184 }
185
186 /** A mapping of deployed warUrl strings to the WebApplication object */
187 protected HashMap deploymentMap = new HashMap();
188 /** The parent class loader first model flag */
189 protected boolean java2ClassLoadingCompliance = false;
190 /** A flag indicating if war archives should be unpacked */
191 protected boolean unpackWars = true;
192
193 /** If true, ejb-links that don't resolve don't cause an error (fallback to jndi-name) */
194 protected boolean lenientEjbLink = false;
195
196 public AbstractWebContainer()
197 {
198 }
199
200 /** Get the flag indicating if the normal Java2 parent first class loading
201 * model should be used over the servlet 2.3 web container first model.
202 * @return true for parent first, false for the servlet 2.3 model
203 * @jmx:managed-attribute
204 */
205 public boolean getJava2ClassLoadingCompliance()
206 {
207 return java2ClassLoadingCompliance;
208 }
209 /** Set the flag indicating if the normal Java2 parent first class loading
210 * model should be used over the servlet 2.3 web container first model.
211 * @param flag true for parent first, false for the servlet 2.3 model
212 * @jmx:managed-attribute
213 */
214 public void setJava2ClassLoadingCompliance(boolean flag)
215 {
216 java2ClassLoadingCompliance = flag;
217 }
218
219 /** Set the flag indicating if war archives should be unpacked. This may
220 * need to be set to false as long extraction paths under deploy can
221 * show up as deployment failures on some platforms.
222 *
223 * @jmx:managed-attribute
224 * @return true is war archives should be unpacked
225 */
226 public boolean getUnpackWars()
227 {
228 return unpackWars;
229 }
230 /** Get the flag indicating if war archives should be unpacked. This may
231 * need to be set to false as long extraction paths under deploy can
232 * show up as deployment failures on some platforms.
233 *
234 * @jmx:managed-attribute
235 * @param flag , true is war archives should be unpacked
236 */
237 public void setUnpackWars(boolean flag)
238 {
239 this.unpackWars = flag;
240 }
241
242
243 /**
244 * Get the flag indicating if ejb-link errors should be ignored
245 * in favour of trying the jndi-name in jboss-web.xml
246 * @return a <code>boolean</code> value
247 *
248 * @jmx:managed-attribute
249 */
250 public boolean getLenientEjbLink ()
251 {
252 return lenientEjbLink;
253 }
254
255 /**
256 * Set the flag indicating if ejb-link errors should be ignored
257 * in favour of trying the jndi-name in jboss-web.xml
258 * @return a <code>boolean</code> value
259 *
260 * @jmx:managed-attribute
261 */
262 public void setLenientEjbLink (boolean flag)
263 {
264 lenientEjbLink = flag;
265 }
266
267 public boolean accepts(DeploymentInfo sdi)
268 {
269 String warFile = sdi.url.getFile();
270 return warFile.endsWith("war") || warFile.endsWith("war/");
271 }
272
273 public synchronized void init(DeploymentInfo di)
274 throws DeploymentException
275 {
276 log.debug("Begin init");
277 try
278 {
279 if (di.url.getPath().endsWith("/"))
280 {
281 // the URL is a unpacked collection, watch the deployment descriptor
282 di.watch = new URL(di.url, "WEB-INF/web.xml");
283 }
284 else
285 {
286 // just watch the original URL
287 di.watch = di.url;
288 }
289
290 // Make sure the war is unpacked if unpackWars is true
291 File warFile = new File(di.localUrl.getFile());
292 if( warFile.isDirectory() == false && unpackWars == true )
293 {
294 File tmp = new File(warFile.getAbsolutePath()+".tmp");
295 if( warFile.renameTo(tmp) == false )
296 throw new DeploymentException("Was unable to move war to: "+tmp);
297 if( warFile.mkdir() == false )
298 throw new DeploymentException("Was unable to mkdir: "+warFile);
299 log.debug("Unpacking war to: "+warFile);
300 FileInputStream fis = new FileInputStream(tmp);
301 JarUtils.unjar(fis, warFile);
302 fis.close();
303 log.debug("Replaced war with unpacked contents");
304 if( tmp.delete() == false )
305 log.debug("Was unable to delete war tmp file");
306 else
307 log.debug("Deleted war archive");
308 // Reset the localUrl to end in a '/'
309 di.localUrl = warFile.toURL();
310 // Reset the localCl to point to the file
311 URL[] localCP = {di.localUrl};
312 di.localCl = new URLClassLoader(localCP);
313 }
314
315 WebMetaData metaData = new WebMetaData();
316 metaData.setJava2ClassLoadingCompliance(this.java2ClassLoadingCompliance);
317 di.metaData = metaData;
318 // Check for a loader-repository
319 XmlFileLoader xfl = new XmlFileLoader();
320 InputStream in = di.localCl.getResourceAsStream("WEB-INF/jboss-web.xml");
321 if( in != null )
322 {
323 Element jbossWeb = xfl.getDocument(in, "WEB-INF/jboss-web.xml").getDocumentElement();
324 in.close();
325 // Check for a war level class loading config
326 Element classLoading = MetaData.getOptionalChild(jbossWeb, "class-loading");
327 if( classLoading != null )
328 {
329 String flagString = classLoading.getAttribute("java2ClassLoadingCompliance");
330 if( flagString.length() == 0 )
331 flagString = "true";
332 boolean flag = Boolean.valueOf(flagString).booleanValue();
333 metaData.setJava2ClassLoadingCompliance(flag);
334 // Check for a loader-repository for scoping
335 Element loader = MetaData.getOptionalChild(classLoading, "loader-repository");
336 if( loader != null )
337 {
338 LoaderRepositoryConfig config = LoaderRepositoryFactory.parseRepositoryConfig(loader);
339 di.setRepositoryInfo(config);
340 }
341 }
342 }
343
344 // Generate an event for the initialization
345 super.init(di);
346 }
347 catch (Exception e)
348 {
349 log.error("Problem in init ", e);
350 throw new DeploymentException(e);
351 }
352
353 log.debug("End init");
354 }
355
356 /** WARs do not have nested deployments
357 * @param di
358 */
359 protected void processNestedDeployments(DeploymentInfo di)
360 {
361 }
362
363 /** A template pattern implementation of the deploy() method. This method
364 calls the {@link #performDeploy(String, String) performDeploy()} method to
365 perform the container specific deployment steps and registers the
366 returned WebApplication in the deployment map. The steps performed are:
367
368 ClassLoader appClassLoader = thread.getContextClassLoader();
369 URLClassLoader warLoader = URLClassLoader.newInstance(empty, appClassLoader);
370 thread.setContextClassLoader(warLoader);
371 WebDescriptorParser webAppParser = ...;
372 WebMetaData metaData = di.metaData;
373 parseMetaData(ctxPath, warUrl, metaData);
374 WebApplication warInfo = new WebApplication(metaData);
375 performDeploy(warInfo, warUrl, webAppParser);
376 deploymentMap.put(warUrl, warInfo);
377 thread.setContextClassLoader(appClassLoader);
378
379 The subclass performDeploy() implementation needs to invoke
380 webAppParser.parseWebAppDescriptors(loader, warInfo) to have the JNDI
381 java:comp/env namespace setup before any web app component can access
382 this namespace.
383
384 Also, an MBean for each servlet deployed should be created and its
385 JMX ObjectName placed into the DeploymentInfo.mbeans list so that the
386 JSR77 layer can create the approriate model view. The servlet MBean
387 needs to provide access to the min, max and total time in milliseconds.
388 Expose this information via MinServiceTime, MaxServiceTime and TotalServiceTime
389 attributes to integrate seemlessly with the JSR77 factory layer.
390
391 @param di, The deployment info that contains the context-root element value
392 from the J2EE application/module/web application.xml descriptor. This may
393 be null if war was is not being deployed as part of an enterprise application.
394 It also contains the URL of the web application war.
395 */
396 public synchronized void start(DeploymentInfo di) throws DeploymentException
397 {
398 Thread thread = Thread.currentThread();
399 ClassLoader appClassLoader = thread.getContextClassLoader();
400 try
401 {
402 // Create a classloader for the war to ensure a unique ENC
403 URL[] empty = {};
404 URLClassLoader warLoader = URLClassLoader.newInstance(empty, di.ucl);
405 thread.setContextClassLoader(warLoader);
406 WebDescriptorParser webAppParser = new DescriptorParser(di);
407 String webContext = di.webContext;
408 if( webContext != null )
409 {
410 if( webContext.length() > 0 && webContext.charAt(0) != '/' )
411 webContext = "/" + webContext;
412 }
413 // Get the war URL
414 URL warURL = di.localUrl != null ? di.localUrl : di.url;
415
416 if (log.isDebugEnabled())
417 {
418 log.debug("webContext: " + webContext);
419 log.debug("warURL: " + warURL);
420 log.debug("webAppParser: " + webAppParser);
421 }
422
423 // Parse the web.xml and jboss-web.xml descriptors
424 WebMetaData metaData = (WebMetaData) di.metaData;
425 parseMetaData(webContext, warURL, di.shortName, metaData);
426 WebApplication warInfo = new WebApplication(metaData);
427 warInfo.setDeploymentInfo(di);
428 performDeploy(warInfo, warURL.toString(), webAppParser);
429 deploymentMap.put(warURL.toString(), warInfo);
430
431 // Generate an event for the startup
432 super.start(di);
433 }
434 catch(DeploymentException e)
435 {
436 throw e;
437 }
438 catch(Exception e)
439 {
440 throw new DeploymentException("Error during deploy", e);
441 }
442 finally
443 {
444 thread.setContextClassLoader(appClassLoader);
445 }
446 }
447
448 /** This method is called by the deploy() method template and must be overriden by
449 subclasses to perform the web container specific deployment steps.
450 @param webApp, The web application information context. This contains the
451 metadata such as the context-root element value from the J2EE
452 application/module/web application.xml descriptor and virtual-host.
453 @param warUrl, The string for the URL of the web application war.
454 @param webAppParser, The callback interface the web container should use to
455 setup the web app JNDI environment for use by the web app components. This
456 needs to be invoked after the web app class loader is known, but before
457 and web app components attempt to access the java:comp/env JNDI namespace.
458 @return WebApplication, the web application information required by the
459 AbstractWebContainer class to track the war deployment status.
460 */
461 protected abstract void performDeploy(WebApplication webApp, String warUrl,
462 WebDescriptorParser webAppParser) throws Exception;
463
464 /** A template pattern implementation of the undeploy() method. This method
465 calls the {@link #performUndeploy(String) performUndeploy()} method to
466 perform the container specific undeployment steps and unregisters the
467 the warUrl from the deployment map.
468 */
469 public synchronized void stop(DeploymentInfo di)
470 throws DeploymentException
471 {
472 URL warURL = di.localUrl != null ? di.localUrl : di.url;
473 String warUrl = warURL.toString();
474 try
475 {
476 performUndeploy(warUrl);
477 // Remove the web application ENC...
478 deploymentMap.remove(warUrl);
479
480 // Generate an event for the stop
481 super.stop(di);
482 }
483 catch(DeploymentException e)
484 {
485 throw e;
486 }
487 catch(Exception e)
488 {
489 throw new DeploymentException("Error during deploy", e);
490 }
491 }
492
493 /** Called as part of the undeploy() method template to ask the
494 subclass for perform the web container specific undeployment steps.
495 */
496 protected abstract void performUndeploy(String warUrl) throws Exception;
497
498 /** See if a war is deployed.
499 @jmx:managed-operation
500 */
501 public boolean isDeployed(String warUrl)
502 {
503 return deploymentMap.containsKey(warUrl);
504 }
505
506 /** Get the WebApplication object for a deployed war.
507 @param warUrl, the war url string as originally passed to deploy().
508 @return The WebApplication created during the deploy step if the
509 warUrl is valid, null if no such deployment exists.
510 */
511 public WebApplication getDeployedApp(String warUrl)
512 {
513 WebApplication appInfo = (WebApplication) deploymentMap.get(warUrl);
514 return appInfo;
515 }
516
517 /** Returns the applications deployed by the web container subclasses.
518 @jmx:managed-attribute
519 @return An Iterator of WebApplication objects for the deployed wars.
520 */
521 public Iterator getDeployedApplications()
522 {
523 return deploymentMap.values().iterator();
524 }
525
526 /** An accessor for any configuration element set via setConfig. This
527 method always returns null and must be overriden by subclasses to
528 return a valid value.
529 @jmx:managed-attribute
530 */
531 public Element getConfig()
532 {
533 return null;
534 }
535 /** This method is invoked to import an arbitrary XML configuration tree.
536 Subclasses should override this method if they support such a configuration
537 capability. This implementation does nothing.
538 @jmx:managed-attribute
539 */
540 public void setConfig(Element config)
541 {
542 }
543
544 /** This method is invoked from within subclass performDeploy() method
545 implementations when they invoke WebDescriptorParser.parseWebAppDescriptors().
546
547 @param loader, the ClassLoader for the web application. May not be null.
548 @param metaData, the WebMetaData from the WebApplication object passed to
549 the performDeploy method.
550 */
551 protected void parseWebAppDescriptors(DeploymentInfo di, ClassLoader loader,
552 WebMetaData metaData)
553 throws Exception
554 {
555 log.debug("AbstractWebContainer.parseWebAppDescriptors, Begin");
556 InitialContext iniCtx = new InitialContext();
557 Context envCtx = null;
558 Thread currentThread = Thread.currentThread();
559 ClassLoader currentLoader = currentThread.getContextClassLoader();
560 try
561 {
562 // Create a java:comp/env environment unique for the web application
563 log.debug("Creating ENC using ClassLoader: "+loader);
564 ClassLoader parent = loader.getParent();
565 while( parent != null )
566 {
567 log.debug(".."+parent);
568 parent = parent.getParent();
569 }
570 currentThread.setContextClassLoader(loader);
571 metaData.setENCLoader(loader);
572 envCtx = (Context) iniCtx.lookup("java:comp");
573
574 // Add a link to the global transaction manager
575 envCtx.bind("UserTransaction", new LinkRef("UserTransaction"));
576 log.debug("Linked java:comp/UserTransaction to JNDI name: UserTransaction");
577 envCtx = envCtx.createSubcontext("env");
578 }
579 finally
580 {
581 currentThread.setContextClassLoader(currentLoader);
582 }
583
584 Iterator envEntries = metaData.getEnvironmentEntries();
585 log.debug("addEnvEntries");
586 addEnvEntries(envEntries, envCtx);
587 Iterator resourceEnvRefs = metaData.getResourceEnvReferences();
588 log.debug("linkResourceEnvRefs");
589 linkResourceEnvRefs(resourceEnvRefs, envCtx);
590 Iterator resourceRefs = metaData.getResourceReferences();
591 log.debug("linkResourceRefs");
592 linkResourceRefs(resourceRefs, envCtx);
593 Iterator ejbRefs = metaData.getEjbReferences();
594 log.debug("linkEjbRefs");
595 linkEjbRefs(ejbRefs, envCtx, di);
596 Iterator ejbLocalRefs = metaData.getEjbLocalReferences();
597 log.debug("linkEjbLocalRefs");
598 linkEjbLocalRefs(ejbLocalRefs, envCtx, di);
599 String securityDomain = metaData.getSecurityDomain();
600 log.debug("linkSecurityDomain");
601 linkSecurityDomain(securityDomain, envCtx);
602 log.debug("AbstractWebContainer.parseWebAppDescriptors, End");
603 }
604
605 protected void addEnvEntries(Iterator envEntries, Context envCtx)
606 throws ClassNotFoundException, NamingException
607 {
608 while( envEntries.hasNext() )
609 {
610 EnvEntryMetaData entry = (EnvEntryMetaData) envEntries.next();
611 log.debug("Binding env-entry: "+entry.getName()+" of type: " +
612 entry.getType()+" to value:"+entry.getValue());
613 EnvEntryMetaData.bindEnvEntry(envCtx, entry);
614 }
615 }
616
617 protected void linkResourceEnvRefs(Iterator resourceEnvRefs, Context envCtx)
618 throws NamingException
619 {
620 while( resourceEnvRefs.hasNext() )
621 {
622 ResourceEnvRefMetaData ref = (ResourceEnvRefMetaData) resourceEnvRefs.next();
623 String resourceName = ref.getJndiName();
624 String refName = ref.getRefName();
625 if( ref.getType().equals("java.net.URL") )
626 {
627 try
628 {
629 log.debug("Binding '"+refName+"' to URL: "+resourceName);
630 URL url = new URL(resourceName);
631 Util.bind(envCtx, refName, url);
632 }
633 catch(MalformedURLException e)
634 {
635 throw new NamingException("Malformed URL:"+e.getMessage());
636 }
637 }
638 else if( resourceName != null )
639 {
640 log.debug("Linking '"+refName+"' to JNDI name: "+resourceName);
641 Util.bind(envCtx, refName, new LinkRef(resourceName));
642 }
643 else
644 {
645 throw new NamingException("resource-env-ref: "+refName
646 +" has no valid JNDI binding. Check the jboss-web/resource-env-ref.");
647 }
648 }
649 }
650
651 protected void linkResourceRefs(Iterator resourceRefs, Context envCtx)
652 throws NamingException
653 {
654 while( resourceRefs.hasNext() )
655 {
656 ResourceRefMetaData ref = (ResourceRefMetaData) resourceRefs.next();
657 String jndiName = ref.getJndiName();
658 String refName = ref.getRefName();
659 if( ref.getType().equals("java.net.URL") )
660 {
661 try
662 {
663 log.debug("Binding '"+refName+"' to URL: "+jndiName);
664 URL url = new URL(jndiName);
665 Util.bind(envCtx, refName, url);
666 }
667 catch(MalformedURLException e)
668 {
669 throw new NamingException("Malformed URL:"+e.getMessage());
670 }
671 }
672 else if( jndiName != null )
673 {
674 log.debug("Linking '"+refName+"' to JNDI name: "+jndiName);
675 Util.bind(envCtx, refName, new LinkRef(jndiName));
676 }
677 else
678 {
679 throw new NamingException("resource-ref: "+refName
680 +" has no valid JNDI binding. Check the jboss-web/resource-ref.");
681 }
682 }
683 }
684
685 protected void linkEjbRefs(Iterator ejbRefs, Context envCtx, DeploymentInfo di)
686 throws NamingException
687 {
688 while( ejbRefs.hasNext() )
689 {
690 EjbRefMetaData ejb = (EjbRefMetaData) ejbRefs.next();
691 String name = ejb.getName();
692 String linkName = ejb.getLink();
693 String jndiName = null;
694
695 //use ejb-link if it is specified
696 if ( linkName != null )
697 {
698 jndiName = EjbUtil.findEjbLink(server, di, linkName);
699
700 //if flag does not allow misconfigured ejb-links, it is an error
701 if ( ( jndiName == null ) && !(getLenientEjbLink()) )
702 throw new NamingException("ejb-ref: "+name+", no ejb-link match");
703 }
704
705
706 //fall through to the jndiName
707 if ( jndiName == null )
708 {
709 jndiName = ejb.getJndiName();
710 if (jndiName == null )
711 throw new NamingException("ejb-ref: "+name+", no ejb-link in web.xml and no jndi-name in jboss-web.xml");
712 }
713
714 log.debug("Linking ejb-ref: "+name+" to JNDI name: "+jndiName);
715 Util.bind(envCtx, name, new LinkRef(jndiName));
716 }
717 }
718
719 protected void linkEjbLocalRefs(Iterator ejbRefs, Context envCtx, DeploymentInfo di)
720 throws NamingException
721 {
722 while( ejbRefs.hasNext() )
723 {
724 EjbLocalRefMetaData ejb = (EjbLocalRefMetaData) ejbRefs.next();
725 String name = ejb.getName();
726 String linkName = ejb.getLink();
727 String jndiName = null;
728
729 //use the ejb-link field if it is specified
730 if ( linkName != null )
731 {
732 jndiName = EjbUtil.findLocalEjbLink(server, di, linkName);
733
734 //if flag does not allow misconfigured ejb-links, it is an error
735 if ( ( jndiName == null ) && !(getLenientEjbLink()) )
736 throw new NamingException("ejb-ref: "+name+", no ejb-link match");
737 }
738
739
740 if (jndiName == null)
741 {
742 jndiName = ejb.getJndiName();
743 if ( jndiName == null )
744 {
745 String msg = null;
746 if( linkName == null )
747 {
748 msg = "ejb-local-ref: '"+name+"', no ejb-link in web.xml and "
749 + "no local-jndi-name in jboss-web.xml";
750 }
751 else
752 {
753 msg = "ejb-local-ref: '"+name+"', with web.xml ejb-link: '"
754 + linkName + "' failed to resolve to an ejb with a LocalHome";
755 }
756 throw new NamingException(msg);
757 }
758 }
759
760 log.debug("Linking ejb-local-ref: "+name+" to JNDI name: "+jndiName);
761 Util.bind(envCtx, name, new LinkRef(jndiName));
762 }
763 }
764
765 /** This creates a java:comp/env/security context that contains a
766 securityMgr binding pointing to an AuthenticationManager implementation
767 and a realmMapping binding pointing to a RealmMapping implementation.
768 If the jboss-web.xml descriptor contained a security-domain element
769 then the bindings are LinkRefs to the jndi name specified by the
770 security-domain element. If there was no security-domain element then
771 the bindings are to NullSecurityManager instance which simply allows
772 all access.
773 */
774 protected void linkSecurityDomain(String securityDomain, Context envCtx)
775 throws NamingException
776 {
777 if( securityDomain == null )
778 {
779 log.debug("Binding security/securityMgr to NullSecurityManager");
780 Object securityMgr = new NullSecurityManager("java:/jaas/null");
781 Util.bind(envCtx, "security/securityMgr", securityMgr);
782 Util.bind(envCtx, "security/realmMapping", securityMgr);
783 Util.bind(envCtx, "security/security-domain", new LinkRef("java:/jaas/null"));
784 Util.bind(envCtx, "security/subject", new LinkRef("java:/jaas/null/subject"));
785 }
786 else
787 {
788 log.debug("Linking security/securityMgr to JNDI name: "+securityDomain);
789 Util.bind(envCtx, "security/securityMgr", new LinkRef(securityDomain));
790 Util.bind(envCtx, "security/realmMapping", new LinkRef(securityDomain));
791 Util.bind(envCtx, "security/security-domain", new LinkRef(securityDomain));
792 Util.bind(envCtx, "security/subject", new LinkRef(securityDomain+"/subject"));
793 }
794 }
795
796 /** A utility method that searches the given loader for the
797 resources: "javax/servlet/resources/web-app_2_3.dtd",
798 "org/apache/jasper/resources/jsp12.dtd", and "javax/ejb/EJBHome.class"
799 and returns an array of URL strings. Any jar: urls are reduced to the
800 underlying <url> portion of the 'jar:<url>!/{entry}' construct.
801 */
802 public String[] getStandardCompileClasspath(ClassLoader loader)
803 {
804 String[] jspResources = {
805 "javax/servlet/resources/web-app_2_3.dtd",
806 "org/apache/jasper/resources/jsp12.dtd",
807 "javax/ejb/EJBHome.class"
808 };
809 ArrayList tmp = new ArrayList();
810 for(int j = 0; j < jspResources.length; j ++)
811 {
812 URL rsrcURL = loader.getResource(jspResources[j]);
813 if( rsrcURL != null )
814 {
815 String url = rsrcURL.toExternalForm();
816 if( rsrcURL.getProtocol().equals("jar") )
817 {
818 // Parse the jar:<url>!/{entry} URL
819 url = url.substring(4);
820 int seperator = url.indexOf('!');
821 url = url.substring(0, seperator);
822 }
823 tmp.add(url);
824 }
825 else
826 {
827 log.warn("Failed to fin jsp rsrc: "+jspResources[j]);
828 }
829 }
830 log.trace("JSP StandardCompileClasspath: " + tmp);
831 String[] cp = new String[tmp.size()];
832 tmp.toArray(cp);
833 return cp;
834 }
835
836 /** A utility method that walks up the ClassLoader chain starting at
837 the given loader and queries each ClassLoader for a 'URL[] getURLs()'
838 method from which a complete classpath of URL strings is built.
839 */
840 public String[] getCompileClasspath(ClassLoader loader)
841 {
842 HashSet tmp = new HashSet();
843 ClassLoader cl = loader;
844 while( cl != null )
845 {
846 URL[] urls = getClassLoaderURLs(cl);
847 addURLs(tmp, urls);
848 cl = cl.getParent();
849 }
850 try
851 {
852 URL[] globalUrls = (URL[])getServer().getAttribute(LoaderRepositoryFactory.DEFAULT_LOADER_REPOSITORY,
853 "URLs");
854 addURLs(tmp, globalUrls);
855 }
856 catch (Exception e)
857 {
858 log.warn("Could not get global URL[] from default loader repository!");
859 } // end of try-catch
860 log.trace("JSP CompileClasspath: " + tmp);
861 String[] cp = new String[tmp.size()];
862 tmp.toArray(cp);
863 return cp;
864 }
865
866 private void addURLs(Set urlSet, URL[] urls)
867 {
868 for(int u = 0; u < urls.length; u ++)
869 {
870 URL url = urls[u];
871 urlSet.add(url.toExternalForm());
872 }
873 }
874
875 /** Use reflection to access a URL[] getURLs method so that non-URLClassLoader
876 *class loaders that support this method can provide info.
877 */
878 protected URL[] getClassLoaderURLs(ClassLoader cl)
879 {
880 URL[] urls = {};
881 try
882 {
883 Class returnType = urls.getClass();
884 Class[] parameterTypes = {};
885 Method getURLs = cl.getClass().getMethod("getURLs", parameterTypes);
886 if( returnType.isAssignableFrom(getURLs.getReturnType()) )
887 {
888 Object[] args = {};
889 urls = (URL[]) getURLs.invoke(cl, args);
890 }
891 if( urls == null || urls.length == 0 )
892 {
893 getURLs = cl.getClass().getMethod("getAllURLs", parameterTypes);
894 if( returnType.isAssignableFrom(getURLs.getReturnType()) )
895 {
896 Object[] args = {};
897 urls = (URL[]) getURLs.invoke(cl, args);
898 }
899 }
900 }
901 catch(Exception ignore)
902 {
903 }
904 return urls;
905 }
906
907 /** This method creates a context-root string from either the
908 WEB-INF/jboss-web.xml context-root element is one exists, or the
909 filename portion of the warURL. It is called if the DeploymentInfo
910 webContext value is null which indicates a standalone war deployment.
911 A war name of ROOT.war is handled as a special case of a war that
912 should be installed as the default web context.
913 */
914 protected void parseMetaData(String ctxPath, URL warURL, String warName,
915 WebMetaData metaData)
916 throws DeploymentException
917 {
918 InputStream jbossWebIS = null;
919 InputStream webIS = null;
920
921 // Parse the war deployment descriptors, web.xml and jboss-web.xml
922 try
923 {
924 // See if the warUrl is a directory
925 File warDir = new File(warURL.getFile());
926 if( warURL.getProtocol().equals("file") && warDir.isDirectory() == true )
927 {
928 File webDD = new File(warDir, "WEB-INF/web.xml");
929 if( webDD.exists() == true )
930 webIS = new FileInputStream(webDD);
931 File jbossWebDD = new File(warDir, "WEB-INF/jboss-web.xml");
932 if( jbossWebDD.exists() == true )
933 jbossWebIS = new FileInputStream(jbossWebDD);
934 }
935 else
936 {
937 // First check for a WEB-INF/web.xml and a WEB-INF/jboss-web.xml
938 InputStream warIS = warURL.openStream();
939 java.util.zip.ZipInputStream zipIS = new java.util.zip.ZipInputStream(warIS);
940 java.util.zip.ZipEntry entry;
941 byte[] buffer = new byte[512];
942 int bytes;
943 while( (entry = zipIS.getNextEntry()) != null )
944 {
945 if( entry.getName().equals("WEB-INF/web.xml") )
946 {
947 ByteArrayOutputStream baos = new ByteArrayOutputStream();
948 while( (bytes = zipIS.read(buffer)) > 0 )
949 {
950 baos.write(buffer, 0, bytes);
951 }
952 webIS = new ByteArrayInputStream(baos.toByteArray());
953 }
954 else if( entry.getName().equals("WEB-INF/jboss-web.xml") )
955 {
956 ByteArrayOutputStream baos = new ByteArrayOutputStream();
957 while( (bytes = zipIS.read(buffer)) > 0 )
958 {
959 baos.write(buffer, 0, bytes);
960 }
961 jbossWebIS = new ByteArrayInputStream(baos.toByteArray());
962 }
963 }
964 zipIS.close();
965 }
966
967 XmlFileLoader xmlLoader = new XmlFileLoader();
968 String warURI = warURL.toExternalForm();
969 try
970 {
971 if( webIS != null )
972 {
973 Document webDoc = xmlLoader.getDocument(webIS, warURI+"/WEB-INF/web.xml");
974 Element web = webDoc.getDocumentElement();
975 metaData.importXml(web);
976 }
977 }
978 catch(Exception e)
979 {
980 throw new DeploymentException("Failed to parse WEB-INF/web.xml", e);
981 }
982 try
983 {
984 if( jbossWebIS != null )
985 {
986 Document jbossWebDoc = xmlLoader.getDocument(jbossWebIS, warURI+"/WEB-INF/jboss-web.xml");
987 Element jbossWeb = jbossWebDoc.getDocumentElement();
988 metaData.importXml(jbossWeb);
989 }
990 }
991 catch(Exception e)
992 {
993 throw new DeploymentException("Failed to parse WEB-INF/jboss-web.xml", e);
994 }
995
996 }
997 catch(Exception e)
998 {
999 log.warn("Failed to parse descriptors for war("+warURL+")", e);
1000 }
1001
1002 // Build a war root context from the war name if one was not specified
1003 String webContext = ctxPath;
1004 if( webContext == null )
1005 webContext = metaData.getContextRoot();
1006 if( webContext == null )
1007 {
1008 // Build the context from the war name, strip the .war suffix
1009 webContext = warName;
1010 webContext = webContext.replace('\\', '/');
1011 if( webContext.endsWith("/") )
1012 webContext = webContext.substring(0, webContext.length()-1);
1013 int prefix = webContext.lastIndexOf('/');
1014 if( prefix > 0 )
1015 webContext = webContext.substring(prefix+1);
1016 int suffix = webContext.lastIndexOf(".war");
1017 if( suffix > 0 )
1018 webContext = webContext.substring(0, suffix);
1019 // Strip any '<int-value>.' prefix
1020 int index = 0;
1021 for(; index < webContext.length(); index ++)
1022 {
1023 char c = webContext.charAt(index);
1024 if( Character.isDigit(c) == false && c != '.' )
1025 break;
1026 }
1027 webContext = webContext.substring(index);
1028 }
1029
1030 // Servlet containers are anal about the web context starting with '/'
1031 if( webContext.length() > 0 && webContext.charAt(0) != '/' )
1032 webContext = "/" + webContext;
1033 // And also the default root context must be an empty string, not '/'
1034 else if( webContext.equals("/") )
1035 webContext = "";
1036 metaData.setContextRoot(webContext);
1037 }
1038
1039 /** An inner class that maps the WebDescriptorParser.parseWebAppDescriptors()
1040 onto the protected parseWebAppDescriptors() AbstractWebContainer method.
1041 */
1042 private class DescriptorParser implements WebDescriptorParser
1043 {
1044 DeploymentInfo di;
1045 DescriptorParser(DeploymentInfo di)
1046 {
1047 this.di = di;
1048 }
1049 public void parseWebAppDescriptors(ClassLoader loader, WebMetaData metaData) throws Exception
1050 {
1051 AbstractWebContainer.this.parseWebAppDescriptors(di, loader, metaData);
1052 }
1053 public DeploymentInfo getDeploymentInfo()
1054 {
1055 return di;
1056 }
1057 }
1058 }