1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.jasper.compiler;
18
19 import java.io.IOException;
20 import java.net.URL;
21 import java.net.URLConnection;
22 import java.util.HashMap;
23 import java.util.Map;
24 import java.util.Map.Entry;
25
26 import javax.servlet.ServletContext;
27
28 import org.apache.jasper.Constants;
29 import org.apache.jasper.JasperException;
30 import org.apache.tomcat.Jar;
31 import org.apache.tomcat.util.descriptor.tld.TaglibXml;
32 import org.apache.tomcat.util.descriptor.tld.TldParser;
33 import org.apache.tomcat.util.descriptor.tld.TldResourcePath;
34 import org.xml.sax.SAXException;
35
36 /**
37  * This class caches parsed instances of TLD files to remove the need for the
38  * same TLD to be parsed for each JSP that references it. It does not protect
39  * against multiple threads processing the same, new TLD but it does ensure that
40  * each all threads will use the same TLD object after parsing.
41  */

42 public class TldCache {
43
44     public static final String SERVLET_CONTEXT_ATTRIBUTE_NAME =
45             TldCache.class.getName();
46
47     private final ServletContext servletContext;
48     private final Map<String,TldResourcePath> uriTldResourcePathMap = new HashMap<>();
49     private final Map<TldResourcePath,TaglibXmlCacheEntry> tldResourcePathTaglibXmlMap =
50             new HashMap<>();
51     private final TldParser tldParser;
52
53
54     public static TldCache getInstance(ServletContext servletContext) {
55         if (servletContext == null) {
56             throw new IllegalArgumentException(Localizer.getMessage(
57                     "org.apache.jasper.compiler.TldCache.servletContextNull"));
58         }
59         return (TldCache) servletContext.getAttribute(SERVLET_CONTEXT_ATTRIBUTE_NAME);
60     }
61
62
63     public TldCache(ServletContext servletContext,
64             Map<String, TldResourcePath> uriTldResourcePathMap,
65             Map<TldResourcePath, TaglibXml> tldResourcePathTaglibXmlMap) {
66         this.servletContext = servletContext;
67         this.uriTldResourcePathMap.putAll(uriTldResourcePathMap);
68         for (Entry<TldResourcePath, TaglibXml> entry : tldResourcePathTaglibXmlMap.entrySet()) {
69             TldResourcePath tldResourcePath = entry.getKey();
70             long lastModified[] = getLastModified(tldResourcePath);
71             TaglibXmlCacheEntry cacheEntry = new TaglibXmlCacheEntry(
72                     entry.getValue(), lastModified[0], lastModified[1]);
73             this.tldResourcePathTaglibXmlMap.put(tldResourcePath, cacheEntry);
74         }
75         boolean validate = Boolean.parseBoolean(
76                 servletContext.getInitParameter(Constants.XML_VALIDATION_TLD_INIT_PARAM));
77         String blockExternalString = servletContext.getInitParameter(
78                 Constants.XML_BLOCK_EXTERNAL_INIT_PARAM);
79         boolean blockExternal;
80         if (blockExternalString == null) {
81             blockExternal = true;
82         } else {
83             blockExternal = Boolean.parseBoolean(blockExternalString);
84         }
85         tldParser = new TldParser(true, validate, blockExternal);
86     }
87
88
89     public TldResourcePath getTldResourcePath(String uri) {
90         return uriTldResourcePathMap.get(uri);
91     }
92
93
94     public TaglibXml getTaglibXml(TldResourcePath tldResourcePath) throws JasperException {
95         TaglibXmlCacheEntry cacheEntry = tldResourcePathTaglibXmlMap.get(tldResourcePath);
96         if (cacheEntry == null) {
97             return null;
98         }
99         long lastModified[] = getLastModified(tldResourcePath);
100         if (lastModified[0] != cacheEntry.getWebAppPathLastModified() ||
101                 lastModified[1] != cacheEntry.getEntryLastModified()) {
102             synchronized (cacheEntry) {
103                 if (lastModified[0] != cacheEntry.getWebAppPathLastModified() ||
104                         lastModified[1] != cacheEntry.getEntryLastModified()) {
105                     // Re-parse TLD
106                     TaglibXml updatedTaglibXml;
107                     try {
108                         updatedTaglibXml = tldParser.parse(tldResourcePath);
109                     } catch (IOException | SAXException e) {
110                         throw new JasperException(e);
111                     }
112                     cacheEntry.setTaglibXml(updatedTaglibXml);
113                     cacheEntry.setWebAppPathLastModified(lastModified[0]);
114                     cacheEntry.setEntryLastModified(lastModified[1]);
115                 }
116             }
117         }
118         return cacheEntry.getTaglibXml();
119     }
120
121
122     private long[] getLastModified(TldResourcePath tldResourcePath) {
123         long[] result = new long[2];
124         result[0] = -1;
125         result[1] = -1;
126         try {
127             String webappPath = tldResourcePath.getWebappPath();
128             if (webappPath != null) {
129                 // webappPath will be null for JARs containing TLDs that are on
130                 // the class path but not part of the web application
131                 URL url = servletContext.getResource(tldResourcePath.getWebappPath());
132                 URLConnection conn = url.openConnection();
133                 result[0] = conn.getLastModified();
134                 if ("file".equals(url.getProtocol())) {
135                     // Reading the last modified time opens an input stream so we
136                     // need to make sure it is closed again otherwise the TLD file
137                     // will be locked until GC runs.
138                     conn.getInputStream().close();
139                 }
140             }
141             try (Jar jar = tldResourcePath.openJar()) {
142                 if (jar != null) {
143                     result[1] = jar.getLastModified(tldResourcePath.getEntryName());
144                 }
145             }
146         } catch (IOException e) {
147             // Ignore (shouldn't happen)
148         }
149         return result;
150     }
151
152     private static class TaglibXmlCacheEntry {
153         private volatile TaglibXml taglibXml;
154         private volatile long webAppPathLastModified;
155         private volatile long entryLastModified;
156
157         public TaglibXmlCacheEntry(TaglibXml taglibXml, long webAppPathLastModified,
158                 long entryLastModified) {
159             this.taglibXml = taglibXml;
160             this.webAppPathLastModified = webAppPathLastModified;
161             this.entryLastModified = entryLastModified;
162         }
163
164         public TaglibXml getTaglibXml() {
165             return taglibXml;
166         }
167
168         public void setTaglibXml(TaglibXml taglibXml) {
169             this.taglibXml = taglibXml;
170         }
171
172         public long getWebAppPathLastModified() {
173             return webAppPathLastModified;
174         }
175
176         public void setWebAppPathLastModified(long webAppPathLastModified) {
177             this.webAppPathLastModified = webAppPathLastModified;
178         }
179
180         public long getEntryLastModified() {
181             return entryLastModified;
182         }
183
184         public void setEntryLastModified(long entryLastModified) {
185             this.entryLastModified = entryLastModified;
186         }
187     }
188 }
189