001: /******************************************************************************
002: * ResponderCompile.java
003: * ****************************************************************************/package org.openlaszlo.servlets.responders;
004:
005: import java.io.*;
006: import java.net.URL;
007: import java.util.*;
008: import javax.servlet.ServletConfig;
009: import javax.servlet.ServletOutputStream;
010: import javax.servlet.ServletException;
011: import javax.servlet.http.HttpUtils;
012: import javax.servlet.http.HttpSession;
013: import javax.servlet.http.HttpServletRequest;
014: import javax.servlet.http.HttpServletResponse;
015: import org.openlaszlo.cm.CompilationManager;
016: import org.openlaszlo.compiler.Canvas;
017: import org.openlaszlo.compiler.CompilationError;
018: import org.openlaszlo.compiler.CompilationEnvironment;
019: import org.openlaszlo.sc.ScriptCompiler;
020: import org.openlaszlo.utils.ContentEncoding;
021: import org.openlaszlo.utils.FileUtils;
022: import org.openlaszlo.utils.LZHttpUtils;
023: import org.openlaszlo.utils.LZGetMethod;
024: import org.openlaszlo.utils.StringUtils;
025: import org.openlaszlo.server.LPS;
026: import org.openlaszlo.servlets.LZBindingListener;
027: import org.apache.commons.httpclient.HttpClient;
028: import org.apache.commons.httpclient.methods.GetMethod;
029: import org.apache.commons.httpclient.HostConfiguration;
030: import org.apache.log4j.Logger;
031:
032: public abstract class ResponderCompile extends Responder {
033: protected static CompilationManager mCompMgr = null;
034: protected static ScriptCompiler mScriptCache = null;
035:
036: private static boolean mIsInitialized = false;
037: private static boolean mAllowRequestSOURCE = true;
038: private static boolean mAllowRecompile = true;
039: private static boolean mCheckModifiedSince = true;
040: private static String mAdminPassword = null;
041: private static Logger mLogger = Logger
042: .getLogger(ResponderCompile.class);
043:
044: /** @param filename path of the actual file to be compiled -- happens when
045: * we're compiling JSPs. */
046: abstract protected void respondImpl(String fileName,
047: HttpServletRequest req, HttpServletResponse res)
048: throws IOException;
049:
050: synchronized public void init(String reqName, ServletConfig config,
051: Properties prop) throws ServletException, IOException {
052: super .init(reqName, config, prop);
053:
054: // All compilation classes share cache directory and compilation manager.
055: if (!mIsInitialized) {
056:
057: mAllowRequestSOURCE = prop.getProperty(
058: "allowRequestSOURCE", "true").intern() == "true";
059: mCheckModifiedSince = prop.getProperty(
060: "checkModifiedSince", "true").intern() == "true";
061: mAllowRecompile = prop
062: .getProperty("allowRecompile", "true").intern() == "true";
063:
064: mAdminPassword = prop.getProperty("adminPassword", null);
065:
066: // Initialize the Compilation Cache
067: String cacheDir = config
068: .getInitParameter("lps.cache.directory");
069: if (cacheDir == null) {
070: cacheDir = prop.getProperty("cache.directory");
071: }
072: if (cacheDir == null) {
073: cacheDir = LPS.getWorkDirectory() + File.separator
074: + "cache";
075: }
076:
077: File cache = checkDirectory(cacheDir);
078: mLogger.info(
079: /* (non-Javadoc)
080: * @i18n.test
081: * @org-mes="application cache is at " + p[0]
082: */
083: org.openlaszlo.i18n.LaszloMessages.getMessage(
084: ResponderCompile.class.getName(), "051018-95",
085: new Object[] { cacheDir }));
086:
087: if (mCompMgr == null) {
088: mCompMgr = new CompilationManager(null, cache, prop);
089: }
090:
091: if (mScriptCache == null) {
092: String scacheDir = config
093: .getInitParameter("lps.scache.directory");
094: if (scacheDir == null) {
095: scacheDir = prop.getProperty("scache.directory");
096: }
097: if (scacheDir == null) {
098: scacheDir = LPS.getWorkDirectory() + File.separator
099: + "scache";
100: }
101: File scache = checkDirectory(scacheDir);
102: mScriptCache = ScriptCompiler.initScriptCompilerCache(
103: scache, prop);
104: }
105:
106: String cmOption = prop
107: .getProperty("compMgrDependencyOption");
108: if (cmOption != null) {
109: mLogger.debug(
110: /* (non-Javadoc)
111: * @i18n.test
112: * @org-mes="Setting cm option to \"" + p[0] + "\""
113: */
114: org.openlaszlo.i18n.LaszloMessages.getMessage(
115: ResponderCompile.class.getName(), "051018-122",
116: new Object[] { cmOption }));
117: mCompMgr.setProperty("recompile", cmOption);
118: }
119: }
120: }
121:
122: /**
123: * This method should be called by subclasses in respondImpl(req, res);
124: *
125: */
126: protected final void respondImpl(HttpServletRequest req,
127: HttpServletResponse res) throws IOException {
128: String fileName = LZHttpUtils.getRealPath(mContext, req);
129:
130: String lzt = req.getParameter("lzt");
131:
132: // FIXME: [2003-01-14 bloch] this needs to be rethought out for real!!
133: // Is this the right logic for deciding when to do preprocessing?
134: File file = new File(fileName);
135: if (!file.canRead()) {
136:
137: File base = new File(FileUtils.getBase(fileName));
138:
139: if (base.canRead() && base.isFile()) {
140: String tempFileName = doPreProcessing(req, fileName);
141: if (tempFileName != null) {
142: fileName = tempFileName;
143: }
144: }
145: }
146:
147: if (!new File(fileName).exists()) {
148: boolean isOpt = fileName.endsWith(".lzo");
149: boolean lzogz = new File(fileName + ".gz").exists();
150: if (!(isOpt && lzogz)) {
151: boolean hasFb = req.getParameter("fb") != null;
152: if (!(isOpt && hasFb)) {
153: res.sendError(HttpServletResponse.SC_NOT_FOUND, req
154: .getRequestURI()
155: + " not found");
156: mLogger.info(req.getRequestURI() + " not found");
157: return;
158: } else {
159: fileName = fileName.substring(0,
160: fileName.length() - 1) + 'x';
161: }
162: }
163: }
164:
165: try {
166: /* If it's a krank instrumented file then we always
167: * fall through and call the code to deliver the
168: * .swf to the client, regardless of the object
169: * file modification time, because that code path
170: * is where we launch the serialization
171: * port-listener thread.
172: */
173: if (mCheckModifiedSince) {
174: long lastModified = getLastModified(fileName, req);
175: // Round to the nearest second.
176: lastModified = ((lastModified + 500L) / 1000L) * 1000L;
177: if (notModified(lastModified, req, res)) {
178: return;
179: }
180: }
181:
182: respondImpl(fileName, req, res);
183:
184: // Check that global config knows about app security options
185: String path = req.getServletPath();
186: if (LPS.configuration.getApplicationOptions(path) == null) {
187: Canvas canvas = getCanvas(fileName, req);
188: LPS.configuration.setApplicationOptions(path, canvas
189: .getSecurityOptions());
190: }
191:
192: } catch (CompilationError e) {
193: handleCompilationError(e, req, res);
194: }
195: }
196:
197: protected void handleCompilationError(CompilationError e,
198: HttpServletRequest req, HttpServletResponse res)
199: throws IOException {
200: throw e;
201: }
202:
203: /**
204: * @return File name for temporary LZX file that is the
205: * result of this http pre-processing; null for a bad request
206: * @param req request
207: * @param fileName file name associated with request
208: */
209: private String doPreProcessing(HttpServletRequest req,
210: String fileName) throws IOException {
211: // Do an http request for this and see what we get back.
212: //
213: StringBuffer s = HttpUtils.getRequestURL(req);
214: int len = s.length();
215: // Remove the .lzx from the end of the URL
216: if (len <= 4) {
217: return null;
218: }
219: s.delete(len - 4, len);
220:
221: // FIXME [2002-12-15 bloch] does any/all of this need to be synchronized on session?
222:
223: // First get the temporary file name for this session
224: HttpSession session = req.getSession();
225: String sid = session.getId();
226: String tempFileName = getTempFileName(fileName, sid);
227: File tempFile = new File(tempFileName);
228:
229: tempFile.deleteOnExit();
230:
231: // Now pre-process the request and copy the data to
232: // the temporary file that is unique to this session
233:
234: // FIXME: query string processing
235:
236: String surl = s.toString();
237:
238: URL url = new URL(surl);
239: mLogger.debug(
240: /* (non-Javadoc)
241: * @i18n.test
242: * @org-mes="Preprocessing request at " + p[0]
243: */
244: org.openlaszlo.i18n.LaszloMessages.getMessage(
245: ResponderCompile.class.getName(), "051018-263",
246: new Object[] { surl }));
247: GetMethod getRequest = new LZGetMethod();
248: getRequest.setPath(url.getPath());
249: //getRequest.setQueryString(url.getQuery());
250: getRequest.setQueryString(req.getQueryString());
251:
252: // Copy headers to request
253: LZHttpUtils.proxyRequestHeaders(req, getRequest);
254: // Mention the last modified time, if the file exists
255: if (tempFile.exists()) {
256: long lastModified = tempFile.lastModified();
257: getRequest.addRequestHeader("If-Modified-Since",
258: LZHttpUtils.getDateString(lastModified));
259: } else {
260: // Otherwise, create a listener that will clean up the tempfile
261: // Note: web server administrators must make sure that their servers are killed
262: // gracefully or temporary files will not be handled by the LZBindingListener.
263: // Add a binding listener for this session that
264: // will remove our temporary files
265: LZBindingListener listener = (LZBindingListener) session
266: .getAttribute("tmpl");
267: if (listener == null) {
268: listener = new LZBindingListener(tempFileName);
269: session.setAttribute("tmpl", listener);
270: } else {
271: listener.addTempFile(tempFileName);
272: }
273: }
274:
275: HostConfiguration hostConfig = new HostConfiguration();
276: hostConfig.setHost(url.getHost(), url.getPort(), url
277: .getProtocol());
278:
279: HttpClient htc = new HttpClient();
280: htc.setHostConfiguration(hostConfig);
281:
282: int rc = htc.executeMethod(getRequest);
283: mLogger.debug("Response Status: " + rc);
284: if (rc >= 400) {
285: // respondWithError(req, res, "HTTP Status code: " + rc + " for url " + surl, rc);
286: return null;
287: }
288: if (rc != HttpServletResponse.SC_NOT_MODIFIED) {
289: FileOutputStream output = new FileOutputStream(tempFile);
290: try {
291: // FIXME:[2002-12-17 bloch] verify that the response body is XML
292: FileUtils.sendToStream(getRequest
293: .getResponseBodyAsStream(), output);
294: // TODO: [2002-12-15 bloch] What to do with response headers?
295: } catch (FileUtils.StreamWritingException e) {
296: mLogger.warn(
297: /* (non-Javadoc)
298: * @i18n.test
299: * @org-mes="StreamWritingException while sending error: " + p[0]
300: */
301: org.openlaszlo.i18n.LaszloMessages.getMessage(
302: ResponderCompile.class.getName(), "051018-313",
303: new Object[] { e.getMessage() }));
304: } finally {
305: FileUtils.close(output);
306: }
307: }
308:
309: return tempFileName;
310: }
311:
312: /**
313: * @return Name of temporary cached version of this file
314: * for this session.
315: */
316: private String getTempFileName(String fileName, String sid) {
317: String webappPath = LZHttpUtils.getRealPath(mContext, "/");
318: File source = mCompMgr.getCacheSourcePath(fileName, webappPath);
319: File cacheDir = source.getParentFile();
320: if (cacheDir != null) {
321: cacheDir.mkdirs();
322: }
323: String sourcePath = source.getAbsolutePath();
324: StringBuffer buf = new StringBuffer(sourcePath);
325: int index = sourcePath.lastIndexOf(File.separator);
326: buf.insert(index + 1, "lzf-" + sid + "-");
327: return buf.toString();
328: }
329:
330: /**
331: * Process if-modified-since req header and stick last-modified
332: * response header there.
333: */
334: private boolean notModified(long lastModified,
335: HttpServletRequest req, HttpServletResponse res)
336: throws IOException {
337: if (lastModified != 0) {
338:
339: String lms = LZHttpUtils.getDateString(lastModified);
340:
341: mLogger.debug("Last-Modified: " + lms);
342:
343: // Check last-modified and if-modified-since dates
344: String ims = req.getHeader(LZHttpUtils.IF_MODIFIED_SINCE);
345: long ifModifiedSince = LZHttpUtils.getDate(ims);
346:
347: if (ifModifiedSince != -1) {
348:
349: mLogger.debug("If-Modified-Since: " + ims);
350:
351: mLogger.debug(
352: /* (non-Javadoc)
353: * @i18n.test
354: * @org-mes="modsince " + p[0] + " lastmod " + p[1]
355: */
356: org.openlaszlo.i18n.LaszloMessages.getMessage(
357: ResponderCompile.class.getName(), "051018-370",
358: new Object[] { new Long(ifModifiedSince),
359: new Long(lastModified) }));
360: if (lastModified <= ifModifiedSince) {
361: res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
362: mLogger.info(
363: /* (non-Javadoc)
364: * @i18n.test
365: * @org-mes="Responding with NOT_MODIFIED"
366: */
367: org.openlaszlo.i18n.LaszloMessages.getMessage(
368: ResponderCompile.class.getName(),
369: "051018-379"));
370: return true;
371: }
372: }
373:
374: res.setHeader(LZHttpUtils.LAST_MODIFIED, lms);
375: }
376:
377: return false;
378: }
379:
380: /**
381: * Return the last modified time for this source file (and its dependencies)
382: *
383: * @param fileName name of file to compile
384: * @return the canvas
385: */
386: protected long getLastModified(String fileName,
387: HttpServletRequest req) throws CompilationError,
388: IOException {
389: Properties props = initCMgrProperties(req);
390: return mCompMgr.getLastModified(fileName, props);
391: }
392:
393: /**
394: * Sets up various parameters for the CompilationManager, using
395: * data from the HttpServletRequest
396: *
397: * @param req
398: *
399: *
400: * Looks for these properties:
401: * <br>
402: * <ul>
403: * <li> "debug"
404: * <li> "logdebug"
405: * <li> "lzbacktrace"
406: * <li> "profile"
407: * <li> "sourcelocators"
408: * <li> "lzr" (swf version := swf5 | swf6)
409: * <li> "lzproxied" true|false
410: * <li> "lzscript" true|false -- emit javascript, not object file
411: * <li> "cssfile"
412: * <ul>
413: * also grabs the request URL.
414: */
415: static protected Properties initCMgrProperties(
416: HttpServletRequest req) {
417:
418: Properties props = new Properties();
419:
420: // Look for "runtime=..." flag
421: String runtime = req.getParameter("lzr");
422: if (runtime == null) {
423: runtime = LPS.getProperty("compiler.runtime.default",
424: "swf6");
425: }
426: props.setProperty(CompilationEnvironment.RUNTIME_PROPERTY,
427: runtime);
428:
429: // TODO: [2003-04-11 pkang] the right way to do this is to have a
430: // separate property to see if this allows debug.
431: if (mAllowRequestSOURCE) {
432: // Look for "logdebug=true" flag
433: props.setProperty(CompilationEnvironment.LOGDEBUG_PROPERTY,
434: "false");
435: String logdebug = req
436: .getParameter(CompilationEnvironment.LOGDEBUG_PROPERTY);
437: if (logdebug != null) {
438: props.setProperty(
439: CompilationEnvironment.LOGDEBUG_PROPERTY,
440: logdebug);
441: }
442:
443: // Look for "debug=true" flag
444: props.setProperty(CompilationEnvironment.DEBUG_PROPERTY,
445: "false");
446: String debug = req
447: .getParameter(CompilationEnvironment.DEBUG_PROPERTY);
448: if (debug != null) {
449: props.setProperty(
450: CompilationEnvironment.DEBUG_PROPERTY, debug);
451: }
452:
453: // Look for "sourcelocators=true" flag
454: props.setProperty(
455: CompilationEnvironment.SOURCELOCATOR_PROPERTY,
456: "false");
457: String sl = req
458: .getParameter(CompilationEnvironment.SOURCELOCATOR_PROPERTY);
459: if (sl != null) {
460: props.setProperty(
461: CompilationEnvironment.SOURCELOCATOR_PROPERTY,
462: sl);
463: }
464:
465: // Look for "profile=true" flag
466: props.setProperty(CompilationEnvironment.PROFILE_PROPERTY,
467: "false");
468: String profile = req
469: .getParameter(CompilationEnvironment.PROFILE_PROPERTY);
470: if (profile != null) {
471: props.setProperty(
472: CompilationEnvironment.PROFILE_PROPERTY,
473: profile);
474: }
475:
476: // Look for "lzbacktrace=true" flag
477: props.setProperty(
478: CompilationEnvironment.BACKTRACE_PROPERTY, "false");
479: String backtrace = req.getParameter("lzbacktrace");
480: if (backtrace != null) {
481: props.setProperty(
482: CompilationEnvironment.BACKTRACE_PROPERTY,
483: backtrace);
484: }
485:
486: }
487:
488: // Set the 'lzproxied' default = false
489: String proxied = req
490: .getParameter(CompilationEnvironment.PROXIED_PROPERTY);
491: if (proxied != null) {
492: props.setProperty(CompilationEnvironment.PROXIED_PROPERTY,
493: proxied);
494: }
495:
496: String lzs = req.getParameter("lzscript");
497: if (lzs != null) {
498: props.setProperty("lzscript", lzs);
499: }
500:
501: if (mAllowRecompile) {
502: String recompile = req
503: .getParameter(CompilationManager.RECOMPILE);
504: if (recompile != null) {
505: // Check for passwd if required.
506: String pwd = req.getParameter("pwd");
507: if (mAdminPassword == null
508: || (pwd != null && pwd.equals(mAdminPassword))) {
509: props.setProperty(CompilationManager.RECOMPILE,
510: "true");
511: } else {
512: mLogger.warn(
513: /* (non-Javadoc)
514: * @i18n.test
515: * @org-mes="lzrecompile attempted but not allowed"
516: */
517: org.openlaszlo.i18n.LaszloMessages.getMessage(
518: ResponderCompile.class.getName(),
519: "051018-523"));
520: }
521: }
522: }
523:
524: return props;
525: }
526:
527: /**
528: * Return the canvas for the given LZX file.
529: *
530: * @param fileName pathname of file
531: * @return the canvas
532: */
533: protected Canvas getCanvas(String fileName, HttpServletRequest req)
534: throws CompilationError, IOException {
535: Properties props = initCMgrProperties(req);
536: return mCompMgr.getCanvas(fileName, props);
537: }
538:
539: public static CompilationManager getCompilationManager() {
540: return mCompMgr;
541: }
542: }
|