View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.webapp;
15  
16  import java.io.IOException;
17  import java.net.URI;
18  import java.net.URL;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.EventListener;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.servlet.Servlet;
30  import javax.servlet.ServletContextEvent;
31  import javax.servlet.ServletContextListener;
32  
33  import org.eclipse.jetty.util.Loader;
34  import org.eclipse.jetty.util.log.Log;
35  import org.eclipse.jetty.util.log.Logger;
36  import org.eclipse.jetty.util.resource.Resource;
37  import org.eclipse.jetty.xml.XmlParser;
38  
39  /* ------------------------------------------------------------ */
40  /** TagLibConfiguration.
41   * 
42   * The class searches for TLD descriptors found in web.xml, in WEB-INF/*.tld files of the web app
43   * or *.tld files within jars found in WEB-INF/lib of the webapp.   Any listeners defined in these
44   * tld's are added to the context.
45   * 
46   * <bile>This is total rubbish special case for JSPs! If there was a general use-case for web app
47   * frameworks to register listeners directly, then a generic mechanism could have been added to the servlet
48   * spec.  Instead some special purpose JSP support is required that breaks all sorts of encapsulation rules as
49   * the servlet container must go searching for and then parsing the descriptors for one particular framework.
50   * It only appears to be used by JSF, which is being developed by the same developer who implemented this
51   * feature in the first place!
52   * </bile>
53   * 
54   * 
55   * Note- this has been superceded by the new TldScanner in jasper which uses ServletContainerInitializer to
56   * find all the listeners in tag libs and register them.
57   */
58  public class TagLibConfiguration extends AbstractConfiguration
59  {
60      private static final Logger LOG = Log.getLogger(TagLibConfiguration.class);
61  
62      public static final String TLD_RESOURCES = "org.eclipse.jetty.tlds";
63      
64    
65      /**
66       * TagLibListener
67       *
68       * A listener that does the job of finding .tld files that contain
69       * (other) listeners that need to be called by the servlet container.
70       * 
71       * This implementation is necessitated by the fact that it is only
72       * after all the Configuration classes have run that we will
73       * parse web.xml/fragments etc and thus find tlds mentioned therein.
74       * 
75       * Note: TagLibConfiguration is not used in jetty-8 as jasper (JSP engine)
76       * uses the new TldScanner class - a ServletContainerInitializer from
77       * Servlet Spec 3 - to find all listeners in taglibs and register them
78       * with the servlet container.
79       */
80      public  class TagLibListener implements ServletContextListener {
81          private List<EventListener> _tldListeners;
82          private WebAppContext _context;       
83          
84          public TagLibListener (WebAppContext context) {
85              _context = context;
86          }
87  
88          public void contextDestroyed(ServletContextEvent sce)
89          {
90              if (_tldListeners == null)
91                  return;
92              
93              for (int i=_tldListeners.size()-1; i>=0; i--) {
94                  EventListener l = _tldListeners.get(i);
95                  if (l instanceof ServletContextListener) {
96                      ((ServletContextListener)l).contextDestroyed(sce);
97                  }
98              }
99          }
100 
101         public void contextInitialized(ServletContextEvent sce)
102         {
103             try 
104             {
105                 //For jasper 2.1: 
106                 //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
107                 try
108                 {
109                     Class<?> clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache");
110                     assert clazz!=null;
111                     Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
112                    
113                     Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
114                     
115                     if (tld_resources != null)
116                     {
117                         //get the jar file names of the files
118                         for (Resource r:tld_resources)
119                         {
120                             Resource jarResource = extractJarResource(r);
121                             //jasper is happy with an empty list of tlds
122                             if (!tldMap.containsKey(jarResource.getURI()))
123                                 tldMap.put(jarResource.getURI(), null);
124 
125                         }
126                         //set the magic context attribute that tells jasper about the system tlds
127                         sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
128                     }
129                 }
130                 catch (ClassNotFoundException e)
131                 {
132                     LOG.ignore(e);
133                 }
134                
135                 //find the tld files and parse them to get out their
136                 //listeners
137                 Set<Resource> tlds = findTldResources();
138                 List<TldDescriptor> descriptors = parseTlds(tlds);
139                 processTlds(descriptors);
140                 
141                 if (_tldListeners == null)
142                     return;
143                 
144                 //call the listeners that are ServletContextListeners, put the
145                 //rest into the context's list of listeners to call at the appropriate
146                 //moment
147                 for (EventListener l:_tldListeners) {
148                     if (l instanceof ServletContextListener) {
149                         ((ServletContextListener)l).contextInitialized(sce);
150                     } else {
151                         _context.addEventListener(l);
152                     }
153                 }
154                 
155             } 
156             catch (Exception e) {
157                 LOG.warn(e);
158             }
159         }
160 
161 
162         
163         
164         private Resource extractJarResource (Resource r)
165         {
166             if (r == null)
167                 return null;
168             
169             try
170             {
171                 String url = r.getURI().toURL().toString();
172                 int idx = url.lastIndexOf("!/");
173                 if (idx >= 0)
174                     url = url.substring(0, idx);
175                 if (url.startsWith("jar:"))
176                     url = url.substring(4);
177                 return Resource.newResource(url);
178             }
179             catch (IOException e)
180             {
181                 LOG.warn(e);
182                 return null;
183             }
184         }
185     
186         /**
187          * Find all the locations that can harbour tld files that may contain
188          * a listener which the web container is supposed to instantiate and
189          * call.
190          * 
191          * @return
192          * @throws IOException
193          */
194         private Set<Resource> findTldResources () throws IOException {
195             
196             Set<Resource> tlds = new HashSet<Resource>();
197             
198             // Find tld's from web.xml
199             // When web.xml was processed, it should have created aliases for all TLDs.  So search resources aliases
200             // for aliases ending in tld
201             if (_context.getResourceAliases()!=null && 
202                     _context.getBaseResource()!=null && 
203                     _context.getBaseResource().exists())
204             {
205                 Iterator<String> iter=_context.getResourceAliases().values().iterator();
206                 while(iter.hasNext())
207                 {
208                     String location = iter.next();
209                     if (location!=null && location.toLowerCase().endsWith(".tld"))
210                     {
211                         if (!location.startsWith("/"))
212                             location="/WEB-INF/"+location;
213                         Resource l=_context.getBaseResource().addPath(location);
214                         tlds.add(l);
215                     }
216                 }
217             }
218             
219             // Look for any tlds in WEB-INF directly.
220             Resource web_inf = _context.getWebInf();
221             if (web_inf!=null)
222             {
223                 String[] contents = web_inf.list();
224                 for (int i=0;contents!=null && i<contents.length;i++)
225                 {
226                     if (contents[i]!=null && contents[i].toLowerCase().endsWith(".tld"))
227                     {
228                         Resource l=web_inf.addPath(contents[i]);
229                         tlds.add(l);
230                     }
231                 }
232             }
233             
234             //Look for tlds in common location of WEB-INF/tlds
235             if (web_inf != null) {
236                 Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
237                 if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
238                     String[] contents = web_inf_tlds.list();
239                     for (int i=0;contents!=null && i<contents.length;i++)
240                     {
241                         if (contents[i]!=null && contents[i].toLowerCase().endsWith(".tld"))
242                         {
243                             Resource l=web_inf_tlds.addPath(contents[i]);
244                             tlds.add(l);
245                         }
246                     }
247                 } 
248             }
249 
250             // Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
251             // the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
252             // and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
253             @SuppressWarnings("unchecked")
254             Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
255             if (tld_resources!=null)
256                 tlds.addAll(tld_resources);
257             
258             return tlds;
259         }
260         
261         
262         /**
263          * Parse xml into in-memory tree
264          * @param tlds
265          * @return
266          */
267         private List<TldDescriptor> parseTlds (Set<Resource> tlds) {         
268             List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
269             
270             Resource tld = null;
271             Iterator<Resource> iter = tlds.iterator();
272             while (iter.hasNext())
273             {
274                 try
275                 {
276                     tld = iter.next();
277                     if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
278                    
279                     TldDescriptor d = new TldDescriptor(tld);
280                     d.parse();
281                     descriptors.add(d);
282                 }
283                 catch(Exception e)
284                 {
285                     LOG.warn("Unable to parse TLD: " + tld,e);
286                 }
287             }
288             return descriptors;
289         }
290         
291         
292         /**
293          * Create listeners from the parsed tld trees
294          * @param descriptors
295          * @throws Exception
296          */
297         private void processTlds (List<TldDescriptor> descriptors) throws Exception {
298 
299             TldProcessor processor = new TldProcessor();
300             for (TldDescriptor d:descriptors)
301                 processor.process(_context, d); 
302             
303             _tldListeners = new ArrayList<EventListener>(processor.getListeners());
304         }
305     }
306     
307     
308     
309     
310     /**
311      * TldDescriptor
312      *
313      *
314      */
315     public static class TldDescriptor extends Descriptor
316     {
317         protected static XmlParser __parserSingleton;
318 
319         public TldDescriptor(Resource xml)
320         {
321             super(xml);
322         }
323 
324         @Override
325         public void ensureParser() throws ClassNotFoundException
326         {
327            if (__parserSingleton == null)
328                __parserSingleton = newParser();
329             _parser = __parserSingleton;
330         }
331 
332         @Override
333         public XmlParser newParser() throws ClassNotFoundException
334         {
335             // Create a TLD parser
336             XmlParser parser = new XmlParser(false);
337             
338             URL taglib11=null;
339             URL taglib12=null;
340             URL taglib20=null;
341             URL taglib21=null;
342 
343             try
344             {
345                 Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
346                 taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
347                 taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
348                 taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
349                 taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
350             }
351             catch(Exception e)
352             {
353                 LOG.ignore(e);
354             }
355             finally
356             {
357                 if(taglib11==null)
358                     taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd",true);
359                 if(taglib12==null)
360                     taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd",true);
361                 if(taglib20==null)
362                     taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd",true);
363                 if(taglib21==null)
364                     taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd",true);
365             }
366             
367 
368             if(taglib11!=null)
369             {
370                 redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);  
371                 redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
372             }
373             if(taglib12!=null)
374             {
375                 redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
376                 redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
377             }
378             if(taglib20!=null)
379             {
380                 redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
381                 redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
382             }
383             if(taglib21!=null)
384             {
385                 redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
386                 redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
387             }
388             
389             parser.setXpath("/taglib/listener/listener-class");
390             return parser;
391         }
392         
393         public void parse ()
394         throws Exception
395         {
396             ensureParser();
397             try
398             {
399                 //xerces on apple appears to sometimes close the zip file instead
400                 //of the inputstream, so try opening the input stream, but if
401                 //that doesn't work, fallback to opening a new url
402                 _root = _parser.parse(_xml.getInputStream());
403             }
404             catch (Exception e)
405             {
406                 _root = _parser.parse(_xml.getURL().toString());
407             }
408 
409             if (_root==null)
410             {
411                 LOG.warn("No TLD root in {}",_xml);
412             }
413         }
414     }
415     
416     
417     /**
418      * TldProcessor
419      *
420      * Process TldDescriptors representing tag libs to find listeners.
421      */
422     public class TldProcessor extends IterativeDescriptorProcessor
423     {
424         public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
425         XmlParser _parser;
426         List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
427         List<EventListener> _listeners;
428         
429         
430         public TldProcessor ()
431         throws Exception
432         {  
433             _listeners = new ArrayList<EventListener>();
434             registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
435         }
436       
437 
438         public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
439         {     
440             String className=node.getString("listener-class",false,true);
441             if (LOG.isDebugEnabled()) 
442                 LOG.debug("listener="+className);
443 
444             try
445             {
446                 Class<?> listenerClass = context.loadClass(className);
447                 EventListener l = (EventListener)listenerClass.newInstance();
448                 _listeners.add(l);
449             }
450             catch(Exception e)
451             {
452                 LOG.warn("Could not instantiate listener "+className+": "+e);
453                 LOG.debug(e);
454             }
455             catch(Error e)
456             {
457                 LOG.warn("Could not instantiate listener "+className+": "+e);
458                 LOG.debug(e);
459             }
460 
461         }
462 
463         @Override
464         public void end(WebAppContext context, Descriptor descriptor)
465         {
466         }
467 
468         @Override
469         public void start(WebAppContext context, Descriptor descriptor)
470         {  
471         }
472         
473         public List<EventListener> getListeners() {
474             return _listeners;
475         }
476     }
477 
478 
479     @Override
480     public void preConfigure(WebAppContext context) throws Exception
481     {
482         try
483         {
484             Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
485         }
486         catch (Exception e)
487         {
488             //no jsp available, don't parse TLDs
489             return;
490         }
491 
492         TagLibListener tagLibListener = new TagLibListener(context);
493         context.addEventListener(tagLibListener);
494     }
495     
496 
497     @Override
498     public void configure (WebAppContext context) throws Exception
499     {         
500     }
501 
502     @Override
503     public void postConfigure(WebAppContext context) throws Exception
504     {     
505     }
506 
507 
508     @Override
509     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
510     {
511     }
512 
513 
514     @Override
515     public void deconfigure(WebAppContext context) throws Exception
516     {
517     } 
518 }