001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.jsp;
031:
032: import com.caucho.jsp.cfg.JspPropertyGroup;
033: import com.caucho.loader.Environment;
034: import com.caucho.log.Log;
035: import com.caucho.server.connection.CauchoResponse;
036: import com.caucho.server.connection.ToCharResponseAdapter;
037: import com.caucho.server.webapp.WebApp;
038: import com.caucho.util.Alarm;
039: import com.caucho.util.Base64;
040: import com.caucho.util.CharBuffer;
041: import com.caucho.util.QDate;
042: import com.caucho.vfs.Depend;
043: import com.caucho.vfs.Dependency;
044: import com.caucho.vfs.Path;
045: import com.caucho.vfs.PersistentDependency;
046:
047: import javax.servlet.Servlet;
048: import javax.servlet.ServletConfig;
049: import javax.servlet.ServletContext;
050: import javax.servlet.ServletException;
051: import javax.servlet.http.HttpServletRequest;
052: import javax.servlet.http.HttpServletResponse;
053: import java.io.IOException;
054: import java.lang.reflect.Method;
055: import java.util.ArrayList;
056: import java.util.Enumeration;
057: import java.util.HashMap;
058: import java.util.logging.Level;
059: import java.util.logging.Logger;
060:
061: /**
062: * Represents a compiled JSP page.
063: */
064: abstract public class Page implements Servlet, ServletConfig,
065: CauchoPage {
066: protected static final Logger _caucho_log = Log.open(Page.class);
067:
068: private ServletConfig _config;
069: private WebApp _webApp;
070:
071: private ArrayList<PersistentDependency> _depends;
072: private ArrayList<Depend> _cacheDepends;
073: private String _media;
074: protected String _contentType;
075: protected boolean _alwaysModified;
076: protected boolean _neverModified;
077:
078: private PageManager.Entry _entry;
079: private long _lastModified;
080: private String _lastModifiedString;
081: private String _etag;
082: private QDate _calendar;
083:
084: private long _updateInterval = Environment
085: .getDependencyCheckInterval();
086: private long _lastUpdateCheck;
087: private JspManager _jspManager;
088:
089: private boolean _isRecompiling = false;
090: private int _useCount;
091: private boolean _isDead = true;
092:
093: public void init(Path path) throws ServletException {
094: }
095:
096: void _caucho_setContentType(String contentType) {
097: _contentType = contentType;
098: }
099:
100: void _caucho_setUpdateInterval(long updateInterval) {
101: _updateInterval = updateInterval;
102: }
103:
104: void _caucho_setJspManager(JspManager manager) {
105: _jspManager = manager;
106: }
107:
108: void _caucho_unload() {
109: if (_jspManager != null)
110: _jspManager.unload(this );
111: }
112:
113: void _caucho_setEntry(PageManager.Entry entry) {
114: _entry = entry;
115: }
116:
117: protected void _caucho_setContentType(String contentType,
118: String encoding) {
119: if (encoding != null && encoding.equals("ISO-8859-1"))
120: encoding = null;
121:
122: _contentType = contentType;
123: }
124:
125: /**
126: * Marks the page as uncacheable.
127: */
128: void _caucho_setUncacheable() {
129: _cacheDepends = null;
130: }
131:
132: /**
133: * When called treats the JSP page as always modified, i.e. always forcing
134: * recompilation.
135: */
136: protected void _caucho_setAlwaysModified() {
137: if (_cacheDepends == null)
138: _alwaysModified = true;
139: }
140:
141: /**
142: * When called treats the JSP page as always modified, i.e. always forcing
143: * recompilation.
144: */
145: protected void _caucho_setModified() {
146: _alwaysModified = true;
147: }
148:
149: /**
150: * Set if the page is never modified. Some users want to deploy
151: * the JSP classes without the JSP source.
152: */
153: protected void _caucho_setNeverModified(boolean modified) {
154: _neverModified = modified;
155: }
156:
157: /**
158: * Adds a dependency to the page.
159: *
160: * @param path the file the JSP page is dependent on.
161: */
162: protected void _caucho_addDepend(Path path) {
163: PersistentDependency depend = path.createDepend();
164: if (depend instanceof Depend)
165: ((Depend) depend).setRequireSource(getRequireSource());
166:
167: _caucho_addDepend(depend);
168: }
169:
170: /**
171: * Adds a dependency to the page.
172: *
173: * @param path the file the JSP page is dependent on.
174: */
175: protected void _caucho_addDepend(PersistentDependency depend) {
176: if (_depends == null)
177: _depends = new ArrayList<PersistentDependency>();
178:
179: if (!_depends.contains(depend))
180: _depends.add(depend);
181: }
182:
183: /**
184: * Adds an array of dependencies to the page.
185: */
186: protected void _caucho_addDepend(
187: ArrayList<PersistentDependency> dependList) {
188: if (dependList == null)
189: return;
190:
191: for (int i = 0; i < dependList.size(); i++)
192: _caucho_addDepend(dependList.get(i));
193: }
194:
195: /**
196: * Adds a JSP source dependency. If the source file changes, the JSP must
197: * be recompiled.
198: *
199: * @param path the path to the file
200: * @param lastModified the last modified time
201: * @param length the length of the file
202: */
203: protected void _caucho_addDepend(Path path, long lastModified,
204: long length) {
205: if (_depends == null)
206: _depends = new ArrayList<PersistentDependency>();
207:
208: Depend depend = new Depend(path, lastModified, length);
209: depend.setRequireSource(getRequireSource());
210:
211: _caucho_addDepend(depend);
212: }
213:
214: /**
215: * Marks the page as cacheable.
216: */
217: protected void _caucho_setCacheable() {
218: _cacheDepends = new ArrayList<Depend>();
219: _alwaysModified = false;
220: }
221:
222: /**
223: * Adds a single cache dependency. A cache dependency will cause
224: * the page to be rerun, but will not force a recompilation of the JSP.
225: *
226: * @param path the path to the file
227: * @param lastModified the last modified time
228: * @param length the length of the file
229: */
230: protected void _caucho_addCacheDepend(Path path, long lastModified,
231: long length) {
232: if (_cacheDepends == null)
233: _cacheDepends = new ArrayList<Depend>();
234:
235: Depend depend = new Depend(path, lastModified, length);
236:
237: if (!_cacheDepends.contains(depend))
238: _cacheDepends.add(depend);
239: }
240:
241: /**
242: * Adds an array of dependencies which will change the results of
243: * running the page.
244: *
245: * @param depends an array list of Depend
246: */
247: void _caucho_setCacheable(ArrayList<Path> depends) {
248: _cacheDepends = new ArrayList<Depend>();
249: for (int i = 0; i < depends.size(); i++) {
250: Path path = depends.get(i);
251:
252: Depend depend = new Depend(path);
253: depend.setRequireSource(getRequireSource());
254:
255: if (!_cacheDepends.contains(depend))
256: _cacheDepends.add(depend);
257: }
258: }
259:
260: /**
261: * Returns true if the underlying source has been modified.
262: */
263: public boolean _caucho_isModified() {
264: if (_alwaysModified || _isDead)
265: return true;
266:
267: if (_depends == null)
268: return false;
269:
270: for (int i = 0; i < _depends.size(); i++) {
271: Dependency depend = _depends.get(i);
272:
273: if (depend.isModified())
274: return true;
275: }
276:
277: return false;
278: }
279:
280: /***
281: * Returns true if the underlying source has been modified.
282: */
283: public boolean cauchoIsModified() {
284: long now = Alarm.getCurrentTime();
285:
286: if (_alwaysModified || _isDead)
287: return true;
288: else if (now < _lastUpdateCheck + _updateInterval)
289: return false;
290:
291: /* XXX: this check is redundant, because the invocation has already
292: * checked it.
293: ClassLoader classLoader = getClass().getClassLoader();
294:
295: if (classLoader instanceof DynamicClassLoader) {
296: DynamicClassLoader dynLoader;
297: dynLoader = (DynamicClassLoader) getClass().getClassLoader();
298:
299: if (dynLoader.isModified())
300: return true;
301: }
302: */
303:
304: if (_neverModified) {
305: _lastUpdateCheck = now;
306:
307: if (_depends == null)
308: return false;
309:
310: for (int i = 0; i < _depends.size(); i++) {
311: Dependency depend = _depends.get(i);
312: if (depend.isModified())
313: return true;
314: }
315:
316: return false;
317: } else {
318: boolean isModified = _caucho_isModified();
319:
320: if (!isModified)
321: _lastUpdateCheck = now;
322: return isModified;
323: }
324: }
325:
326: protected HashMap<String, Method> _caucho_getFunctionMap() {
327: return null;
328: }
329:
330: /**
331: * Returns true if deleting the underlying JSP will force a recompilation.
332: */
333: private boolean getRequireSource() {
334: return false;
335: }
336:
337: /**
338: * Initialize the servlet.
339: */
340: public void init(ServletConfig config) throws ServletException {
341: if (_config != null)
342: return;
343:
344: _config = config;
345: _isDead = false;
346:
347: _webApp = (WebApp) config.getServletContext();
348:
349: cauchoIsModified();
350:
351: if (!disableLog() && _caucho_log.isLoggable(Level.FINE))
352: _caucho_log.fine(getClass().getName() + " init");
353: }
354:
355: /**
356: * Returns true if initializes.
357: */
358: public boolean isInit() {
359: return _config != null;
360: }
361:
362: /**
363: * Returns the Resin webApp.
364: */
365: public WebApp _caucho_getApplication() {
366: return _webApp;
367: }
368:
369: public ServletContext getServletContext() {
370: return _webApp;
371: }
372:
373: public String getServletName() {
374: return _config.getServletName();
375: }
376:
377: public String getInitParameter(String name) {
378: return _config.getInitParameter(name);
379: }
380:
381: public Enumeration getInitParameterNames() {
382: return _config.getInitParameterNames();
383: }
384:
385: public void log(String msg) {
386: _webApp.log(getClass().getName() + ": " + msg);
387: }
388:
389: public void log(String msg, Throwable cause) {
390: _webApp.log(getClass().getName() + ": " + msg, cause);
391: }
392:
393: public String getServletInfo() {
394: return "A JSP Page";
395: }
396:
397: boolean disableLog() {
398: JspPropertyGroup jsp = _webApp.getJsp();
399:
400: if (jsp != null)
401: return jsp.isDisableLog();
402:
403: return true;
404: }
405:
406: /**
407: * Returns this servlet's configuration.
408: */
409: public ServletConfig getServletConfig() {
410: return _config;
411: }
412:
413: /**
414: * Initialize the response headers.
415: */
416: public void _caucho_init(HttpServletRequest req,
417: HttpServletResponse res) {
418: if (_contentType != null)
419: res.setContentType(_contentType);
420: else
421: res.setContentType("text/html");
422: }
423:
424: /**
425: * Returns the Last-Modified time for use in caching. If the result
426: * is <= 0, last-modified caching is disabled.
427: *
428: * @return the last modified time.
429: */
430: public long getLastModified(HttpServletRequest request) {
431: return _caucho_lastModified();
432: }
433:
434: /**
435: * The default Last-Modified time is just the most recently modified file.
436: * For JSP files, this is overwritten to always return 0.
437: */
438: public long _caucho_lastModified() {
439: if (_cacheDepends == null) {
440: return 0;
441: } else {
442: return calculateLastModified(_depends, _cacheDepends);
443: }
444: }
445:
446: /**
447: * Calculate the last modified time for all the dependencies. The
448: * last modified time is the time of the most recently changed
449: * cache or source file.
450: *
451: * @param depends list of the source file dependencies
452: * @param cacheDepends list of the cache dependencies
453: *
454: * @return the last modified time in milliseconds
455: */
456: public static long calculateLastModified(
457: ArrayList<PersistentDependency> depends,
458: ArrayList<Depend> cacheDepends) {
459: long lastModified = 0;
460:
461: for (int i = 0; i < depends.size(); i++) {
462: PersistentDependency dependency = depends.get(i);
463:
464: if (dependency instanceof Depend) {
465: Depend depend = (Depend) dependency;
466: long modified = depend.getLastModified();
467: if (lastModified < modified)
468: lastModified = modified;
469: }
470: }
471:
472: for (int i = 0; cacheDepends != null && i < cacheDepends.size(); i++) {
473: Depend depend = cacheDepends.get(i);
474: long modified = depend.getLastModified();
475: if (lastModified < modified)
476: lastModified = modified;
477: }
478:
479: return lastModified;
480: }
481:
482: /**
483: * The extended service method creates JavaScript global variables
484: * from a property map.
485: *
486: * <p>This method only makes sense for JavaScript templates. To pass
487: * variables to Java templates, use the setAttribute() method of the
488: * request.
489: *
490: * @param properties hashmap of objects to create as JavaScript globals.
491: */
492: public void pageservice(HttpServletRequest req,
493: HttpServletResponse res) throws IOException,
494: ServletException {
495: PageManager.Entry entry = _entry;
496: if (entry != null)
497: entry.accessPage();
498:
499: long lastModified = getLastModified(req);
500:
501: if (lastModified > 0) {
502: if (_calendar == null)
503: _calendar = new QDate();
504:
505: String etag = _etag;
506: if (lastModified != _lastModified) {
507: CharBuffer cb = new CharBuffer();
508: cb.append('"');
509: Base64.encode(cb, lastModified);
510: cb.append('"');
511: etag = cb.close();
512: _etag = etag;
513:
514: synchronized (_calendar) {
515: _calendar.setGMTTime(lastModified);
516: _lastModifiedString = _calendar.printDate();
517: }
518:
519: _lastModified = lastModified;
520: }
521:
522: String ifMatch = req.getHeader("If-None-Match");
523: if (etag.equals(ifMatch)) {
524: res.sendError(res.SC_NOT_MODIFIED);
525: return;
526: }
527:
528: String ifModifiedSince = req.getHeader("If-Modified-Since");
529: if (_lastModifiedString.equals(ifModifiedSince)) {
530: res.sendError(res.SC_NOT_MODIFIED);
531: return;
532: }
533:
534: res.setHeader("ETag", etag);
535: res.setHeader("Last-Modified", _lastModifiedString);
536: }
537:
538: if (res instanceof CauchoResponse) {
539: service(req, res);
540: } else {
541: // The wrapping is needed to handle the output stream.
542: ToCharResponseAdapter resAdapt = ToCharResponseAdapter
543: .create(res);
544: // resAdapt.setRequest(req);
545: //resAdapt.init(res);
546:
547: try {
548: service(req, resAdapt);
549: } finally {
550: resAdapt.close();
551: ToCharResponseAdapter.free(resAdapt);
552: }
553: }
554: }
555:
556: protected void setDead() {
557: _isDead = true;
558: }
559:
560: public boolean isDead() {
561: return _isDead;
562: }
563:
564: /**
565: * Starts recompiling. Returns false if the recompiling has already
566: * started or if the page has already been destroyed.
567: */
568: public boolean startRecompiling() {
569: boolean allowRecompiling;
570:
571: synchronized (this ) {
572: allowRecompiling = _isDead || !_isRecompiling;
573: _isRecompiling = true;
574: }
575:
576: return allowRecompiling;
577: }
578:
579: String getErrorPage() {
580: return null;
581: }
582:
583: /**
584: * Marks the page as used.
585: */
586: public void _caucho_use() {
587: synchronized (this ) {
588: _useCount++;
589: }
590: }
591:
592: /**
593: * Marks the page as free.
594: */
595: public void _caucho_free() {
596: synchronized (this ) {
597: _useCount--;
598: }
599:
600: if (_useCount <= 0)
601: destroy();
602: }
603:
604: public void destroy() {
605: if (_isDead)
606: return;
607:
608: _isDead = true;
609: /*
610: if (! _isDead)
611: throw new IllegalStateException();
612: */
613:
614: _entry = null;
615:
616: Thread thread = Thread.currentThread();
617: ClassLoader oldLoader = thread.getContextClassLoader();
618: try {
619: thread.setContextClassLoader(getClass().getClassLoader());
620:
621: _caucho_log.fine(getClass().getName() + " destroy");
622: } finally {
623: thread.setContextClassLoader(oldLoader);
624: }
625: }
626: }
|