1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.jasper.compiler;
19
20 import com.opensymphony.xwork2.util.logging.Logger;
21 import com.opensymphony.xwork2.util.logging.LoggerFactory;
22 import org.apache.commons.io.FileUtils;
23 import org.apache.struts2.jasper.Constants;
24 import org.apache.struts2.jasper.JasperException;
25 import org.apache.struts2.jasper.xmlparser.ParserUtils;
26 import org.apache.struts2.jasper.xmlparser.TreeNode;
27 import org.xml.sax.InputSource;
28
29 import javax.servlet.ServletContext;
30 import java.io.File;
31 import java.io.InputStream;
32 import java.net.*;
33 import java.util.*;
34 import java.util.jar.JarEntry;
35 import java.util.jar.JarFile;
36
37 /***
38 * A container for all tag libraries that are defined "globally"
39 * for the web application.
40 * <p/>
41 * Tag Libraries can be defined globally in one of two ways:
42 * 1. Via <taglib> elements in web.xml:
43 * the uri and location of the tag-library are specified in
44 * the <taglib> element.
45 * 2. Via packaged jar files that contain .tld files
46 * within the META-INF directory, or some subdirectory
47 * of it. The taglib is 'global' if it has the <uri>
48 * element defined.
49 * <p/>
50 * A mapping between the taglib URI and its associated TaglibraryInfoImpl
51 * is maintained in this container.
52 * Actually, that's what we'd like to do. However, because of the
53 * way the classes TagLibraryInfo and TagInfo have been defined,
54 * it is not currently possible to share an instance of TagLibraryInfo
55 * across page invocations. A bug has been submitted to the spec lead.
56 * In the mean time, all we do is save the 'location' where the
57 * TLD associated with a taglib URI can be found.
58 * <p/>
59 * When a JSP page has a taglib directive, the mappings in this container
60 * are first searched (see method getLocation()).
61 * If a mapping is found, then the location of the TLD is returned.
62 * If no mapping is found, then the uri specified
63 * in the taglib directive is to be interpreted as the location for
64 * the TLD of this tag library.
65 *
66 * @author Pierre Delisle
67 * @author Jan Luehe
68 */
69
70 public class TldLocationsCache {
71
72
73 private Logger log = LoggerFactory.getLogger(TldLocationsCache.class);
74
75 /***
76 * The types of URI one may specify for a tag library
77 */
78 public static final int ABS_URI = 0;
79 public static final int ROOT_REL_URI = 1;
80 public static final int NOROOT_REL_URI = 2;
81
82 private static final String WEB_XML = "/WEB-INF/web.xml";
83 private static final String FILE_PROTOCOL = "file:";
84 private static final String JAR_FILE_SUFFIX = ".jar";
85
86
87 private static HashSet noTldJars;
88
89 /***
90 * The mapping of the 'global' tag library URI to the location (resource
91 * path) of the TLD associated with that tag library. The location is
92 * returned as a String array:
93 * [0] The location
94 * [1] If the location is a jar file, this is the location of the tld.
95 */
96 private Hashtable mappings;
97
98 private boolean initialized;
99 private ServletContext ctxt;
100 private boolean redeployMode;
101
102 //**********************************************************************
103 // Constructor and Initilizations
104
105 /*
106 * Initializes the set of JARs that are known not to contain any TLDs
107 */
108
109 static {
110 noTldJars = new HashSet();
111 noTldJars.add("ant.jar");
112 noTldJars.add("catalina.jar");
113 noTldJars.add("catalina-ant.jar");
114 noTldJars.add("catalina-cluster.jar");
115 noTldJars.add("catalina-optional.jar");
116 noTldJars.add("catalina-i18n-fr.jar");
117 noTldJars.add("catalina-i18n-ja.jar");
118 noTldJars.add("catalina-i18n-es.jar");
119 noTldJars.add("commons-dbcp.jar");
120 noTldJars.add("commons-modeler.jar");
121 noTldJars.add("commons-logging-api.jar");
122 noTldJars.add("commons-beanutils.jar");
123 noTldJars.add("commons-fileupload-1.0.jar");
124 noTldJars.add("commons-pool.jar");
125 noTldJars.add("commons-digester.jar");
126 noTldJars.add("commons-logging.jar");
127 noTldJars.add("commons-collections.jar");
128 noTldJars.add("commons-el.jar");
129 noTldJars.add("jakarta-regexp-1.2.jar");
130 noTldJars.add("jasper-compiler.jar");
131 noTldJars.add("jasper-runtime.jar");
132 noTldJars.add("jmx.jar");
133 noTldJars.add("jmx-tools.jar");
134 noTldJars.add("jsp-api.jar");
135 noTldJars.add("jkshm.jar");
136 noTldJars.add("jkconfig.jar");
137 noTldJars.add("naming-common.jar");
138 noTldJars.add("naming-resources.jar");
139 noTldJars.add("naming-factory.jar");
140 noTldJars.add("naming-java.jar");
141 noTldJars.add("servlet-api.jar");
142 noTldJars.add("servlets-default.jar");
143 noTldJars.add("servlets-invoker.jar");
144 noTldJars.add("servlets-common.jar");
145 noTldJars.add("servlets-webdav.jar");
146 noTldJars.add("tomcat-util.jar");
147 noTldJars.add("tomcat-http11.jar");
148 noTldJars.add("tomcat-jni.jar");
149 noTldJars.add("tomcat-jk.jar");
150 noTldJars.add("tomcat-jk2.jar");
151 noTldJars.add("tomcat-coyote.jar");
152 noTldJars.add("xercesImpl.jar");
153 noTldJars.add("xmlParserAPIs.jar");
154 noTldJars.add("xml-apis.jar");
155
156 noTldJars.add("sunjce_provider.jar");
157 noTldJars.add("ldapsec.jar");
158 noTldJars.add("localedata.jar");
159 noTldJars.add("dnsns.jar");
160 }
161
162 public TldLocationsCache(ServletContext ctxt) {
163 this(ctxt, true);
164 }
165
166 /***
167 * Constructor.
168 *
169 * @param ctxt the servlet context of the web application in which Jasper
170 * is running
171 * @param redeployMode if true, then the compiler will allow redeploying
172 * a tag library from the same jar, at the expense of slowing down the
173 * server a bit. Note that this may only work on JDK 1.3.1_01a and later,
174 * because of JDK bug 4211817 fixed in this release.
175 * If redeployMode is false, a faster but less capable mode will be used.
176 */
177 public TldLocationsCache(ServletContext ctxt, boolean redeployMode) {
178 this.ctxt = ctxt;
179 this.redeployMode = redeployMode;
180 mappings = new Hashtable();
181 initialized = false;
182 }
183
184 /***
185 * Sets the list of JARs that are known not to contain any TLDs.
186 *
187 * @param jarNames List of comma-separated names of JAR files that are
188 * known not to contain any TLDs
189 */
190 public static void setNoTldJars(String jarNames) {
191 if (jarNames != null) {
192 noTldJars.clear();
193 StringTokenizer tokenizer = new StringTokenizer(jarNames, ",");
194 while (tokenizer.hasMoreElements()) {
195 noTldJars.add(tokenizer.nextToken());
196 }
197 }
198 }
199
200 /***
201 * Gets the 'location' of the TLD associated with the given taglib 'uri'.
202 * <p/>
203 * Returns null if the uri is not associated with any tag library 'exposed'
204 * in the web application. A tag library is 'exposed' either explicitly in
205 * web.xml or implicitly via the uri tag in the TLD of a taglib deployed
206 * in a jar file (WEB-INF/lib).
207 *
208 * @param uri The taglib uri
209 * @return An array of two Strings: The first element denotes the real
210 * path to the TLD. If the path to the TLD points to a jar file, then the
211 * second element denotes the name of the TLD entry in the jar file.
212 * Returns null if the uri is not associated with any tag library 'exposed'
213 * in the web application.
214 */
215 public String[] getLocation(String uri) throws JasperException {
216 if (!initialized) {
217 init();
218 }
219 return (String[]) mappings.get(uri);
220 }
221
222 /***
223 * Returns a list of absolute paths of the locations in the cache
224 */
225 public Set<String> getAbsolutePathsOfLocations() {
226 Set<String> paths = new HashSet<String>(mappings.size());
227 for (Object value : mappings.values()) {
228 String[] location = (String[]) value;
229
230 try {
231 File file = FileUtils.toFile(new URL(location[0]));
232 paths.add(file.getAbsolutePath());
233 } catch (Exception e) {
234
235 }
236 }
237 return paths;
238 }
239
240 /***
241 * Returns the type of a URI:
242 * ABS_URI
243 * ROOT_REL_URI
244 * NOROOT_REL_URI
245 */
246 public static int uriType(String uri) {
247 if (uri.indexOf(':') != -1) {
248 return ABS_URI;
249 } else if (uri.startsWith("/")) {
250 return ROOT_REL_URI;
251 } else {
252 return NOROOT_REL_URI;
253 }
254 }
255
256 private void init() throws JasperException {
257 if (initialized) return;
258 try {
259 processWebDotXml();
260 scanJars();
261 processTldsInFileSystem("/WEB-INF/");
262 initialized = true;
263 } catch (Exception ex) {
264 throw new JasperException(Localizer.getMessage(
265 "jsp.error.internal.tldinit", ex.getMessage()));
266 }
267 }
268
269
270
271
272 private void processWebDotXml() throws Exception {
273
274 InputStream is = null;
275
276 try {
277
278 String altDDName = (String) ctxt.getAttribute(
279 Constants.ALT_DD_ATTR);
280 URL uri = null;
281 if (altDDName != null) {
282 try {
283 uri = new URL(FILE_PROTOCOL + altDDName.replace('//', '/'));
284 } catch (MalformedURLException e) {
285 if (log.isWarnEnabled()) {
286 log.warn(Localizer.getMessage(
287 "jsp.error.internal.filenotfound",
288 altDDName));
289 }
290 }
291 } else {
292 uri = ctxt.getResource(WEB_XML);
293 }
294
295 if (uri == null) {
296 return;
297 }
298 is = uri.openStream();
299 InputSource ip = new InputSource(is);
300 ip.setSystemId(uri.toExternalForm());
301
302
303 TreeNode webtld = null;
304
305 if (altDDName != null) {
306 webtld = new ParserUtils().parseXMLDocument(altDDName, ip);
307 } else {
308 webtld = new ParserUtils().parseXMLDocument(WEB_XML, ip);
309 }
310
311
312 TreeNode jspConfig = webtld.findChild("jsp-config");
313 if (jspConfig != null) {
314 webtld = jspConfig;
315 }
316 Iterator taglibs = webtld.findChildren("taglib");
317 while (taglibs.hasNext()) {
318
319
320 TreeNode taglib = (TreeNode) taglibs.next();
321 String tagUri = null;
322 String tagLoc = null;
323 TreeNode child = taglib.findChild("taglib-uri");
324 if (child != null)
325 tagUri = child.getBody();
326 child = taglib.findChild("taglib-location");
327 if (child != null)
328 tagLoc = child.getBody();
329
330
331 if (tagLoc == null)
332 continue;
333 if (uriType(tagLoc) == NOROOT_REL_URI)
334 tagLoc = "/WEB-INF/" + tagLoc;
335 String tagLoc2 = null;
336 if (tagLoc.endsWith(JAR_FILE_SUFFIX)) {
337 tagLoc = ctxt.getResource(tagLoc).toString();
338 tagLoc2 = "META-INF/taglib.tld";
339 }
340 mappings.put(tagUri, new String[]{tagLoc, tagLoc2});
341 }
342 } finally {
343 if (is != null) {
344 try {
345 is.close();
346 } catch (Throwable t) {
347 }
348 }
349 }
350 }
351
352 /***
353 * Scans the given JarURLConnection for TLD files located in META-INF
354 * (or a subdirectory of it), adding an implicit map entry to the taglib
355 * map for any TLD that has a <uri> element.
356 *
357 * @param conn The JarURLConnection to the JAR file to scan
358 * @param ignore true if any exceptions raised when processing the given
359 * JAR should be ignored, false otherwise
360 */
361 private void scanJar(JarURLConnection conn, boolean ignore)
362 throws JasperException {
363
364 JarFile jarFile = null;
365 String resourcePath = conn.getJarFileURL().toString();
366 try {
367 if (redeployMode) {
368 conn.setUseCaches(false);
369 }
370 jarFile = conn.getJarFile();
371 Enumeration entries = jarFile.entries();
372 while (entries.hasMoreElements()) {
373 JarEntry entry = (JarEntry) entries.nextElement();
374 String name = entry.getName();
375 if (!name.startsWith("META-INF/")) continue;
376 if (!name.endsWith(".tld")) continue;
377 InputStream stream = jarFile.getInputStream(entry);
378 try {
379 String uri = getUriFromTld(resourcePath, stream);
380
381
382 if (uri != null && mappings.get(uri) == null) {
383 mappings.put(uri, new String[]{resourcePath, name});
384 }
385 } finally {
386 if (stream != null) {
387 try {
388 stream.close();
389 } catch (Throwable t) {
390
391 }
392 }
393 }
394 }
395 } catch (Exception ex) {
396 if (!redeployMode) {
397
398 if (jarFile != null) {
399 try {
400 jarFile.close();
401 } catch (Throwable t) {
402
403 }
404 }
405 }
406 if (!ignore) {
407 throw new JasperException(ex);
408 }
409 } finally {
410 if (redeployMode) {
411
412 if (jarFile != null) {
413 try {
414 jarFile.close();
415 } catch (Throwable t) {
416
417 }
418 }
419 }
420 }
421 }
422
423
424
425
426
427
428 private void processTldsInFileSystem(String startPath)
429 throws Exception {
430
431 Set dirList = ctxt.getResourcePaths(startPath);
432 if (dirList != null) {
433 Iterator it = dirList.iterator();
434 while (it.hasNext()) {
435 String path = (String) it.next();
436 if (path.endsWith("/")) {
437 processTldsInFileSystem(path);
438 }
439 if (!path.endsWith(".tld")) {
440 continue;
441 }
442 InputStream stream = ctxt.getResourceAsStream(path);
443 String uri = null;
444 try {
445 uri = getUriFromTld(path, stream);
446 } finally {
447 if (stream != null) {
448 try {
449 stream.close();
450 } catch (Throwable t) {
451
452 }
453 }
454 }
455
456
457 if (uri != null && mappings.get(uri) == null) {
458 mappings.put(uri, new String[]{path, null});
459 }
460 }
461 }
462 }
463
464
465
466
467
468 private String getUriFromTld(String resourcePath, InputStream in)
469 throws JasperException {
470
471 TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in);
472 TreeNode uri = tld.findChild("uri");
473 if (uri != null) {
474 String body = uri.getBody();
475 if (body != null)
476 return body;
477 }
478
479 return null;
480 }
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501 private void scanJars() throws Exception {
502
503 ClassLoader webappLoader
504 = Thread.currentThread().getContextClassLoader();
505 ClassLoader loader = webappLoader;
506
507 while (loader != null) {
508 if (loader instanceof URLClassLoader) {
509 URL[] urls = ((URLClassLoader) loader).getURLs();
510 for (int i = 0; i < urls.length; i++) {
511 URLConnection conn = urls[i].openConnection();
512 if (conn instanceof JarURLConnection) {
513 if (needScanJar(loader, webappLoader,
514 ((JarURLConnection) conn).getJarFile().getName())) {
515 scanJar((JarURLConnection) conn, true);
516 }
517 } else {
518 String urlStr = urls[i].toString();
519 if (urlStr.startsWith(FILE_PROTOCOL)
520 && urlStr.endsWith(JAR_FILE_SUFFIX)
521 && needScanJar(loader, webappLoader, urlStr)) {
522 URL jarURL = new URL("jar:" + urlStr + "!/");
523 scanJar((JarURLConnection) jarURL.openConnection(),
524 true);
525 }
526 }
527 }
528 }
529
530 loader = loader.getParent();
531 }
532 }
533
534
535
536
537
538
539
540
541
542
543
544
545 private boolean needScanJar(ClassLoader loader, ClassLoader webappLoader,
546 String jarPath) {
547 if (loader == webappLoader) {
548
549
550 return true;
551 } else {
552 String jarName = jarPath;
553 int slash = jarPath.lastIndexOf('/');
554 if (slash >= 0) {
555 jarName = jarPath.substring(slash + 1);
556 }
557 return (!noTldJars.contains(jarName));
558 }
559 }
560 }