View Javadoc

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