001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.jasper.compiler;
018:
019: import java.io.File;
020: import java.io.FileNotFoundException;
021: import java.io.FilePermission;
022: import java.net.URL;
023: import java.net.URLClassLoader;
024: import java.security.CodeSource;
025: import java.security.PermissionCollection;
026: import java.security.Policy;
027: import java.util.Collections;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.Map;
031:
032: import javax.servlet.ServletContext;
033: import javax.servlet.jsp.JspFactory;
034:
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.apache.jasper.Constants;
038: import org.apache.jasper.JspCompilationContext;
039: import org.apache.jasper.Options;
040: import org.apache.jasper.util.SystemLogHandler;
041: import org.apache.jasper.runtime.JspFactoryImpl;
042: import org.apache.jasper.security.SecurityClassLoad;
043: import org.apache.jasper.servlet.JspServletWrapper;
044:
045: /**
046: * Class for tracking JSP compile time file dependencies when the
047: * &060;%@include file="..."%&062; directive is used.
048: *
049: * A background thread periodically checks the files a JSP page
050: * is dependent upon. If a dpendent file changes the JSP page
051: * which included it is recompiled.
052: *
053: * Only used if a web application context is a directory.
054: *
055: * @author Glenn L. Nielsen
056: * @version $Revision: 1.23 $
057: */
058: public final class JspRuntimeContext implements Runnable {
059:
060: // Logger
061: private static Log log = LogFactory.getLog(JspRuntimeContext.class);
062:
063: /*
064: * Counts how many times the webapp's JSPs have been reloaded.
065: */
066: private int jspReloadCount;
067:
068: /**
069: * Preload classes required at runtime by a JSP servlet so that
070: * we don't get a defineClassInPackage security exception.
071: */
072: static {
073: JspFactoryImpl factory = new JspFactoryImpl();
074: SecurityClassLoad.securityClassLoad(factory.getClass()
075: .getClassLoader());
076: JspFactory.setDefaultFactory(factory);
077: }
078:
079: // ----------------------------------------------------------- Constructors
080:
081: /**
082: * Create a JspRuntimeContext for a web application context.
083: *
084: * Loads in any previously generated dependencies from file.
085: *
086: * @param context ServletContext for web application
087: */
088: public JspRuntimeContext(ServletContext context, Options options) {
089:
090: System.setErr(new SystemLogHandler(System.err));
091:
092: this .context = context;
093: this .options = options;
094:
095: // Get the parent class loader
096: parentClassLoader = (URLClassLoader) Thread.currentThread()
097: .getContextClassLoader();
098: if (parentClassLoader == null) {
099: parentClassLoader = (URLClassLoader) this .getClass()
100: .getClassLoader();
101: }
102:
103: if (log.isDebugEnabled()) {
104: if (parentClassLoader != null) {
105: log.debug(Localizer.getMessage(
106: "jsp.message.parent_class_loader_is",
107: parentClassLoader.toString()));
108: } else {
109: log
110: .debug(Localizer.getMessage(
111: "jsp.message.parent_class_loader_is",
112: "<none>"));
113: }
114: }
115:
116: initClassPath();
117:
118: if (context instanceof org.apache.jasper.servlet.JspCServletContext) {
119: return;
120: }
121:
122: if (System.getSecurityManager() != null) {
123: initSecurity();
124: }
125:
126: // If this web application context is running from a
127: // directory, start the background compilation thread
128: String appBase = context.getRealPath("/");
129: if (!options.getDevelopment() && appBase != null
130: && options.getReloading()) {
131: if (appBase.endsWith(File.separator)) {
132: appBase = appBase.substring(0, appBase.length() - 1);
133: }
134: String directory = appBase.substring(appBase
135: .lastIndexOf(File.separator));
136: threadName = threadName + "[" + directory + "]";
137: threadStart();
138: }
139: }
140:
141: // ----------------------------------------------------- Instance Variables
142:
143: /**
144: * This web applications ServletContext
145: */
146: private ServletContext context;
147: private Options options;
148: private URLClassLoader parentClassLoader;
149: private PermissionCollection permissionCollection;
150: private CodeSource codeSource;
151: private String classpath;
152:
153: /**
154: * Maps JSP pages to their JspServletWrapper's
155: */
156: private Map jsps = Collections.synchronizedMap(new HashMap());
157:
158: /**
159: * The background thread.
160: */
161: private Thread thread = null;
162:
163: /**
164: * The background thread completion semaphore.
165: */
166: private boolean threadDone = false;
167:
168: /**
169: * Name to register for the background thread.
170: */
171: private String threadName = "JspRuntimeContext";
172:
173: // ------------------------------------------------------ Public Methods
174:
175: /**
176: * Add a new JspServletWrapper.
177: *
178: * @param jspUri JSP URI
179: * @param jsw Servlet wrapper for JSP
180: */
181: public void addWrapper(String jspUri, JspServletWrapper jsw) {
182: jsps.remove(jspUri);
183: jsps.put(jspUri, jsw);
184: }
185:
186: /**
187: * Get an already existing JspServletWrapper.
188: *
189: * @param jspUri JSP URI
190: * @return JspServletWrapper for JSP
191: */
192: public JspServletWrapper getWrapper(String jspUri) {
193: return (JspServletWrapper) jsps.get(jspUri);
194: }
195:
196: /**
197: * Remove a JspServletWrapper.
198: *
199: * @param jspUri JSP URI of JspServletWrapper to remove
200: */
201: public void removeWrapper(String jspUri) {
202: jsps.remove(jspUri);
203: }
204:
205: /**
206: * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
207: * the number of JSPs that have been loaded into the webapp.
208: *
209: * @return The number of JSPs that have been loaded into the webapp
210: */
211: public int getJspCount() {
212: return jsps.size();
213: }
214:
215: /**
216: * Get the SecurityManager Policy CodeSource for this web
217: * applicaiton context.
218: *
219: * @return CodeSource for JSP
220: */
221: public CodeSource getCodeSource() {
222: return codeSource;
223: }
224:
225: /**
226: * Get the parent URLClassLoader.
227: *
228: * @return URLClassLoader parent
229: */
230: public URLClassLoader getParentClassLoader() {
231: return parentClassLoader;
232: }
233:
234: /**
235: * Get the SecurityManager PermissionCollection for this
236: * web application context.
237: *
238: * @return PermissionCollection permissions
239: */
240: public PermissionCollection getPermissionCollection() {
241: return permissionCollection;
242: }
243:
244: /**
245: * Process a "destory" event for this web application context.
246: */
247: public void destroy() {
248:
249: if (System.err instanceof SystemLogHandler)
250: System.setErr(((SystemLogHandler) System.err).getWrapped());
251:
252: threadStop();
253:
254: Iterator servlets = jsps.values().iterator();
255: while (servlets.hasNext()) {
256: ((JspServletWrapper) servlets.next()).destroy();
257: }
258: }
259:
260: /**
261: * Increments the JSP reload counter.
262: */
263: public synchronized void incrementJspReloadCount() {
264: jspReloadCount++;
265: }
266:
267: /**
268: * Resets the JSP reload counter.
269: *
270: * @param count Value to which to reset the JSP reload counter
271: */
272: public synchronized void setJspReloadCount(int count) {
273: this .jspReloadCount = count;
274: }
275:
276: /**
277: * Gets the current value of the JSP reload counter.
278: *
279: * @return The current value of the JSP reload counter
280: */
281: public int getJspReloadCount() {
282: return jspReloadCount;
283: }
284:
285: // -------------------------------------------------------- Private Methods
286:
287: /**
288: * Method used by background thread to check the JSP dependencies
289: * registered with this class for JSP's.
290: */
291: private void checkCompile() {
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(
305: "Background compile failed", t);
306: }
307: }
308: }
309: }
310:
311: /**
312: * The classpath that is passed off to the Java compiler.
313: */
314: public String getClassPath() {
315: return classpath;
316: }
317:
318: /**
319: * Method used to initialize classpath for compiles.
320: */
321: private void initClassPath() {
322:
323: URL[] urls = parentClassLoader.getURLs();
324: StringBuffer cpath = new StringBuffer();
325: String sep = System.getProperty("path.separator");
326:
327: for (int i = 0; i < urls.length; i++) {
328: // Tomcat 4 can use URL's other than file URL's,
329: // a protocol other than file: will generate a
330: // bad file system path, so only add file:
331: // protocol URL's to the classpath.
332:
333: if (urls[i].getProtocol().equals("file")) {
334: cpath.append((String) urls[i].getFile() + sep);
335: }
336: }
337:
338: cpath.append(options.getScratchDir() + sep);
339:
340: String cp = (String) context
341: .getAttribute(Constants.SERVLET_CLASSPATH);
342: if (cp == null || cp.equals("")) {
343: cp = options.getClassPath();
344: }
345:
346: classpath = cpath.toString() + cp;
347: }
348:
349: /**
350: * Method used to initialize SecurityManager data.
351: */
352: private void initSecurity() {
353:
354: // Setup the PermissionCollection for this web app context
355: // based on the permissions configured for the root of the
356: // web app context directory, then add a file read permission
357: // for that directory.
358: Policy policy = Policy.getPolicy();
359: if (policy != null) {
360: try {
361: // Get the permissions for the web app context
362: String docBase = context.getRealPath("/");
363: if (docBase == null) {
364: docBase = options.getScratchDir().toString();
365: }
366: String codeBase = docBase;
367: if (!codeBase.endsWith(File.separator)) {
368: codeBase = codeBase + File.separator;
369: }
370: File contextDir = new File(codeBase);
371: URL url = contextDir.getCanonicalFile().toURL();
372: codeSource = new CodeSource(url, null);
373: permissionCollection = policy
374: .getPermissions(codeSource);
375:
376: // Create a file read permission for web app context directory
377: if (!docBase.endsWith(File.separator)) {
378: permissionCollection.add(new FilePermission(
379: docBase, "read"));
380: docBase = docBase + File.separator;
381: } else {
382: permissionCollection
383: .add(new FilePermission(docBase.substring(
384: 0, docBase.length() - 1), "read"));
385: }
386: docBase = docBase + "-";
387: permissionCollection.add(new FilePermission(docBase,
388: "read"));
389:
390: // Create a file read permission for web app tempdir (work)
391: // directory
392: String workDir = options.getScratchDir().toString();
393: if (!workDir.endsWith(File.separator)) {
394: permissionCollection.add(new FilePermission(
395: workDir, "read"));
396: workDir = workDir + File.separator;
397: }
398: workDir = workDir + "-";
399: permissionCollection.add(new FilePermission(workDir,
400: "read"));
401:
402: // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase
403: permissionCollection
404: .add(new RuntimePermission(
405: "accessClassInPackage.org.apache.jasper.runtime"));
406:
407: if (parentClassLoader instanceof URLClassLoader) {
408: URL[] urls = parentClassLoader.getURLs();
409: String jarUrl = null;
410: String jndiUrl = null;
411: for (int i = 0; i < urls.length; i++) {
412: if (jndiUrl == null
413: && urls[i].toString().startsWith(
414: "jndi:")) {
415: jndiUrl = urls[i].toString() + "-";
416: }
417: if (jarUrl == null
418: && urls[i].toString().startsWith(
419: "jar:jndi:")) {
420: jarUrl = urls[i].toString();
421: jarUrl = jarUrl.substring(0, jarUrl
422: .length() - 2);
423: jarUrl = jarUrl.substring(0, jarUrl
424: .lastIndexOf('/'))
425: + "/-";
426: }
427: }
428: if (jarUrl != null) {
429: permissionCollection.add(new FilePermission(
430: jarUrl, "read"));
431: permissionCollection.add(new FilePermission(
432: jarUrl.substring(4), "read"));
433: }
434: if (jndiUrl != null)
435: permissionCollection.add(new FilePermission(
436: jndiUrl, "read"));
437: }
438: } catch (Exception e) {
439: context.log("Security Init for context failed", e);
440: }
441: }
442: }
443:
444: // -------------------------------------------------------- Thread Support
445:
446: /**
447: * Start the background thread that will periodically check for
448: * changes to compile time included files in a JSP.
449: *
450: * @exception IllegalStateException if we should not be starting
451: * a background thread now
452: */
453: protected void threadStart() {
454:
455: // Has the background thread already been started?
456: if (thread != null) {
457: return;
458: }
459:
460: // Start the background thread
461: threadDone = false;
462: thread = new Thread(this , threadName);
463: thread.setDaemon(true);
464: thread.start();
465:
466: }
467:
468: /**
469: * Stop the background thread that is periodically checking for
470: * changes to compile time included files in a JSP.
471: */
472: protected void threadStop() {
473:
474: if (thread == null) {
475: return;
476: }
477:
478: threadDone = true;
479: thread.interrupt();
480: try {
481: thread.join();
482: } catch (InterruptedException e) {
483: ;
484: }
485:
486: thread = null;
487:
488: }
489:
490: /**
491: * Sleep for the duration specified by the <code>checkInterval</code>
492: * property.
493: */
494: protected void threadSleep() {
495:
496: try {
497: Thread.sleep(options.getCheckInterval() * 1000L);
498: } catch (InterruptedException e) {
499: ;
500: }
501:
502: }
503:
504: // ------------------------------------------------------ Background Thread
505:
506: /**
507: * The background thread that checks for changes to files
508: * included by a JSP and flags that a recompile is required.
509: */
510: public void run() {
511:
512: // Loop until the termination semaphore is set
513: while (!threadDone) {
514:
515: // Wait for our check interval
516: threadSleep();
517:
518: // Check for included files which are newer than the
519: // JSP which uses them.
520: try {
521: checkCompile();
522: } catch (Throwable t) {
523: t.printStackTrace();
524: }
525: }
526:
527: }
528:
529: }
|