001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.jasper.compiler;
019:
020: import java.io.File;
021: import java.io.FileNotFoundException;
022: import java.io.FilePermission;
023: import java.net.URL;
024: import java.net.URLClassLoader;
025: import java.security.CodeSource;
026: import java.security.PermissionCollection;
027: import java.security.Policy;
028: import java.security.cert.Certificate;
029: import java.util.Iterator;
030: import java.util.Map;
031: import java.util.concurrent.ConcurrentHashMap;
032:
033: import javax.servlet.ServletContext;
034: import javax.servlet.jsp.JspFactory;
035:
036: import org.apache.jasper.Constants;
037: import org.apache.jasper.JspCompilationContext;
038: import org.apache.jasper.Options;
039: import org.apache.jasper.runtime.JspFactoryImpl;
040: import org.apache.jasper.security.SecurityClassLoad;
041: import org.apache.jasper.servlet.JspServletWrapper;
042: import org.apache.juli.logging.Log;
043: import org.apache.juli.logging.LogFactory;
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: 505593 $
057: */
058: public final class JspRuntimeContext {
059:
060: // Logger
061: private 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: if (System.getSecurityManager() != null) {
077: String basePackage = "org.apache.jasper.";
078: try {
079: factory
080: .getClass()
081: .getClassLoader()
082: .loadClass(
083: basePackage
084: + "runtime.JspFactoryImpl$PrivilegedGetPageContext");
085: factory
086: .getClass()
087: .getClassLoader()
088: .loadClass(
089: basePackage
090: + "runtime.JspFactoryImpl$PrivilegedReleasePageContext");
091: factory.getClass().getClassLoader().loadClass(
092: basePackage + "runtime.JspRuntimeLibrary");
093: factory
094: .getClass()
095: .getClassLoader()
096: .loadClass(
097: basePackage
098: + "runtime.JspRuntimeLibrary$PrivilegedIntrospectHelper");
099: factory
100: .getClass()
101: .getClassLoader()
102: .loadClass(
103: basePackage
104: + "runtime.ServletResponseWrapperInclude");
105: factory.getClass().getClassLoader().loadClass(
106: basePackage + "servlet.JspServletWrapper");
107: } catch (ClassNotFoundException ex) {
108: throw new IllegalStateException(ex);
109: }
110: }
111:
112: JspFactory.setDefaultFactory(factory);
113: }
114:
115: // ----------------------------------------------------------- Constructors
116:
117: /**
118: * Create a JspRuntimeContext for a web application context.
119: *
120: * Loads in any previously generated dependencies from file.
121: *
122: * @param context ServletContext for web application
123: */
124: public JspRuntimeContext(ServletContext context, Options options) {
125:
126: this .context = context;
127: this .options = options;
128:
129: // Get the parent class loader
130: parentClassLoader = (URLClassLoader) Thread.currentThread()
131: .getContextClassLoader();
132: if (parentClassLoader == null) {
133: parentClassLoader = (URLClassLoader) this .getClass()
134: .getClassLoader();
135: }
136:
137: if (log.isDebugEnabled()) {
138: if (parentClassLoader != null) {
139: log.debug(Localizer.getMessage(
140: "jsp.message.parent_class_loader_is",
141: parentClassLoader.toString()));
142: } else {
143: log
144: .debug(Localizer.getMessage(
145: "jsp.message.parent_class_loader_is",
146: "<none>"));
147: }
148: }
149:
150: initClassPath();
151:
152: if (context instanceof org.apache.jasper.servlet.JspCServletContext) {
153: return;
154: }
155:
156: if (Constants.IS_SECURITY_ENABLED) {
157: initSecurity();
158: }
159:
160: // If this web application context is running from a
161: // directory, start the background compilation thread
162: String appBase = context.getRealPath("/");
163: if (!options.getDevelopment() && appBase != null
164: && options.getCheckInterval() > 0) {
165: lastCheck = System.currentTimeMillis();
166: }
167: }
168:
169: // ----------------------------------------------------- Instance Variables
170:
171: /**
172: * This web applications ServletContext
173: */
174: private ServletContext context;
175: private Options options;
176: private URLClassLoader parentClassLoader;
177: private PermissionCollection permissionCollection;
178: private CodeSource codeSource;
179: private String classpath;
180: private long lastCheck = -1L;
181:
182: /**
183: * Maps JSP pages to their JspServletWrapper's
184: */
185: private Map<String, JspServletWrapper> jsps = new ConcurrentHashMap<String, JspServletWrapper>();
186:
187: // ------------------------------------------------------ Public Methods
188:
189: /**
190: * Add a new JspServletWrapper.
191: *
192: * @param jspUri JSP URI
193: * @param jsw Servlet wrapper for JSP
194: */
195: public void addWrapper(String jspUri, JspServletWrapper jsw) {
196: jsps.put(jspUri, jsw);
197: }
198:
199: /**
200: * Get an already existing JspServletWrapper.
201: *
202: * @param jspUri JSP URI
203: * @return JspServletWrapper for JSP
204: */
205: public JspServletWrapper getWrapper(String jspUri) {
206: return jsps.get(jspUri);
207: }
208:
209: /**
210: * Remove a JspServletWrapper.
211: *
212: * @param jspUri JSP URI of JspServletWrapper to remove
213: */
214: public void removeWrapper(String jspUri) {
215: jsps.remove(jspUri);
216: }
217:
218: /**
219: * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
220: * the number of JSPs that have been loaded into the webapp.
221: *
222: * @return The number of JSPs that have been loaded into the webapp
223: */
224: public int getJspCount() {
225: return jsps.size();
226: }
227:
228: /**
229: * Get the SecurityManager Policy CodeSource for this web
230: * applicaiton context.
231: *
232: * @return CodeSource for JSP
233: */
234: public CodeSource getCodeSource() {
235: return codeSource;
236: }
237:
238: /**
239: * Get the parent URLClassLoader.
240: *
241: * @return URLClassLoader parent
242: */
243: public URLClassLoader getParentClassLoader() {
244: return parentClassLoader;
245: }
246:
247: /**
248: * Get the SecurityManager PermissionCollection for this
249: * web application context.
250: *
251: * @return PermissionCollection permissions
252: */
253: public PermissionCollection getPermissionCollection() {
254: return permissionCollection;
255: }
256:
257: /**
258: * Process a "destory" event for this web application context.
259: */
260: public void destroy() {
261: Iterator servlets = jsps.values().iterator();
262: while (servlets.hasNext()) {
263: ((JspServletWrapper) servlets.next()).destroy();
264: }
265: }
266:
267: /**
268: * Increments the JSP reload counter.
269: */
270: public synchronized void incrementJspReloadCount() {
271: jspReloadCount++;
272: }
273:
274: /**
275: * Resets the JSP reload counter.
276: *
277: * @param count Value to which to reset the JSP reload counter
278: */
279: public synchronized void setJspReloadCount(int count) {
280: this .jspReloadCount = count;
281: }
282:
283: /**
284: * Gets the current value of the JSP reload counter.
285: *
286: * @return The current value of the JSP reload counter
287: */
288: public int getJspReloadCount() {
289: return jspReloadCount;
290: }
291:
292: /**
293: * Method used by background thread to check the JSP dependencies
294: * registered with this class for JSP's.
295: */
296: public void checkCompile() {
297:
298: if (lastCheck < 0) {
299: // Checking was disabled
300: return;
301: }
302: long now = System.currentTimeMillis();
303: if (now > (lastCheck + (options.getCheckInterval() * 1000L))) {
304: lastCheck = now;
305: } else {
306: return;
307: }
308:
309: Object[] wrappers = jsps.values().toArray();
310: for (int i = 0; i < wrappers.length; i++) {
311: JspServletWrapper jsw = (JspServletWrapper) wrappers[i];
312: JspCompilationContext ctxt = jsw.getJspEngineContext();
313: // JspServletWrapper also synchronizes on this when
314: // it detects it has to do a reload
315: synchronized (jsw) {
316: try {
317: ctxt.compile();
318: } catch (FileNotFoundException ex) {
319: ctxt.incrementRemoved();
320: } catch (Throwable t) {
321: jsw.getServletContext().log(
322: "Background compile failed", t);
323: }
324: }
325: }
326:
327: }
328:
329: /**
330: * The classpath that is passed off to the Java compiler.
331: */
332: public String getClassPath() {
333: return classpath;
334: }
335:
336: // -------------------------------------------------------- Private Methods
337:
338: /**
339: * Method used to initialize classpath for compiles.
340: */
341: private void initClassPath() {
342:
343: URL[] urls = parentClassLoader.getURLs();
344: StringBuffer cpath = new StringBuffer();
345: String sep = System.getProperty("path.separator");
346:
347: for (int i = 0; i < urls.length; i++) {
348: // Tomcat 4 can use URL's other than file URL's,
349: // a protocol other than file: will generate a
350: // bad file system path, so only add file:
351: // protocol URL's to the classpath.
352:
353: if (urls[i].getProtocol().equals("file")) {
354: cpath.append((String) urls[i].getFile() + sep);
355: }
356: }
357:
358: cpath.append(options.getScratchDir() + sep);
359:
360: String cp = (String) context
361: .getAttribute(Constants.SERVLET_CLASSPATH);
362: if (cp == null || cp.equals("")) {
363: cp = options.getClassPath();
364: }
365:
366: classpath = cpath.toString() + cp;
367:
368: if (log.isDebugEnabled()) {
369: log.debug("Compilation classpath initialized: "
370: + getClassPath());
371: }
372: }
373:
374: /**
375: * Method used to initialize SecurityManager data.
376: */
377: private void initSecurity() {
378:
379: // Setup the PermissionCollection for this web app context
380: // based on the permissions configured for the root of the
381: // web app context directory, then add a file read permission
382: // for that directory.
383: Policy policy = Policy.getPolicy();
384: if (policy != null) {
385: try {
386: // Get the permissions for the web app context
387: String docBase = context.getRealPath("/");
388: if (docBase == null) {
389: docBase = options.getScratchDir().toString();
390: }
391: String codeBase = docBase;
392: if (!codeBase.endsWith(File.separator)) {
393: codeBase = codeBase + File.separator;
394: }
395: File contextDir = new File(codeBase);
396: URL url = contextDir.getCanonicalFile().toURL();
397: codeSource = new CodeSource(url, (Certificate[]) null);
398: permissionCollection = policy
399: .getPermissions(codeSource);
400:
401: // Create a file read permission for web app context directory
402: if (!docBase.endsWith(File.separator)) {
403: permissionCollection.add(new FilePermission(
404: docBase, "read"));
405: docBase = docBase + File.separator;
406: } else {
407: permissionCollection
408: .add(new FilePermission(docBase.substring(
409: 0, docBase.length() - 1), "read"));
410: }
411: docBase = docBase + "-";
412: permissionCollection.add(new FilePermission(docBase,
413: "read"));
414:
415: // Create a file read permission for web app tempdir (work)
416: // directory
417: String workDir = options.getScratchDir().toString();
418: if (!workDir.endsWith(File.separator)) {
419: permissionCollection.add(new FilePermission(
420: workDir, "read"));
421: workDir = workDir + File.separator;
422: }
423: workDir = workDir + "-";
424: permissionCollection.add(new FilePermission(workDir,
425: "read"));
426:
427: // Allow the JSP to access org.apache.jasper.runtime.HttpJspBase
428: permissionCollection
429: .add(new RuntimePermission(
430: "accessClassInPackage.org.apache.jasper.runtime"));
431:
432: if (parentClassLoader instanceof URLClassLoader) {
433: URL[] urls = parentClassLoader.getURLs();
434: String jarUrl = null;
435: String jndiUrl = null;
436: for (int i = 0; i < urls.length; i++) {
437: if (jndiUrl == null
438: && urls[i].toString().startsWith(
439: "jndi:")) {
440: jndiUrl = urls[i].toString() + "-";
441: }
442: if (jarUrl == null
443: && urls[i].toString().startsWith(
444: "jar:jndi:")) {
445: jarUrl = urls[i].toString();
446: jarUrl = jarUrl.substring(0, jarUrl
447: .length() - 2);
448: jarUrl = jarUrl.substring(0, jarUrl
449: .lastIndexOf('/'))
450: + "/-";
451: }
452: }
453: if (jarUrl != null) {
454: permissionCollection.add(new FilePermission(
455: jarUrl, "read"));
456: permissionCollection.add(new FilePermission(
457: jarUrl.substring(4), "read"));
458: }
459: if (jndiUrl != null)
460: permissionCollection.add(new FilePermission(
461: jndiUrl, "read"));
462: }
463: } catch (Exception e) {
464: context.log("Security Init for context failed", e);
465: }
466: }
467: }
468:
469: }
|