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                     Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
111                    
112                     Map<URI, List<String>> tldMap = new HashMap<URI, List<String>>();
113                     
114                     if (tld_resources != null)
115                     {
116                         //get the jar file names of the files
117                         for (Resource r:tld_resources)
118                         {
119                             Resource jarResource = extractJarResource(r);
120                             //jasper is happy with an empty list of tlds
121                             if (!tldMap.containsKey(jarResource.getURI()))
122                                 tldMap.put(jarResource.getURI(), null);
123 
124                         }
125                         //set the magic context attribute that tells jasper about the system tlds
126                         sce.getServletContext().setAttribute("com.sun.appserv.tld.map", tldMap);
127                     }
128                 }
129                 catch (ClassNotFoundException e)
130                 {
131                     LOG.ignore(e);
132                 }
133                
134                 //find the tld files and parse them to get out their
135                 //listeners
136                 Set<Resource> tlds = findTldResources();
137                 List<TldDescriptor> descriptors = parseTlds(tlds);
138                 processTlds(descriptors);
139                 
140                 if (_tldListeners == null)
141                     return;
142                 
143                 //call the listeners that are ServletContextListeners, put the
144                 //rest into the context's list of listeners to call at the appropriate
145                 //moment
146                 for (EventListener l:_tldListeners) {
147                     if (l instanceof ServletContextListener) {
148                         ((ServletContextListener)l).contextInitialized(sce);
149                     } else {
150                         _context.addEventListener(l);
151                     }
152                 }
153                 
154             } 
155             catch (Exception e) {
156                 LOG.warn(e);
157             }
158         }
159 
160 
161         
162         
163         private Resource extractJarResource (Resource r)
164         {
165             if (r == null)
166                 return null;
167             
168             try
169             {
170                 String url = r.getURI().toURL().toString();
171                 int idx = url.lastIndexOf("!/");
172                 if (idx >= 0)
173                     url = url.substring(0, idx);
174                 if (url.startsWith("jar:"))
175                     url = url.substring(4);
176                 return Resource.newResource(url);
177             }
178             catch (IOException e)
179             {
180                 LOG.warn(e);
181                 return null;
182             }
183         }
184     
185         /**
186          * Find all the locations that can harbour tld files that may contain
187          * a listener which the web container is supposed to instantiate and
188          * call.
189          * 
190          * @return
191          * @throws IOException
192          */
193         private Set<Resource> findTldResources () throws IOException {
194             
195             Set<Resource> tlds = new HashSet<Resource>();
196             
197             // Find tld's from web.xml
198             // When web.xml was processed, it should have created aliases for all TLDs.  So search resources aliases
199             // for aliases ending in tld
200             if (_context.getResourceAliases()!=null && 
201                     _context.getBaseResource()!=null && 
202                     _context.getBaseResource().exists())
203             {
204                 Iterator<String> iter=_context.getResourceAliases().values().iterator();
205                 while(iter.hasNext())
206                 {
207                     String location = iter.next();
208                     if (location!=null && location.toLowerCase().endsWith(".tld"))
209                     {
210                         if (!location.startsWith("/"))
211                             location="/WEB-INF/"+location;
212                         Resource l=_context.getBaseResource().addPath(location);
213                         tlds.add(l);
214                     }
215                 }
216             }
217             
218             // Look for any tlds in WEB-INF directly.
219             Resource web_inf = _context.getWebInf();
220             if (web_inf!=null)
221             {
222                 String[] contents = web_inf.list();
223                 for (int i=0;contents!=null && i<contents.length;i++)
224                 {
225                     if (contents[i]!=null && contents[i].toLowerCase().endsWith(".tld"))
226                     {
227                         Resource l=web_inf.addPath(contents[i]);
228                         tlds.add(l);
229                     }
230                 }
231             }
232             
233             //Look for tlds in common location of WEB-INF/tlds
234             if (web_inf != null) {
235                 Resource web_inf_tlds = _context.getWebInf().addPath("/tlds/");
236                 if (web_inf_tlds.exists() && web_inf_tlds.isDirectory()) {
237                     String[] contents = web_inf_tlds.list();
238                     for (int i=0;contents!=null && i<contents.length;i++)
239                     {
240                         if (contents[i]!=null && contents[i].toLowerCase().endsWith(".tld"))
241                         {
242                             Resource l=web_inf_tlds.addPath(contents[i]);
243                             tlds.add(l);
244                         }
245                     }
246                 } 
247             }
248 
249             // Add in tlds found in META-INF of jars. The jars that will be scanned are controlled by
250             // the patterns defined in the context attributes: org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern,
251             // and org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern
252             @SuppressWarnings("unchecked")
253             Collection<Resource> tld_resources=(Collection<Resource>)_context.getAttribute(TLD_RESOURCES);
254             if (tld_resources!=null)
255                 tlds.addAll(tld_resources);
256             
257             return tlds;
258         }
259         
260         
261         /**
262          * Parse xml into in-memory tree
263          * @param tlds
264          * @return
265          */
266         private List<TldDescriptor> parseTlds (Set<Resource> tlds) {         
267             List<TldDescriptor> descriptors = new ArrayList<TldDescriptor>();
268             
269             Resource tld = null;
270             Iterator<Resource> iter = tlds.iterator();
271             while (iter.hasNext())
272             {
273                 try
274                 {
275                     tld = iter.next();
276                     if (LOG.isDebugEnabled()) LOG.debug("TLD="+tld);
277                    
278                     TldDescriptor d = new TldDescriptor(tld);
279                     d.parse();
280                     descriptors.add(d);
281                 }
282                 catch(Exception e)
283                 {
284                     LOG.warn("Unable to parse TLD: " + tld,e);
285                 }
286             }
287             return descriptors;
288         }
289         
290         
291         /**
292          * Create listeners from the parsed tld trees
293          * @param descriptors
294          * @throws Exception
295          */
296         private void processTlds (List<TldDescriptor> descriptors) throws Exception {
297 
298             TldProcessor processor = new TldProcessor();
299             for (TldDescriptor d:descriptors)
300                 processor.process(_context, d); 
301             
302             _tldListeners = new ArrayList<EventListener>(processor.getListeners());
303         }
304     }
305     
306     
307     
308     
309     /**
310      * TldDescriptor
311      *
312      *
313      */
314     public static class TldDescriptor extends Descriptor
315     {
316         protected static XmlParser __parserSingleton;
317 
318         public TldDescriptor(Resource xml)
319         {
320             super(xml);
321         }
322 
323         @Override
324         public void ensureParser() throws ClassNotFoundException
325         {
326            if (__parserSingleton == null)
327                __parserSingleton = newParser();
328             _parser = __parserSingleton;
329         }
330 
331         @Override
332         public XmlParser newParser() throws ClassNotFoundException
333         {
334             // Create a TLD parser
335             XmlParser parser = new XmlParser(false);
336             
337             URL taglib11=null;
338             URL taglib12=null;
339             URL taglib20=null;
340             URL taglib21=null;
341 
342             try
343             {
344                 Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
345                 taglib11=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd");
346                 taglib12=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd");
347                 taglib20=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd");
348                 taglib21=jsp_page.getResource("javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd");
349             }
350             catch(Exception e)
351             {
352                 LOG.ignore(e);
353             }
354             finally
355             {
356                 if(taglib11==null)
357                     taglib11=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_1.dtd",true);
358                 if(taglib12==null)
359                     taglib12=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_1_2.dtd",true);
360                 if(taglib20==null)
361                     taglib20=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_0.xsd",true);
362                 if(taglib21==null)
363                     taglib21=Loader.getResource(Servlet.class,"javax/servlet/jsp/resources/web-jsptaglibrary_2_1.xsd",true);
364             }
365             
366 
367             if(taglib11!=null)
368             {
369                 redirect(parser, "web-jsptaglib_1_1.dtd",taglib11);  
370                 redirect(parser, "web-jsptaglibrary_1_1.dtd",taglib11);
371             }
372             if(taglib12!=null)
373             {
374                 redirect(parser, "web-jsptaglib_1_2.dtd",taglib12);
375                 redirect(parser, "web-jsptaglibrary_1_2.dtd",taglib12);
376             }
377             if(taglib20!=null)
378             {
379                 redirect(parser, "web-jsptaglib_2_0.xsd",taglib20);
380                 redirect(parser, "web-jsptaglibrary_2_0.xsd",taglib20);
381             }
382             if(taglib21!=null)
383             {
384                 redirect(parser, "web-jsptaglib_2_1.xsd",taglib21);
385                 redirect(parser, "web-jsptaglibrary_2_1.xsd",taglib21);
386             }
387             
388             parser.setXpath("/taglib/listener/listener-class");
389             return parser;
390         }
391         
392         public void parse ()
393         throws Exception
394         {
395             ensureParser();
396             try
397             {
398                 //xerces on apple appears to sometimes close the zip file instead
399                 //of the inputstream, so try opening the input stream, but if
400                 //that doesn't work, fallback to opening a new url
401                 _root = _parser.parse(_xml.getInputStream());
402             }
403             catch (Exception e)
404             {
405                 _root = _parser.parse(_xml.getURL().toString());
406             }
407 
408             if (_root==null)
409             {
410                 LOG.warn("No TLD root in {}",_xml);
411             }
412         }
413     }
414     
415     
416     /**
417      * TldProcessor
418      *
419      * Process TldDescriptors representing tag libs to find listeners.
420      */
421     public class TldProcessor extends IterativeDescriptorProcessor
422     {
423         public static final String TAGLIB_PROCESSOR = "org.eclipse.jetty.tagLibProcessor";
424         XmlParser _parser;
425         List<XmlParser.Node> _roots = new ArrayList<XmlParser.Node>();
426         List<EventListener> _listeners;
427         
428         
429         public TldProcessor ()
430         throws Exception
431         {  
432             _listeners = new ArrayList<EventListener>();
433             registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
434         }
435       
436 
437         public void visitListener (WebAppContext context, Descriptor descriptor, XmlParser.Node node)
438         {     
439             String className=node.getString("listener-class",false,true);
440             if (LOG.isDebugEnabled()) 
441                 LOG.debug("listener="+className);
442 
443             try
444             {
445                 Class<?> listenerClass = context.loadClass(className);
446                 EventListener l = (EventListener)listenerClass.newInstance();
447                 _listeners.add(l);
448             }
449             catch(Exception e)
450             {
451                 LOG.warn("Could not instantiate listener "+className+": "+e);
452                 LOG.debug(e);
453             }
454             catch(Error e)
455             {
456                 LOG.warn("Could not instantiate listener "+className+": "+e);
457                 LOG.debug(e);
458             }
459 
460         }
461 
462         @Override
463         public void end(WebAppContext context, Descriptor descriptor)
464         {
465         }
466 
467         @Override
468         public void start(WebAppContext context, Descriptor descriptor)
469         {  
470         }
471         
472         public List<EventListener> getListeners() {
473             return _listeners;
474         }
475     }
476 
477 
478     @Override
479     public void preConfigure(WebAppContext context) throws Exception
480     {
481         try
482         {
483             Class<?> jsp_page = Loader.loadClass(WebXmlConfiguration.class,"javax.servlet.jsp.JspPage");
484         }
485         catch (Exception e)
486         {
487             //no jsp available, don't parse TLDs
488             return;
489         }
490 
491         TagLibListener tagLibListener = new TagLibListener(context);
492         context.addEventListener(tagLibListener);
493     }
494     
495 
496     @Override
497     public void configure (WebAppContext context) throws Exception
498     {         
499     }
500 
501     @Override
502     public void postConfigure(WebAppContext context) throws Exception
503     {     
504     }
505 
506 
507     @Override
508     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
509     {
510     }
511 
512 
513     @Override
514     public void deconfigure(WebAppContext context) throws Exception
515     {
516     } 
517 }