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