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.struts2.jasper.Constants;
23 import org.apache.struts2.jasper.JspCompilationContext;
24 import org.apache.struts2.jasper.Options;
25 import org.apache.struts2.jasper.runtime.JspFactoryImpl;
26 import org.apache.struts2.jasper.security.SecurityClassLoad;
27 import org.apache.struts2.jasper.servlet.JspServletWrapper;
28
29 import javax.servlet.ServletContext;
30 import javax.servlet.jsp.JspFactory;
31 import java.io.File;
32 import java.io.FileNotFoundException;
33 import java.io.FilePermission;
34 import java.net.URL;
35 import java.net.URLClassLoader;
36 import java.security.CodeSource;
37 import java.security.PermissionCollection;
38 import java.security.Policy;
39 import java.security.cert.Certificate;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.Map;
44
45 /***
46 * Class for tracking JSP compile time file dependencies when the
47 * &060;%@include file="..."%&062; directive is used.
48 * <p/>
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 * <p/>
53 * Only used if a web application context is a directory.
54 *
55 * @author Glenn L. Nielsen
56 * @version $Revision: 466606 $
57 */
58 public final class JspRuntimeContext implements Runnable {
59
60
61 private Logger log = LoggerFactory.getLogger(JspRuntimeContext.class);
62
63
64
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 JspFactory.setDefaultFactory(factory);
76 }
77
78
79
80 /***
81 * Create a JspRuntimeContext for a web application context.
82 * <p/>
83 * Loads in any previously generated dependencies from file.
84 *
85 * @param context ServletContext for web application
86 */
87 public JspRuntimeContext(ServletContext context, Options options) {
88
89 this.context = context;
90 this.options = options;
91
92
93 parentClassLoader =
94 (URLClassLoader) Thread.currentThread().getContextClassLoader();
95 if (parentClassLoader == null) {
96 parentClassLoader =
97 (URLClassLoader) this.getClass().getClassLoader();
98 }
99
100 if (log.isDebugEnabled()) {
101 if (parentClassLoader != null) {
102 log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
103 parentClassLoader.toString()));
104 } else {
105 log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
106 "<none>"));
107 }
108 }
109
110 initClassPath();
111
112 if (context instanceof org.apache.struts2.jasper.servlet.JspCServletContext) {
113 return;
114 }
115
116 if (System.getSecurityManager() != null) {
117 initSecurity();
118 }
119
120
121
122 String appBase = context.getRealPath("/");
123 if (!options.getDevelopment()
124 && appBase != null
125 && options.getCheckInterval() > 0) {
126 if (appBase.endsWith(File.separator)) {
127 appBase = appBase.substring(0, appBase.length() - 1);
128 }
129 String directory =
130 appBase.substring(appBase.lastIndexOf(File.separator));
131 threadName = threadName + "[" + directory + "]";
132 threadStart();
133 }
134 }
135
136
137
138 /***
139 * This web applications ServletContext
140 */
141 private ServletContext context;
142 private Options options;
143 private URLClassLoader parentClassLoader;
144 private PermissionCollection permissionCollection;
145 private CodeSource codeSource;
146 private String classpath;
147
148 /***
149 * Maps JSP pages to their JspServletWrapper's
150 */
151 private Map jsps = Collections.synchronizedMap(new HashMap());
152
153
154 /***
155 * The background thread.
156 */
157 private Thread thread = null;
158
159
160 /***
161 * The background thread completion semaphore.
162 */
163 private boolean threadDone = false;
164
165
166 /***
167 * Name to register for the background thread.
168 */
169 private String threadName = "JspRuntimeContext";
170
171
172
173 /***
174 * Add a new JspServletWrapper.
175 *
176 * @param jspUri JSP URI
177 * @param jsw Servlet wrapper for JSP
178 */
179 public void addWrapper(String jspUri, JspServletWrapper jsw) {
180 jsps.remove(jspUri);
181 jsps.put(jspUri, jsw);
182 }
183
184 /***
185 * Get an already existing JspServletWrapper.
186 *
187 * @param jspUri JSP URI
188 * @return JspServletWrapper for JSP
189 */
190 public JspServletWrapper getWrapper(String jspUri) {
191 return (JspServletWrapper) jsps.get(jspUri);
192 }
193
194 /***
195 * Remove a JspServletWrapper.
196 *
197 * @param jspUri JSP URI of JspServletWrapper to remove
198 */
199 public void removeWrapper(String jspUri) {
200 jsps.remove(jspUri);
201 }
202
203 /***
204 * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
205 * the number of JSPs that have been loaded into the webapp.
206 *
207 * @return The number of JSPs that have been loaded into the webapp
208 */
209 public int getJspCount() {
210 return jsps.size();
211 }
212
213 /***
214 * Get the SecurityManager Policy CodeSource for this web
215 * applicaiton context.
216 *
217 * @return CodeSource for JSP
218 */
219 public CodeSource getCodeSource() {
220 return codeSource;
221 }
222
223 /***
224 * Get the parent URLClassLoader.
225 *
226 * @return URLClassLoader parent
227 */
228 public URLClassLoader getParentClassLoader() {
229 return parentClassLoader;
230 }
231
232 /***
233 * Get the SecurityManager PermissionCollection for this
234 * web application context.
235 *
236 * @return PermissionCollection permissions
237 */
238 public PermissionCollection getPermissionCollection() {
239 return permissionCollection;
240 }
241
242 /***
243 * Process a "destory" event for this web application context.
244 */
245 public void destroy() {
246 threadStop();
247
248 Iterator servlets = jsps.values().iterator();
249 while (servlets.hasNext()) {
250 ((JspServletWrapper) servlets.next()).destroy();
251 }
252 }
253
254 /***
255 * Increments the JSP reload counter.
256 */
257 public synchronized void incrementJspReloadCount() {
258 jspReloadCount++;
259 }
260
261 /***
262 * Resets the JSP reload counter.
263 *
264 * @param count Value to which to reset the JSP reload counter
265 */
266 public synchronized void setJspReloadCount(int count) {
267 this.jspReloadCount = count;
268 }
269
270 /***
271 * Gets the current value of the JSP reload counter.
272 *
273 * @return The current value of the JSP reload counter
274 */
275 public int getJspReloadCount() {
276 return jspReloadCount;
277 }
278
279
280
281
282 /***
283 * Method used by background thread to check the JSP dependencies
284 * registered with this class for JSP's.
285 */
286 private void checkCompile() {
287 Object[] wrappers = jsps.values().toArray();
288 for (int i = 0; i < wrappers.length; i++) {
289 JspServletWrapper jsw = (JspServletWrapper) wrappers[i];
290 JspCompilationContext ctxt = jsw.getJspEngineContext();
291
292
293 synchronized (jsw) {
294 try {
295 ctxt.compile();
296 } catch (FileNotFoundException ex) {
297 ctxt.incrementRemoved();
298 } catch (Throwable t) {
299 jsw.getServletContext().log("Background compile failed",
300 t);
301 }
302 }
303 }
304 }
305
306 /***
307 * The classpath that is passed off to the Java compiler.
308 */
309 public String getClassPath() {
310 return classpath;
311 }
312
313 /***
314 * Method used to initialize classpath for compiles.
315 */
316 private void initClassPath() {
317
318 URL[] urls = parentClassLoader.getURLs();
319 StringBuffer cpath = new StringBuffer();
320 String sep = System.getProperty("path.separator");
321
322 for (int i = 0; i < urls.length; i++) {
323
324
325
326
327
328 if (urls[i].getProtocol().equals("file")) {
329 cpath.append((String) urls[i].getFile() + sep);
330 }
331 }
332
333 cpath.append(options.getScratchDir() + sep);
334
335 String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH);
336 if (cp == null || cp.equals("")) {
337 cp = options.getClassPath();
338 }
339
340 classpath = cpath.toString() + cp;
341
342 if (log.isDebugEnabled()) {
343 log.debug("Compilation classpath initialized: " + getClassPath());
344 }
345 }
346
347 /***
348 * Method used to initialize SecurityManager data.
349 */
350 private void initSecurity() {
351
352
353
354
355
356 Policy policy = Policy.getPolicy();
357 if (policy != null) {
358 try {
359
360 String docBase = context.getRealPath("/");
361 if (docBase == null) {
362 docBase = options.getScratchDir().toString();
363 }
364 String codeBase = docBase;
365 if (!codeBase.endsWith(File.separator)) {
366 codeBase = codeBase + File.separator;
367 }
368 File contextDir = new File(codeBase);
369 URL url = contextDir.getCanonicalFile().toURL();
370 codeSource = new CodeSource(url, (Certificate[]) null);
371 permissionCollection = policy.getPermissions(codeSource);
372
373
374 if (!docBase.endsWith(File.separator)) {
375 permissionCollection.add
376 (new FilePermission(docBase, "read"));
377 docBase = docBase + File.separator;
378 } else {
379 permissionCollection.add
380 (new FilePermission
381 (docBase.substring(0, docBase.length() - 1), "read"));
382 }
383 docBase = docBase + "-";
384 permissionCollection.add(new FilePermission(docBase, "read"));
385
386
387
388 String workDir = options.getScratchDir().toString();
389 if (!workDir.endsWith(File.separator)) {
390 permissionCollection.add
391 (new FilePermission(workDir, "read"));
392 workDir = workDir + File.separator;
393 }
394 workDir = workDir + "-";
395 permissionCollection.add(new FilePermission(workDir, "read"));
396
397
398 permissionCollection.add(new RuntimePermission(
399 "accessClassInPackage.org.apache.struts2.jasper.runtime"));
400
401 if (parentClassLoader instanceof URLClassLoader) {
402 URL[] urls = parentClassLoader.getURLs();
403 String jarUrl = null;
404 String jndiUrl = null;
405 for (int i = 0; i < urls.length; i++) {
406 if (jndiUrl == null
407 && urls[i].toString().startsWith("jndi:")) {
408 jndiUrl = urls[i].toString() + "-";
409 }
410 if (jarUrl == null
411 && urls[i].toString().startsWith("jar:jndi:")
412 ) {
413 jarUrl = urls[i].toString();
414 jarUrl = jarUrl.substring(0, jarUrl.length() - 2);
415 jarUrl = jarUrl.substring(0,
416 jarUrl.lastIndexOf('/')) + "/-";
417 }
418 }
419 if (jarUrl != null) {
420 permissionCollection.add(
421 new FilePermission(jarUrl, "read"));
422 permissionCollection.add(
423 new FilePermission(jarUrl.substring(4), "read"));
424 }
425 if (jndiUrl != null)
426 permissionCollection.add(
427 new FilePermission(jndiUrl, "read"));
428 }
429 } catch (Exception e) {
430 context.log("Security Init for context failed", e);
431 }
432 }
433 }
434
435
436
437
438 /***
439 * Start the background thread that will periodically check for
440 * changes to compile time included files in a JSP.
441 *
442 * @throws IllegalStateException if we should not be starting
443 * a background thread now
444 */
445 protected void threadStart() {
446
447
448 if (thread != null) {
449 return;
450 }
451
452
453 threadDone = false;
454 thread = new Thread(this, threadName);
455 thread.setDaemon(true);
456 thread.start();
457
458 }
459
460
461 /***
462 * Stop the background thread that is periodically checking for
463 * changes to compile time included files in a JSP.
464 */
465 protected void threadStop() {
466
467 if (thread == null) {
468 return;
469 }
470
471 threadDone = true;
472 thread.interrupt();
473 try {
474 thread.join();
475 } catch (InterruptedException e) {
476 ;
477 }
478
479 thread = null;
480
481 }
482
483 /***
484 * Sleep for the duration specified by the <code>checkInterval</code>
485 * property.
486 */
487 protected void threadSleep() {
488
489 try {
490 Thread.sleep(options.getCheckInterval() * 1000L);
491 } catch (InterruptedException e) {
492 ;
493 }
494
495 }
496
497
498
499
500
501 /***
502 * The background thread that checks for changes to files
503 * included by a JSP and flags that a recompile is required.
504 */
505 public void run() {
506
507
508 while (!threadDone) {
509
510
511 threadSleep();
512
513
514
515 try {
516 checkCompile();
517 } catch (Throwable t) {
518 log.error("Exception checking if recompile needed: ", t);
519 }
520 }
521
522 }
523
524 }