org.openlaszlo.servlets.responders.ResponderCompile.java Source code

Java tutorial

Introduction

Here is the source code for org.openlaszlo.servlets.responders.ResponderCompile.java

Source

/******************************************************************************
 * ResponderCompile.java
 * ****************************************************************************/

/* J_LZ_COPYRIGHT_BEGIN *******************************************************
* Copyright 2001-2011 Laszlo Systems, Inc.  All Rights Reserved.              *
* Use is subject to license terms.                                            *
* J_LZ_COPYRIGHT_END *********************************************************/

package org.openlaszlo.servlets.responders;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.openlaszlo.cm.CompilationManager;
import org.openlaszlo.compiler.Canvas;
import org.openlaszlo.compiler.CompilationEnvironment;
import org.openlaszlo.compiler.CompilationError;
import org.openlaszlo.sc.ScriptCompiler;
import org.openlaszlo.server.LPS;
import org.openlaszlo.servlets.LZBindingListener;
import org.openlaszlo.utils.FileUtils;
import org.openlaszlo.utils.LZGetMethod;
import org.openlaszlo.utils.LZHttpUtils;

public abstract class ResponderCompile extends Responder {
    protected static CompilationManager mCompMgr = null;
    protected static ScriptCompiler mScriptCache = null;

    private static boolean mIsInitialized = false;
    private static boolean mAllowRequestSOURCE = true;
    private static boolean mAllowRecompile = true;
    private static boolean mCheckModifiedSince = true;
    private static String mAdminPassword = null;
    private static Logger mLogger = Logger.getLogger(ResponderCompile.class);

    /** @param filename path of the actual file to be compiled -- happens when
     * we're compiling JSPs. */
    abstract protected void respondImpl(String fileName, HttpServletRequest req, HttpServletResponse res)
            throws IOException;

    @Override
    synchronized public void init(String reqName, ServletConfig config, Properties prop)
            throws ServletException, IOException {
        super.init(reqName, config, prop);

        // All compilation classes share cache directory and compilation manager.
        if (!mIsInitialized) {

            mAllowRequestSOURCE = prop.getProperty("allowRequestSOURCE", "true").intern() == "true";
            mCheckModifiedSince = prop.getProperty("checkModifiedSince", "true").intern() == "true";
            mAllowRecompile = prop.getProperty("allowRecompile", "true").intern() == "true";

            mAdminPassword = prop.getProperty("adminPassword", null);

            // Initialize the Compilation Cache
            String cacheDir = config.getInitParameter("lps.cache.directory");
            if (cacheDir == null) {
                cacheDir = prop.getProperty("cache.directory");
            }
            if (cacheDir == null) {
                cacheDir = LPS.getWorkDirectory() + File.separator + "cache";
            }

            File cache = checkDirectory(cacheDir);
            mLogger.info(
                    /* (non-Javadoc)
                     * @i18n.test
                     * @org-mes="application cache is at " + p[0]
                     */
                    org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(), "051018-95",
                            new Object[] { cacheDir }));

            if (mCompMgr == null) {
                mCompMgr = new CompilationManager(null, cache, prop);
                boolean clear = "true".equals(LPS.getProperty("compile.cache.clearonstartup"));
                if (clear) {
                    mCompMgr.clearCacheDirectory();
                }
            }

            if (mScriptCache == null) {
                String scacheDir = config.getInitParameter("lps.scache.directory");
                if (scacheDir == null) {
                    scacheDir = prop.getProperty("scache.directory");
                }
                if (scacheDir == null) {
                    scacheDir = LPS.getWorkDirectory() + File.separator + "scache";
                }
                File scache = checkDirectory(scacheDir);
                boolean enableScriptCache = "true".equals(LPS.getProperty("compiler.scache.enabled"));
                if (enableScriptCache) {
                    mScriptCache = ScriptCompiler.initScriptCompilerCache(scache, prop);
                }
            }

            String cmOption = prop.getProperty("compMgrDependencyOption");
            if (cmOption != null) {
                mLogger.debug(
                        /* (non-Javadoc)
                         * @i18n.test
                         * @org-mes="Setting cm option to \"" + p[0] + "\""
                         */
                        org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(),
                                "051018-122", new Object[] { cmOption }));
                mCompMgr.setProperty("recompile", cmOption);
            }
        }
    }

    /**
     * This method should be called by subclasses in respondImpl(req, res);
     *
     */
    @Override
    protected final void respondImpl(HttpServletRequest req, HttpServletResponse res) throws IOException {
        String fileName = LZHttpUtils.getRealPath(mContext, req);

        // FIXME: [2003-01-14 bloch] this needs to be rethought out for real!!
        // Is this the right logic for deciding when to do preprocessing?
        File file = new File(fileName);
        if (!file.canRead()) {

            File base = new File(FileUtils.getBase(fileName));

            if (base.canRead() && base.isFile()) {
                String tempFileName = doPreProcessing(req, fileName);
                if (tempFileName != null) {
                    fileName = tempFileName;
                }
            }
        }

        if (!new File(fileName).exists()) {
            boolean isOpt = fileName.endsWith(".lzo");
            boolean lzogz = new File(fileName + ".gz").exists();
            if (!(isOpt && lzogz)) {
                boolean hasFb = req.getParameter("fb") != null;
                if (!(isOpt && hasFb)) {
                    res.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI() + " not found");
                    mLogger.info(req.getRequestURI() + " not found");
                    return;
                } else {
                    fileName = fileName.substring(0, fileName.length() - 1) + 'x';
                }
            }
        }

        try {
            /* If it's a krank instrumented file then we always
             * fall through and call the code to deliver the
             * .swf to the client, regardless of the object
             * file modification time, because that code path
             * is where we launch the serialization
             * port-listener thread.
             */
            if (mCheckModifiedSince) {
                long lastModified = getLastModified(fileName, req);
                // Round to the nearest second.
                lastModified = ((lastModified + 500L) / 1000L) * 1000L;
                if (notModified(lastModified, req, res)) {
                    return;
                }
            }

            respondImpl(fileName, req, res);

            // Check that global config knows about app security options
            String path = req.getServletPath();
            if (LPS.configuration.getApplicationOptions(path) == null) {
                Canvas canvas = getCanvas(fileName, req);
                LPS.configuration.setApplicationOptions(path, canvas.getSecurityOptions());
            }

        } catch (CompilationError e) {
            handleCompilationError(e, req, res);
        }
    }

    protected void handleCompilationError(CompilationError e, HttpServletRequest req, HttpServletResponse res)
            throws IOException {
        throw e;
    }

    /**
     * @return File name for temporary LZX file that is the
     *         result of this http pre-processing; null for a bad request
     * @param req request
     * @param fileName file name associated with request
     */
    private String doPreProcessing(HttpServletRequest req, String fileName) throws IOException {
        // Do an http request for this and see what we get back.
        //
        StringBuffer s = req.getRequestURL();
        int len = s.length();
        // Remove the .lzx from the end of the URL
        if (len <= 4) {
            return null;
        }
        s.delete(len - 4, len);

        // FIXME [2002-12-15 bloch] does any/all of this need to be synchronized on session?

        // First get the temporary file name for this session
        HttpSession session = req.getSession();
        String sid = session.getId();
        String tempFileName = getTempFileName(fileName, sid);
        File tempFile = new File(tempFileName);

        tempFile.deleteOnExit();

        // Now pre-process the request and copy the data to
        // the temporary file that is unique to this session

        // FIXME: query string processing

        String surl = s.toString();

        URL url = new URL(surl);
        mLogger.debug(
                /* (non-Javadoc)
                 * @i18n.test
                 * @org-mes="Preprocessing request at " + p[0]
                 */
                org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(), "051018-263",
                        new Object[] { surl }));
        GetMethod getRequest = new LZGetMethod();
        getRequest.setPath(url.getPath());
        //getRequest.setQueryString(url.getQuery());
        getRequest.setQueryString(req.getQueryString());

        // Copy headers to request
        LZHttpUtils.proxyRequestHeaders(req, getRequest);
        // Mention the last modified time, if the file exists
        if (tempFile.exists()) {
            long lastModified = tempFile.lastModified();
            getRequest.addRequestHeader("If-Modified-Since", LZHttpUtils.getDateString(lastModified));
        } else {
            // Otherwise, create a listener that will clean up the tempfile
            // Note: web server administrators must make sure that their servers are killed
            // gracefully or temporary files will not be handled by the LZBindingListener.
            // Add a binding listener for this session that
            // will remove our temporary files
            LZBindingListener listener = (LZBindingListener) session.getAttribute("tmpl");
            if (listener == null) {
                listener = new LZBindingListener(tempFileName);
                session.setAttribute("tmpl", listener);
            } else {
                listener.addTempFile(tempFileName);
            }
        }

        HostConfiguration hostConfig = new HostConfiguration();
        hostConfig.setHost(url.getHost(), url.getPort(), url.getProtocol());

        HttpClient htc = new HttpClient();
        htc.setHostConfiguration(hostConfig);

        int rc = htc.executeMethod(getRequest);
        mLogger.debug("Response Status: " + rc);
        if (rc >= 400) {
            // respondWithError(req, res, "HTTP Status code: " + rc + " for url " + surl, rc);
            return null;
        }
        if (rc != HttpServletResponse.SC_NOT_MODIFIED) {
            FileOutputStream output = new FileOutputStream(tempFile);
            try {
                // FIXME:[2002-12-17 bloch] verify that the response body is XML
                FileUtils.sendToStream(getRequest.getResponseBodyAsStream(), output);
                // TODO: [2002-12-15 bloch] What to do with response headers?
            } catch (FileUtils.StreamWritingException e) {
                mLogger.warn(
                        /* (non-Javadoc)
                         * @i18n.test
                         * @org-mes="StreamWritingException while sending error: " + p[0]
                         */
                        org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(),
                                "051018-313", new Object[] { e.getMessage() }));
            } finally {
                FileUtils.close(output);
            }
        }

        return tempFileName;
    }

    /**
     * @return Name of temporary cached version of this file
     * for this session.
     */
    private String getTempFileName(String fileName, String sid) {
        String webappPath = LZHttpUtils.getRealPath(mContext, "/");
        File source = mCompMgr.getCacheSourcePath(fileName, webappPath);
        File cacheDir = source.getParentFile();
        if (cacheDir != null) {
            cacheDir.mkdirs();
        }
        String sourcePath = source.getAbsolutePath();
        StringBuilder buf = new StringBuilder(sourcePath);
        int index = sourcePath.lastIndexOf(File.separator);
        buf.insert(index + 1, "lzf-" + sid + "-");
        return buf.toString();
    }

    /**
     * Process if-modified-since req header and stick last-modified
     * response header there.
     */
    private boolean notModified(long lastModified, HttpServletRequest req, HttpServletResponse res)
            throws IOException {
        if (lastModified != 0) {

            String lms = LZHttpUtils.getDateString(lastModified);

            mLogger.debug("Last-Modified: " + lms);

            // Check last-modified and if-modified-since dates
            String ims = req.getHeader(LZHttpUtils.IF_MODIFIED_SINCE);
            long ifModifiedSince = LZHttpUtils.getDate(ims);

            if (ifModifiedSince != -1) {

                mLogger.debug("If-Modified-Since: " + ims);

                mLogger.debug(
                        /* (non-Javadoc)
                         * @i18n.test
                         * @org-mes="modsince " + p[0] + " lastmod " + p[1]
                         */
                        org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(),
                                "051018-370", new Object[] { ifModifiedSince, lastModified }));
                if (lastModified <= ifModifiedSince) {
                    res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                    mLogger.info(
                            /* (non-Javadoc)
                             * @i18n.test
                             * @org-mes="Responding with NOT_MODIFIED"
                             */
                            org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(),
                                    "051018-379"));
                    return true;
                }
            }

            res.setHeader(LZHttpUtils.LAST_MODIFIED, lms);
        }

        return false;
    }

    /**
     * Return the last modified time for this source file (and its dependencies)
     *
     * @param fileName name of file to compile
     * @return the canvas
     */
    protected long getLastModified(String fileName, HttpServletRequest req) throws CompilationError, IOException {
        Properties props = initCMgrProperties(req);
        return mCompMgr.getLastModified(fileName, props);
    }

    /**
     * Sets up various parameters for the CompilationManager, using
     * data from the HttpServletRequest
     *
     * @param req
     *
     *
     * Looks for these properties:
     * <br>
     * <ul>
     * <li> "debug"
     * <li> "logdebug"
     * <li> "backtrace"
     * <li> "profile"
     * <li> "sourcelocators"
     * <li> "lzsourceannotations"
     * <li> "lzr" (swf version := swf5 | swf6)
     * <li> "proxied" true|false
     * <li> "lzscript" true|false   -- emit javascript, not object file
     * <li> "lzconsoledebug" use remote debug protocol
     * <li> "cssfile"
     * <li> "lzcopyresources" -- dhtml compilation should make local copy of external resources
     * <li> "flexoptions" -- options are "version=[10.1|10.0]", "air"
     * <li> "lzoptions" -- new style of passing compiler options
        
     * <ul>
     * also grabs the request URL.
     */
    static protected Properties initCMgrProperties(HttpServletRequest req) {

        Properties props = new Properties();

        // Look for "runtime=..." flag
        String runtime = LZHttpUtils.getLzOption("runtime", req);
        if (runtime == null) {
            runtime = LPS.getProperty("compiler.runtime.default", "swf8");
        }
        props.setProperty(CompilationEnvironment.RUNTIME_PROPERTY, runtime);

        props.setProperty(CompilationEnvironment.FLEX_OPTIONS, "");
        String flexOptions = LZHttpUtils.getLzOption(CompilationEnvironment.FLEX_OPTIONS, req);
        if (flexOptions != null) {
            mLogger.info("flexOptions = " + flexOptions);
            props.setProperty(CompilationEnvironment.FLEX_OPTIONS, flexOptions);
        }

        // TODO: [2003-04-11 pkang] the right way to do this is to have a
        // separate property to see if this allows debug.
        if (mAllowRequestSOURCE) {
            // Look for "logdebug=true" flag
            props.setProperty(CompilationEnvironment.LOGDEBUG_PROPERTY, "false");
            String logdebug = LZHttpUtils.getLzOption(CompilationEnvironment.LOGDEBUG_PROPERTY, req);
            if (logdebug != null) {
                props.setProperty(CompilationEnvironment.LOGDEBUG_PROPERTY, logdebug);
            }

            // Look for "lzconsoledebug=true" flag
            props.setProperty(CompilationEnvironment.CONSOLEDEBUG_PROPERTY, "false");
            String lzconsoledebug = LZHttpUtils.getLzOption(CompilationEnvironment.CONSOLEDEBUG_PROPERTY, req);
            if (lzconsoledebug != null) {
                props.setProperty(CompilationEnvironment.CONSOLEDEBUG_PROPERTY, lzconsoledebug);
            }

            // Look for "debug=true" flag
            props.setProperty(CompilationEnvironment.DEBUG_PROPERTY, "false");
            String debug = LZHttpUtils.getLzOption(CompilationEnvironment.DEBUG_PROPERTY, req);
            if (debug != null) {
                props.setProperty(CompilationEnvironment.DEBUG_PROPERTY, debug);
            }

            // Look for "sourcelocators=true" flag
            props.setProperty(CompilationEnvironment.SOURCELOCATOR_PROPERTY, "false");
            String sl = LZHttpUtils.getLzOption(CompilationEnvironment.SOURCELOCATOR_PROPERTY, req);
            if (sl != null) {
                props.setProperty(CompilationEnvironment.SOURCELOCATOR_PROPERTY, sl);
            }

            // Look for "profile=true" flag
            props.setProperty(CompilationEnvironment.PROFILE_PROPERTY, "false");
            String profile = LZHttpUtils.getLzOption(CompilationEnvironment.PROFILE_PROPERTY, req);
            if (profile != null) {
                props.setProperty(CompilationEnvironment.PROFILE_PROPERTY, profile);
            }

            // Look for "backtrace=true" flag
            props.setProperty(CompilationEnvironment.BACKTRACE_PROPERTY, "false");
            String backtrace = LZHttpUtils.getLzOption("backtrace", req);
            if (backtrace != null) {
                props.setProperty(CompilationEnvironment.BACKTRACE_PROPERTY, backtrace);
            }

            // Look for "lzsourceannotations=true" flag
            props.setProperty(CompilationEnvironment.SOURCE_ANNOTATIONS_PROPERTY, "false");
            String srcann = LZHttpUtils.getLzOption(CompilationEnvironment.SOURCE_ANNOTATIONS_PROPERTY, req);
            if (srcann != null) {
                props.setProperty(CompilationEnvironment.SOURCE_ANNOTATIONS_PROPERTY, srcann);
            }

            // Look for "lzcopyresources=true" flag
            props.setProperty(CompilationEnvironment.COPY_RESOURCES_LOCAL, "false");
            String lzcopyresources = LZHttpUtils.getLzOption(CompilationEnvironment.COPY_RESOURCES_LOCAL, req);
            if (lzcopyresources != null) {
                mLogger.info("lzcopyresources = " + lzcopyresources);
                props.setProperty(CompilationEnvironment.COPY_RESOURCES_LOCAL, lzcopyresources);
            }

            // Look for "incremental=true" flag
            props.setProperty(CompilationEnvironment.INCREMENTAL_MODE, "false");
            String inc = LZHttpUtils.getLzOption("incremental", req);
            if (inc != null) {
                props.setProperty(CompilationEnvironment.INCREMENTAL_MODE, inc);
            }
        }

        // Set the 'proxied' default = false
        String proxied = LZHttpUtils.getLzOption(CompilationEnvironment.PROXIED_PROPERTY, req);
        if (proxied != null) {
            props.setProperty(CompilationEnvironment.PROXIED_PROPERTY, proxied);
        }

        String lzs = LZHttpUtils.getLzOption("lzscript", req);
        if (lzs != null) {
            props.setProperty("lzscript", lzs);
        }

        if (mAllowRecompile) {
            String recompile = LZHttpUtils.getLzOption(CompilationManager.RECOMPILE, req);
            if (recompile != null) {
                // Check for passwd if required.
                String pwd = LZHttpUtils.getLzOption("pwd", req);
                if (mAdminPassword == null || (pwd != null && pwd.equals(mAdminPassword))) {
                    props.setProperty(CompilationManager.RECOMPILE, "true");
                } else {
                    mLogger.warn(
                            /* (non-Javadoc)
                             * @i18n.test
                             * @org-mes="lzrecompile attempted but not allowed"
                             */
                            org.openlaszlo.i18n.LaszloMessages.getMessage(ResponderCompile.class.getName(),
                                    "051018-523"));
                }
            }
        }

        return props;
    }

    /**
     * Return the canvas for the given LZX file.
     *
     * @param fileName pathname of file
     * @return the canvas
     */
    protected Canvas getCanvas(String fileName, HttpServletRequest req) throws CompilationError, IOException {
        Properties props = initCMgrProperties(req);
        return mCompMgr.getCanvas(fileName, props);
    }

    public static CompilationManager getCompilationManager() {
        return mCompMgr;
    }
}