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
19package org.eclipse.jetty.webapp;
20
21import java.io.IOException;
22import java.net.URI;
23import java.net.URL;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.EventListener;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Locale;
32import java.util.Map;
33import java.util.Set;
34
35import javax.servlet.Servlet;
36import javax.servlet.ServletContextEvent;
37import javax.servlet.ServletContextListener;
38
39import org.eclipse.jetty.util.Loader;
40import org.eclipse.jetty.util.log.Log;
41import org.eclipse.jetty.util.log.Logger;
42import org.eclipse.jetty.util.resource.Resource;
43import 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 */
64public 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}
531