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
18 package org.apache.jasper.compiler;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FilePermission;
23 import java.net.URL;
24 import java.net.URLClassLoader;
25 import java.security.CodeSource;
26 import java.security.PermissionCollection;
27 import java.security.Policy;
28 import java.security.cert.Certificate;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.concurrent.ConcurrentHashMap;
32
33 import javax.servlet.ServletContext;
34 import javax.servlet.jsp.JspFactory;
35
36 import org.apache.jasper.Constants;
37 import org.apache.jasper.JspCompilationContext;
38 import org.apache.jasper.Options;
39 import org.apache.jasper.runtime.JspFactoryImpl;
40 import org.apache.jasper.security.SecurityClassLoad;
41 import org.apache.jasper.servlet.JspServletWrapper;
42 import org.apache.juli.logging.Log;
43 import org.apache.juli.logging.LogFactory;
44
45 /**
46 * Class for tracking JSP compile time file dependencies when the
47 * &060;%@include file="..."%&062; directive is used.
48 *
49 * A background thread periodically checks the files a JSP page
50 * is dependent upon. If a dpendent file changes the JSP page
51 * which included it is recompiled.
52 *
53 * Only used if a web application context is a directory.
54 *
55 * @author Glenn L. Nielsen
56 * @version $Revision: 505593 $
57 */
58 public final class JspRuntimeContext {
59
60 // Logger
61 private Log log = LogFactory.getLog(JspRuntimeContext.class);
62
63 /*
64 * Counts how many times the webapp's JSPs have been reloaded.
65 */
66 private int jspReloadCount;
67
68 /**
69 * Preload classes required at runtime by a JSP servlet so that
70 * we don't get a defineClassInPackage security exception.
71 */
72 static {
73 JspFactoryImpl factory = new JspFactoryImpl();
74 SecurityClassLoad.securityClassLoad(factory.getClass().getClassLoader());
75 if( System.getSecurityManager() != null ) {
76 String basePackage = "org.apache.jasper.";
77 try {
78 factory.getClass().getClassLoader().loadClass( basePackage +
79 "runtime.JspFactoryImpl$PrivilegedGetPageContext");
80 factory.getClass().getClassLoader().loadClass( basePackage +
81 "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
82 factory.getClass().getClassLoader().loadClass( basePackage +
83 "runtime.JspRuntimeLibrary");
84 factory.getClass().getClassLoader().loadClass( basePackage +
85 "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
86 factory.getClass().getClassLoader().loadClass( basePackage +
87 "runtime.ServletResponseWrapperInclude");
88 factory.getClass().getClassLoader().loadClass( basePackage +
89 "servlet.JspServletWrapper");
90 } catch (ClassNotFoundException ex) {
91 throw new IllegalStateException(ex);
92 }
93 }
94
95 JspFactory.setDefaultFactory(factory);
96 }
97
98 // ----------------------------------------------------------- Constructors
99
100 /**
101 * Create a JspRuntimeContext for a web application context.
102 *
103 * Loads in any previously generated dependencies from file.
104 *
105 * @param context ServletContext for web application
106 */
107 public JspRuntimeContext(ServletContext context, Options options) {
108
109 this.context = context;
110 this.options = options;
111
112 // Get the parent class loader
113 parentClassLoader =
114 (URLClassLoader) Thread.currentThread().getContextClassLoader();
115 if (parentClassLoader == null) {
116 parentClassLoader =
117 (URLClassLoader)this.getClass().getClassLoader();
118 }
119
120 if (log.isDebugEnabled()) {
121 if (parentClassLoader != null) {
122 log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
123 parentClassLoader.toString()));
124 } else {
125 log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
126 "<none>"));
127 }
128 }
129
130 initClassPath();
131
132 if (context instanceof org.apache.jasper.servlet.JspCServletContext) {
133 return;
134 }
135
136 if (Constants.IS_SECURITY_ENABLED) {
137 initSecurity();
138 }
139
140 // If this web application context is running from a
141 // directory, start the background compilation thread
142 String appBase = context.getRealPath("/");
143 if (!options.getDevelopment()
144 && appBase != null
145 && options.getCheckInterval() > 0) {
146 lastCheck = System.currentTimeMillis();
147 }
148 }
149
150 // ----------------------------------------------------- Instance Variables
151
152 /**
153 * This web applications ServletContext
154 */
155 private ServletContext context;
156 private Options options;
157 private URLClassLoader parentClassLoader;
158 private PermissionCollection permissionCollection;
159 private CodeSource codeSource;
160 private String classpath;
161 private long lastCheck = -1L;
162
163 /**
164 * Maps JSP pages to their JspServletWrapper's
165 */
166 private Map<String, JspServletWrapper> jsps = new ConcurrentHashMap<String, JspServletWrapper>();
167
168
169 // ------------------------------------------------------ Public Methods
170
171 /**
172 * Add a new JspServletWrapper.
173 *
174 * @param jspUri JSP URI
175 * @param jsw Servlet wrapper for JSP
176 */
177 public void addWrapper(String jspUri, JspServletWrapper jsw) {
178 jsps.put(jspUri, jsw);
179 }
180
181 /**
182 * Get an already existing JspServletWrapper.
183 *
184 * @param jspUri JSP URI
185 * @return JspServletWrapper for JSP
186 */
187 public JspServletWrapper getWrapper(String jspUri) {
188 return jsps.get(jspUri);
189 }
190
191 /**
192 * Remove a JspServletWrapper.
193 *
194 * @param jspUri JSP URI of JspServletWrapper to remove
195 */
196 public void removeWrapper(String jspUri) {
197 jsps.remove(jspUri);
198 }
199
200 /**
201 * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
202 * the number of JSPs that have been loaded into the webapp.
203 *
204 * @return The number of JSPs that have been loaded into the webapp
205 */
206 public int getJspCount() {
207 return jsps.size();
208 }
209
210 /**
211 * Get the SecurityManager Policy CodeSource for this web
212 * applicaiton context.
213 *
214 * @return CodeSource for JSP
215 */
216 public CodeSource getCodeSource() {
217 return codeSource;
218 }
219
220 /**
221 * Get the parent URLClassLoader.
222 *
223 * @return URLClassLoader parent
224 */
225 public URLClassLoader getParentClassLoader() {
226 return parentClassLoader;
227 }
228
229 /**
230 * Get the SecurityManager PermissionCollection for this
231 * web application context.
232 *
233 * @return PermissionCollection permissions
234 */
235 public PermissionCollection getPermissionCollection() {
236 return permissionCollection;
237 }
238
239 /**
240 * Process a "destory" event for this web application context.
241 */
242 public void destroy() {
243 Iterator servlets = jsps.values().iterator();
244 while (servlets.hasNext()) {
245 ((JspServletWrapper) servlets.next()).destroy();
246 }
247 }
248
249 /**
250 * Increments the JSP reload counter.
251 */
252 public synchronized void incrementJspReloadCount() {
253 jspReloadCount++;
254 }
255
256 /**
257 * Resets the JSP reload counter.
258 *
259 * @param count Value to which to reset the JSP reload counter
260 */
261 public synchronized void setJspReloadCount(int count) {
262 this.jspReloadCount = count;
263 }
264
265 /**
266 * Gets the current value of the JSP reload counter.
267 *
268 * @return The current value of the JSP reload counter
269 */
270 public int getJspReloadCount() {
271 return jspReloadCount;
272 }
273
274
275 /**
276 * Method used by background thread to check the JSP dependencies
277 * registered with this class for JSP's.
278 */
279 public void checkCompile() {
280
281 if (lastCheck < 0) {
282 // Checking was disabled
283 return;
284 }
285 long now = System.currentTimeMillis();
286 if (now > (lastCheck + (options.getCheckInterval() * 1000L))) {
287 lastCheck = now;
288 } else {
289 return;
290 }
291
292 Object [] wrappers = jsps.values().toArray();
293 for (int i = 0; i < wrappers.length; i++ ) {
294 JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
295 JspCompilationContext ctxt = jsw.getJspEngineContext();
296 // JspServletWrapper also synchronizes on this when
297 // it detects it has to do a reload
298 synchronized(jsw) {
299 try {
300 ctxt.compile();
301 } catch (FileNotFoundException ex) {
302 ctxt.incrementRemoved();
303 } catch (Throwable t) {
304 jsw.getServletContext().log("Background compile failed",
305 t);
306 }
307 }
308 }
309
310 }
311
312 /**
313 * The classpath that is passed off to the Java compiler.
314 */
315 public String getClassPath() {
316 return classpath;
317 }
318
319
320 // -------------------------------------------------------- Private Methods
321
322
323 /**
324 * Method used to initialize classpath for compiles.
325 */
326 private void initClassPath() {
327
328 URL [] urls = parentClassLoader.getURLs();
329 StringBuffer cpath = new StringBuffer();
330 String sep = System.getProperty("path.separator");
331
332 for(int i = 0; i < urls.length; i++) {
333 // Tomcat 4 can use URL's other than file URL's,
334 // a protocol other than file: will generate a
335 // bad file system path, so only add file:
336 // protocol URL's to the classpath.
337
338 if( urls[i].getProtocol().equals("file") ) {
339 cpath.append((String)urls[i].getFile()+sep);
340 }
341 }
342
343 cpath.append(options.getScratchDir() + sep);
344
345 String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH);
346 if (cp == null || cp.equals("")) {
347 cp = options.getClassPath();
348 }
349
350 classpath = cpath.toString() + cp;
351
352 if(log.isDebugEnabled()) {
353 log.debug("Compilation classpath initialized: " + getClassPath());
354 }
355 }
356
357 /**
358 * Method used to initialize SecurityManager data.
359 */
360 private void initSecurity() {
361
362 // Setup the PermissionCollection for this web app context
363 // based on the permissions configured for the root of the
364 // web app context directory, then add a file read permission
365 // for that directory.
366 Policy policy = Policy.getPolicy();
367 if( policy != null ) {
368 try {
369 // Get the permissions for the web app context
370 String docBase = context.getRealPath("/");
371 if( docBase == null ) {
372 docBase = options.getScratchDir().toString();
373 }
374 String codeBase = docBase;
375 if (!codeBase.endsWith(File.separator)){
376 codeBase = codeBase + File.separator;
377 }
378 File contextDir = new File(codeBase);
379 URL url = contextDir.getCanonicalFile().toURL();
380 codeSource = new CodeSource(url,(Certificate[])null);
381 permissionCollection = policy.getPermissions(codeSource);
382
383 // Create a file read permission for web app context directory
384 if (!docBase.endsWith(File.separator)){
385 permissionCollection.add
386 (new FilePermission(docBase,"read"));
387 docBase = docBase + File.separator;
388 } else {
389 permissionCollection.add
390 (new FilePermission
391 (docBase.substring(0,docBase.length() - 1),"read"));
392 }
393 docBase = docBase + "-";
394 permissionCollection.add(new FilePermission(docBase,"read"));
395
396 // Create a file read permission for web app tempdir (work)
397 // directory
398 String workDir = options.getScratchDir().toString();
399 if (!workDir.endsWith(File.separator)){
400 permissionCollection.add
401 (new FilePermission(workDir,"read"));
402 workDir = workDir + File.separator;
403 }
404 workDir = workDir + "-";
405 permissionCollection.add(new FilePermission(workDir,"read"));
406
407 // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase
408 permissionCollection.add( new RuntimePermission(
409 "accessClassInPackage.org.apache.jasper.runtime") );
410
411 if (parentClassLoader instanceof URLClassLoader) {
412 URL [] urls = parentClassLoader.getURLs();
413 String jarUrl = null;
414 String jndiUrl = null;
415 for (int i=0; i<urls.length; i++) {
416 if (jndiUrl == null
417 && urls[i].toString().startsWith("jndi:") ) {
418 jndiUrl = urls[i].toString() + "-";
419 }
420 if (jarUrl == null
421 && urls[i].toString().startsWith("jar:jndi:")
422 ) {
423 jarUrl = urls[i].toString();
424 jarUrl = jarUrl.substring(0,jarUrl.length() - 2);
425 jarUrl = jarUrl.substring(0,
426 jarUrl.lastIndexOf('/')) + "/-";
427 }
428 }
429 if (jarUrl != null) {
430 permissionCollection.add(
431 new FilePermission(jarUrl,"read"));
432 permissionCollection.add(
433 new FilePermission(jarUrl.substring(4),"read"));
434 }
435 if (jndiUrl != null)
436 permissionCollection.add(
437 new FilePermission(jndiUrl,"read") );
438 }
439 } catch(Exception e) {
440 context.log("Security Init for context failed",e);
441 }
442 }
443 }
444
445
446 }