• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 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 
TagLibListener(WebAppContext context)90         public TagLibListener (WebAppContext context) {
91             _context = context;
92         }
93 
contextDestroyed(ServletContextEvent sce)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 
contextInitialized(ServletContextEvent sce)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 
extractJarResource(Resource r)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          */
findTldResources()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          */
parseTlds(Set<Resource> tlds)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          */
processTlds(List<TldDescriptor> descriptors)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 
TldDescriptor(Resource xml)331         public TldDescriptor(Resource xml)
332         {
333             super(xml);
334         }
335 
336         @Override
ensureParser()337         public void ensureParser() throws ClassNotFoundException
338         {
339            if (__parserSingleton == null)
340                __parserSingleton = newParser();
341             _parser = __parserSingleton;
342         }
343 
344         @Override
newParser()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 
parse()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 
TldProcessor()442         public TldProcessor ()
443         throws Exception
444         {
445             _listeners = new ArrayList<EventListener>();
446             registerVisitor("listener", this.getClass().getDeclaredMethod("visitListener", __signature));
447         }
448 
449 
visitListener(WebAppContext context, Descriptor descriptor, XmlParser.Node node)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
end(WebAppContext context, Descriptor descriptor)476         public void end(WebAppContext context, Descriptor descriptor)
477         {
478         }
479 
480         @Override
start(WebAppContext context, Descriptor descriptor)481         public void start(WebAppContext context, Descriptor descriptor)
482         {
483         }
484 
getListeners()485         public List<EventListener> getListeners() {
486             return _listeners;
487         }
488     }
489 
490 
491     @Override
preConfigure(WebAppContext context)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
configure(WebAppContext context)510     public void configure (WebAppContext context) throws Exception
511     {
512     }
513 
514     @Override
postConfigure(WebAppContext context)515     public void postConfigure(WebAppContext context) throws Exception
516     {
517     }
518 
519 
520     @Override
cloneConfigure(WebAppContext template, WebAppContext context)521     public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
522     {
523     }
524 
525 
526     @Override
deconfigure(WebAppContext context)527     public void deconfigure(WebAppContext context) throws Exception
528     {
529     }
530 }
531