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