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