Jake Slack | 03928ae | 2014-05-13 18:41:56 -0700 | [diff] [blame] | 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 | |
| 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 | } |