Package org.jaggeryjs.jaggery.core.manager

Source Code of org.jaggeryjs.jaggery.core.manager.WebAppManager

package org.jaggeryjs.jaggery.core.manager;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaggeryjs.hostobjects.file.FileHostObject;
import org.jaggeryjs.hostobjects.log.LogHostObject;
import org.jaggeryjs.hostobjects.web.Constants;
import org.jaggeryjs.jaggery.core.JaggeryCoreConstants;
import org.jaggeryjs.jaggery.core.ScriptReader;
import org.jaggeryjs.jaggery.core.plugins.WebAppFileManager;
import org.jaggeryjs.scriptengine.EngineConstants;
import org.jaggeryjs.scriptengine.cache.ScriptCachingContext;
import org.jaggeryjs.scriptengine.engine.JaggeryContext;
import org.jaggeryjs.scriptengine.engine.JavaScriptProperty;
import org.jaggeryjs.scriptengine.engine.RhinoEngine;
import org.jaggeryjs.scriptengine.engine.RhinoTopLevel;
import org.jaggeryjs.scriptengine.exceptions.ScriptException;
import org.jaggeryjs.scriptengine.security.RhinoSecurityController;
import org.jaggeryjs.scriptengine.security.RhinoSecurityDomain;
import org.jaggeryjs.scriptengine.util.HostObjectUtil;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.mozilla.javascript.*;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.*;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.PermissionCollection;
import java.util.*;

public class WebAppManager {

    private static final Log log = LogFactory.getLog(WebAppManager.class);

    public static final String CORE_MODULE_NAME = "core";

    public static final String SERVLET_RESPONSE = "webappmanager.servlet.response";

    public static final String SERVLET_REQUEST = "webappmanager.servlet.request";

    private static final String DEFAULT_CONTENT_TYPE = "text/html";

    private static final String DEFAULT_CHAR_ENCODING = "UTF-8";

    public static final String JAGGERY_MODULES_DIR = "modules";

    public static final String WS_REQUEST_PATH = "requestURI";

    public static final String WS_SERVLET_CONTEXT = "/websocket";

    private static final String SHARED_JAGGERY_CONTEXT = "shared.jaggery.context";

    private static final String SERVE_FUNCTION_JAGGERY = "org.jaggeryjs.serveFunction";

    private static final Map<String, List<String>> timeouts = new HashMap<String, List<String>>();

    private static final Map<String, List<String>> intervals = new HashMap<String, List<String>>();

    private static final Map<String, Object> locks = new HashMap<String, Object>();

    private static boolean isWebSocket = false;

    static {
        try {

            String jaggeryDir = System.getProperty("jaggery.home");
            if (jaggeryDir == null) {
                jaggeryDir = System.getProperty("carbon.home");
            }

            if (jaggeryDir == null) {
                log.error("Unable to find jaggery.home or carbon.home system properties");
            }

            String modulesDir = jaggeryDir + File.separator + JAGGERY_MODULES_DIR;

            CommonManager.getInstance().initialize(modulesDir, new RhinoSecurityController() {
                @Override
                protected void updatePermissions(PermissionCollection permissions, RhinoSecurityDomain securityDomain) {
                    JaggerySecurityDomain domain = (JaggerySecurityDomain) securityDomain;
                    ServletContext context = domain.getServletContext();
                    String docBase = context.getRealPath("/");
                    // Create a file read permission for web app context directory
                    if (!docBase.endsWith(File.separator)) {
                        permissions.add(new FilePermission(docBase, "read"));
                        docBase = docBase + File.separator;
                    } else {
                        permissions.add(new FilePermission(docBase.substring(0, docBase.length() - 1), "read"));
                    }
                    docBase = docBase + "-";
                    permissions.add(new FilePermission(docBase, "read"));
                }
            });
        } catch (ScriptException e) {
            log.error(e.getMessage(), e);
        }
    }

    public static RhinoEngine getEngine() throws ScriptException {
        return CommonManager.getInstance().getEngine();
    }

    public static void include(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        String functionName = "include";
        int argsCount = args.length;
        if (argsCount != 1 && argsCount != 2) {
            HostObjectUtil.invalidNumberOfArgs(CommonManager.HOST_OBJECT_NAME, functionName, argsCount, false);
        }
        if (!(args[0] instanceof String)) {
            HostObjectUtil.invalidArgsError(CommonManager.HOST_OBJECT_NAME, functionName, "1", "string", args[0], false);
        }

        JaggeryContext jaggeryContext = CommonManager.getJaggeryContext();
        Stack<String> includesCallstack = CommonManager.getCallstack(jaggeryContext);
        String parent = includesCallstack.lastElement();
        String fileURL = (String) args[0];

        if (CommonManager.isHTTP(fileURL) || CommonManager.isHTTP(parent)) {
            CommonManager.include(cx, thisObj, args, funObj);
            return;
        }
        if (argsCount == 2 && !(args[1] instanceof ScriptableObject)) {
            HostObjectUtil.invalidArgsError(CommonManager.HOST_OBJECT_NAME, functionName, "2", "Object", args[1], false);
        }
        ScriptableObject scope;
        if (argsCount == 2) {
            scope = (ScriptableObject) args[1];
        } else {
            scope = jaggeryContext.getScope();
        }
        executeScript(jaggeryContext, scope, fileURL, false, false, false);
    }

    public static void include_once(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        String functionName = "include_once";
        int argsCount = args.length;
        if (argsCount != 1 && argsCount != 2) {
            HostObjectUtil.invalidNumberOfArgs(CommonManager.HOST_OBJECT_NAME, functionName, argsCount, false);
        }
        if (!(args[0] instanceof String)) {
            HostObjectUtil.invalidArgsError(CommonManager.HOST_OBJECT_NAME, functionName, "1", "string", args[0], false);
        }

        JaggeryContext jaggeryContext = CommonManager.getJaggeryContext();
        Stack<String> includesCallstack = CommonManager.getCallstack(jaggeryContext);
        String parent = includesCallstack.lastElement();
        String fileURL = (String) args[0];

        if (CommonManager.isHTTP(fileURL) || CommonManager.isHTTP(parent)) {
            CommonManager.include_once(cx, thisObj, args, funObj);
            return;
        }
        if (argsCount == 2 && !(args[1] instanceof ScriptableObject)) {
            HostObjectUtil.invalidArgsError(CommonManager.HOST_OBJECT_NAME, functionName, "2", "Object", args[1], false);
        }
        ScriptableObject scope;
        if (argsCount == 2) {
            scope = (ScriptableObject) args[1];
        } else {
            scope = jaggeryContext.getScope();
        }
        executeScript(jaggeryContext, scope, fileURL, false, false, true);
    }

    /**
     * JaggeryMethod responsible of writing to the output stream
     */
    public static void print(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        if (!isWebSocket) {
            JaggeryContext jaggeryContext = CommonManager.getJaggeryContext();

            //If the script itself havent set the content type we set the default content type to be text/html
            HttpServletResponse servletResponse = (HttpServletResponse) jaggeryContext.getProperty(SERVLET_RESPONSE);

            //servletResponse can be null if the script is executed as a initScripts, destroyScripts or
            //a sessionDestroyedListeners
            if (servletResponse != null) {
                if (servletResponse.getContentType() == null) {
                    servletResponse.setContentType(DEFAULT_CONTENT_TYPE);
                }

                if (servletResponse.getCharacterEncoding() == null) {
                    servletResponse.setCharacterEncoding(DEFAULT_CHAR_ENCODING);
                }

                CommonManager.print(cx, thisObj, args, funObj);
            }
        }
    }

    public static String setTimeout(final Context cx, final Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        JaggeryContext context = CommonManager.getJaggeryContext();
        ServletContext servletContext = (ServletContext) context.getProperty(Constants.SERVLET_CONTEXT);
        String contextPath = servletContext.getContextPath();
        String taskId = RhinoTopLevel.setTimeout(cx, thisObj, args, funObj);
        List<String> taskIds = timeouts.get(contextPath);
        if (taskIds == null) {
            taskIds = new ArrayList<String>();
            timeouts.put(contextPath, taskIds);
        }
        taskIds.add(taskId);
        return taskId;
    }

    public static String setInterval(final Context cx, final Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        JaggeryContext context = CommonManager.getJaggeryContext();
        ServletContext servletContext = (ServletContext) context.getProperty(Constants.SERVLET_CONTEXT);
        String contextPath = servletContext.getContextPath();
        String taskId = RhinoTopLevel.setInterval(cx, thisObj, args, funObj);
        List<String> taskIds = intervals.get(contextPath);
        if (taskIds == null) {
            taskIds = new ArrayList<String>();
            intervals.put(contextPath, taskIds);
        }
        taskIds.add(taskId);
        return taskId;
    }

    public static void clearTimeout(final Context cx, final Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        JaggeryContext context = CommonManager.getJaggeryContext();
        ServletContext servletContext = (ServletContext) context.getProperty(Constants.SERVLET_CONTEXT);
        String contextPath = servletContext.getContextPath();
        RhinoTopLevel.clearTimeout(cx, thisObj, args, funObj);
        List<String> taskIds = timeouts.get(contextPath);
        taskIds.remove(String.valueOf(args[0]));
    }

    public static void clearInterval(final Context cx, final Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        JaggeryContext context = CommonManager.getJaggeryContext();
        ServletContext servletContext = (ServletContext) context.getProperty(Constants.SERVLET_CONTEXT);
        String contextPath = servletContext.getContextPath();
        RhinoTopLevel.clearTimeout(cx, thisObj, args, funObj);
        List<String> taskIds = intervals.get(contextPath);
        taskIds.remove(String.valueOf(args[0]));
    }

    private static ScriptableObject executeScript(JaggeryContext jaggeryContext, ScriptableObject scope,
                                                  String fileURL, final boolean isJSON, boolean isBuilt,
                                                  boolean isIncludeOnce) throws ScriptException {
        Stack<String> includesCallstack = CommonManager.getCallstack(jaggeryContext);
        Map<String, Boolean> includedScripts = CommonManager.getIncludes(jaggeryContext);
        ServletContext context = (ServletContext) jaggeryContext.getProperty(Constants.SERVLET_CONTEXT);
        String parent = includesCallstack.lastElement();

        String keys[] = WebAppManager.getKeys(context.getContextPath(), parent, fileURL);
        fileURL = getNormalizedScriptPath(keys);
        if (includesCallstack.search(fileURL) != -1) {
            return scope;
        }
        if (isIncludeOnce && includedScripts.get(fileURL) != null) {
            return scope;
        }

        ScriptReader source;
        RhinoEngine engine = jaggeryContext.getEngine();
        if (isBuilt) {
            source = new ScriptReader(context.getResourceAsStream(fileURL)) {
                @Override
                protected void build() throws IOException {
                    try {
                        if (isJSON) {
                            sourceReader = new StringReader("(" + HostObjectUtil.streamToString(sourceIn) + ")");
                        } else {
                            sourceReader = new StringReader(HostObjectUtil.streamToString(sourceIn));
                        }
                    } catch (ScriptException e) {
                        throw new IOException(e);
                    }
                }
            };
        } else {
            source = new ScriptReader(context.getResourceAsStream(fileURL));
        }

        ScriptCachingContext sctx = new ScriptCachingContext(jaggeryContext.getTenantId(), keys[0], keys[1], keys[2]);
        sctx.setSecurityDomain(new JaggerySecurityDomain(fileURL, context));
        long lastModified = WebAppManager.getScriptLastModified(context, fileURL);
        sctx.setSourceModifiedTime(lastModified);

        includedScripts.put(fileURL, true);
        includesCallstack.push(fileURL);
        if (isJSON) {
            scope = (ScriptableObject) engine.eval(source, scope, sctx);
        } else {
            engine.exec(source, scope, sctx);
        }
        includesCallstack.pop();
        return scope;
    }

    private static String getNormalizedScriptPath(String[] keys) {
        return "/".equals(keys[1]) ? keys[2] : keys[1] + keys[2];
    }

    public static ScriptableObject require(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException, IOException {
        String functionName = "require";
        int argsCount = args.length;
        if (argsCount != 1) {
            HostObjectUtil.invalidNumberOfArgs(CommonManager.HOST_OBJECT_NAME, functionName, argsCount, false);
        }
        if (!(args[0] instanceof String)) {
            HostObjectUtil.invalidArgsError(CommonManager.HOST_OBJECT_NAME, functionName, "1", "string", args[0], false);
        }

        String moduleId = (String) args[0];
        int dotIndex = moduleId.lastIndexOf(".");
        if (moduleId.length() == dotIndex + 1) {
            String msg = "Invalid file path for require method : " + moduleId;
            log.error(msg);
            throw new ScriptException(msg);
        }

        JaggeryContext jaggeryContext = CommonManager.getJaggeryContext();
        Map<String, ScriptableObject> requiredModules = (Map<String, ScriptableObject>) jaggeryContext.getProperty(
                Constants.JAGGERY_REQUIRED_MODULES);
        ScriptableObject object = requiredModules.get(moduleId);
        if (object != null) {
            return object;
        }

        if (dotIndex == -1) {
            object = CommonManager.require(cx, thisObj, args, funObj);
            initModule(cx, jaggeryContext, moduleId, object);
        } else {
            object = (ScriptableObject) cx.newObject(thisObj);
            object.setPrototype(thisObj);
            object.setParentScope(thisObj);
            //sharedJaggeryContext((ServletContext) jaggeryContext.getProperty(Constants.SERVLET_CONTEXT)).getScope()
            //object.setParentScope(sharedJaggeryContext((ServletContext) jaggeryContext.getProperty(Constants.SERVLET_CONTEXT)).getScope());
            String ext = moduleId.substring(dotIndex + 1);
            if (ext.equalsIgnoreCase("json")) {
                object = executeScript(jaggeryContext, object, moduleId, true, true, false);
            } else if (ext.equalsIgnoreCase("js")) {
                object = executeScript(jaggeryContext, object, moduleId, false, true, false);
            } else if (ext.equalsIgnoreCase("jag")) {
                object = executeScript(jaggeryContext, object, moduleId, false, false, false);
            } else {
                String msg = "Unsupported file type for require() method : ." + ext;
                log.error(msg);
                throw new ScriptException(msg);
            }
        }
        requiredModules.put(moduleId, object);
        return object;
    }

    /**
     * The sync function creates a synchronized function (in the sense
     * of a Java synchronized method) from an existing function. The
     * new function synchronizes on the the second argument if it is
     * defined, or otherwise the <code>this</code>
     */
    public static Object sync(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        String functionName = "sync";
        int argsCount = args.length;
        if (argsCount <= 1 && argsCount >= 2) {
            HostObjectUtil.invalidNumberOfArgs(EngineConstants.GLOBAL_OBJECT_NAME,
                    functionName, argsCount, false);
        }
        if (!(args[0] instanceof Function)) {
            HostObjectUtil.invalidArgsError(EngineConstants.GLOBAL_OBJECT_NAME,
                    EngineConstants.GLOBAL_OBJECT_NAME, "1", "function", args[0], false);
        }
        Object lock = null;
        if (argsCount == 2) {
            if (args[1] == Undefined.instance) {
                HostObjectUtil.invalidArgsError(EngineConstants.GLOBAL_OBJECT_NAME,
                        EngineConstants.GLOBAL_OBJECT_NAME, "1", "object", args[0], false);
            }
            if (args[0] instanceof String) {
                String key = (String) args[0];
                lock = locks.get(key);
                if (lock == null) {
                    lock = new Object();
                    locks.put(key, lock);
                }
            } else {
                lock = args[1];
            }
        }
        return new Synchronizer((Function) args[0], lock);
    }

    public static void initModule(Context cx, JaggeryContext context, String module, ScriptableObject object) {
        if (CORE_MODULE_NAME.equals(module)) {
            defineProperties(cx, context, object);
        }
    }

    public static JaggeryContext sharedJaggeryContext(ServletContext ctx) {
        return (JaggeryContext) ctx.getAttribute(SHARED_JAGGERY_CONTEXT);
    }

    public static JaggeryContext clonedJaggeryContext(ServletContext context) {
        JaggeryContext shared = sharedJaggeryContext(context);
        RhinoEngine engine = shared.getEngine();
        Scriptable sharedScope = shared.getScope();
        Context cx = Context.getCurrentContext();
        ScriptableObject instanceScope = (ScriptableObject) cx.newObject(sharedScope);
        instanceScope.setPrototype(sharedScope);
        instanceScope.setParentScope(null);

        JaggeryContext clone = new JaggeryContext();
        clone.setEngine(engine);
        clone.setTenantId(shared.getTenantId());
        clone.setScope(instanceScope);

        clone.addProperty(Constants.SERVLET_CONTEXT, shared.getProperty(Constants.SERVLET_CONTEXT));
        clone.addProperty(LogHostObject.LOG_LEVEL, shared.getProperty(LogHostObject.LOG_LEVEL));
        clone.addProperty(FileHostObject.JAVASCRIPT_FILE_MANAGER,
                shared.getProperty(FileHostObject.JAVASCRIPT_FILE_MANAGER));
        clone.addProperty(Constants.JAGGERY_CORE_MANAGER, shared.getProperty(Constants.JAGGERY_CORE_MANAGER));
        clone.addProperty(Constants.JAGGERY_INCLUDED_SCRIPTS, new HashMap<String, Boolean>());
        clone.addProperty(Constants.JAGGERY_INCLUDES_CALLSTACK, new Stack<String>());
        clone.addProperty(Constants.JAGGERY_REQUIRED_MODULES, new HashMap<String, ScriptableObject>());

        return clone;
    }

    public static void deploy(org.apache.catalina.Context context) throws ScriptException {
        ServletContext ctx = context.getServletContext();
        JaggeryContext sharedContext = new JaggeryContext();
        Context cx = Context.getCurrentContext();
        CommonManager.initContext(sharedContext);

        sharedContext.addProperty(Constants.SERVLET_CONTEXT, ctx);
        sharedContext.addProperty(FileHostObject.JAVASCRIPT_FILE_MANAGER, new WebAppFileManager(ctx));
        sharedContext.addProperty(Constants.JAGGERY_REQUIRED_MODULES, new HashMap<String, ScriptableObject>());
        String logLevel = (String) ctx.getAttribute(LogHostObject.LOG_LEVEL);
        if (logLevel != null) {
            sharedContext.addProperty(LogHostObject.LOG_LEVEL, logLevel);
        }
        ScriptableObject sharedScope = sharedContext.getScope();
        JavaScriptProperty application = new JavaScriptProperty("application");
        application.setValue(cx.newObject(sharedScope, "Application", new Object[]{ctx}));
        application.setAttribute(ScriptableObject.READONLY);
        RhinoEngine.defineProperty(sharedScope, application);
        ctx.setAttribute(SHARED_JAGGERY_CONTEXT, sharedContext);
    }

    public static void undeploy(org.apache.catalina.Context context) {
        String contextPath = context.getServletContext().getContextPath();
        List<String> taskIds = timeouts.get(contextPath);
        if (taskIds != null) {
            for (String taskId : taskIds) {
                try {
                    log.debug("clearTimeout : " + taskId);
                    RhinoTopLevel.clearTimeout(taskId);
                } catch (ScriptException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        taskIds = intervals.get(contextPath);
        if (taskIds != null) {
            for (String taskId : taskIds) {
                try {
                    log.debug("clearInterval : " + taskId);
                    RhinoTopLevel.clearInterval(taskId);
                } catch (ScriptException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }
        log.debug("Releasing resources of : " + context.getServletContext().getContextPath());
    }

    public static void execute(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        InputStream sourceIn;
        Context cx;
        JaggeryContext context;
        RhinoEngine engine = null;
        ServletContext servletContext = request.getServletContext();

        try {
            engine = CommonManager.getInstance().getEngine();
            cx = engine.enterContext();
            String scriptPath = getScriptPath(request);
            OutputStream out = response.getOutputStream();
            context = createJaggeryContext(cx, out, scriptPath, request, response);
            context.addProperty(FileHostObject.JAVASCRIPT_FILE_MANAGER,
                    new WebAppFileManager(request.getServletContext()));

            if (ModuleManager.isModuleRefreshEnabled()) {
                //reload init scripts
                InputStream jaggeryConf = servletContext.getResourceAsStream(JaggeryCoreConstants.JAGGERY_CONF_FILE);
                if (jaggeryConf != null) {
                    StringWriter writer = new StringWriter();
                    IOUtils.copy(jaggeryConf, writer, null);
                    String jsonString = writer.toString();
                    JSONObject conf = (JSONObject) JSONValue.parse(jsonString);
                    JSONArray initScripts = (JSONArray) conf.get(JaggeryCoreConstants.JaggeryConfigParams.INIT_SCRIPTS);
                    if (initScripts != null) {
                        JaggeryContext sharedContext = WebAppManager.sharedJaggeryContext(servletContext);
                        ScriptableObject sharedScope = sharedContext.getScope();

                        Object[] scripts = initScripts.toArray();
                        for (Object script : scripts) {
                            if (!(script instanceof String)) {
                                log.error("Invalid value for initScripts in jaggery.conf : " + script);
                                continue;
                            }
                            String path = (String) script;
                            path = path.startsWith("/") ? path : "/" + path;
                            Stack<String> callstack = CommonManager.getCallstack(sharedContext);
                            callstack.push(path);
                            engine.exec(new ScriptReader(servletContext.getResourceAsStream(path)) {
                                @Override
                                protected void build() throws IOException {
                                    try {
                                        sourceReader = new StringReader(HostObjectUtil.streamToString(sourceIn));
                                    } catch (ScriptException e) {
                                        throw new IOException(e);
                                    }
                                }
                            }, sharedScope, null);
                            callstack.pop();
                        }
                    }
                }
            }

            Object serveFunction = request.getServletContext().getAttribute(SERVE_FUNCTION_JAGGERY);
            if (serveFunction != null) {
                Function function = (Function) serveFunction;
                ScriptableObject scope = context.getScope();
                function.call(cx, scope, scope, new Object[]{
                        scope.get("request", scope),
                        scope.get("response", scope),
                        scope.get("session", scope)
                });
            } else {
                //resource rendering model proceeding
                sourceIn = request.getServletContext().getResourceAsStream(scriptPath);
                if (sourceIn == null) {
                    response.sendError(HttpServletResponse.SC_NOT_FOUND, request.getRequestURI());
                    return;
                }
                CommonManager.getInstance().getEngine()
                        .exec(new ScriptReader(sourceIn), context.getScope(),
                                getScriptCachingContext(request, scriptPath));
            }
        } catch (ScriptException e) {
            String msg = e.getMessage();
            log.error(msg, e);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    msg);
        } catch (Error e) {
            //Rhino doesn't catch Error instances and it is been used to stop the script execution
            //from any specific place. Hence, Error exception propagates up to Java and we silently
            //ignore it, assuming it was initiated by an exit() method call.
            log.debug("Script has called exit() method", e);
        } finally {
            // Exiting from the context
            if (engine != null) {
                RhinoEngine.exitContext();
            }
        }
    }

    public static String getScriptPath(HttpServletRequest request) {
        String url = request.getServletPath();
        Map<String, Object> urlMappings = (Map<String, Object>) request.getServletContext()
                .getAttribute(CommonManager.JAGGERY_URLS_MAP);
        if (urlMappings == null) {
            return url;
        }
        String path;
        if (url.equals("/")) {
            path = getPath(urlMappings, url);
        } else {
            path = resolveScriptPath(new ArrayList<String>(Arrays.asList(url.substring(1).split("/", -1))), urlMappings);
        }
        return path == null ? url : path;
    }

    private static String resolveScriptPath(List<String> parts, Map<String, Object> map) {
        String part = parts.remove(0);
        if (parts.isEmpty()) {
            return getPath(map, part);
        }
        Object obj = map.get(part);
        if (obj == null) {
            return getPath(map, "/");
        }
        if (obj instanceof Map) {
            return resolveScriptPath(parts, (Map<String, Object>) obj);
        }
        return null;
    }

    private static String getPath(Map<String, Object> map, String part) {
        Object obj = "/".equals(part) ? null : map.get(part);
        if (obj == null) {
            obj = map.get("/");
            if (obj != null) {
                return (String) obj;
            }
            obj = map.get("*");
            String path = (String) obj;
            if (path != null && path.endsWith("*")) {
                return path.substring(0, path.length() - 1) + part;
            } else {
                return path;
            }
        }
        if (obj instanceof String) {
            return (String) obj;
        }
        map = (Map<String, Object>) obj;
        obj = map.get("/");
        if (obj != null) {
            return (String) obj;
        }
        obj = map.get("*");
        return (String) obj;
    }

    private static void defineProperties(Context cx, JaggeryContext context, ScriptableObject scope) {

        JavaScriptProperty request = new JavaScriptProperty("request");
        HttpServletRequest servletRequest = (HttpServletRequest) context.getProperty(SERVLET_REQUEST);
        request.setValue(cx.newObject(scope, "Request", new Object[]{servletRequest}));
        request.setAttribute(ScriptableObject.READONLY);
        RhinoEngine.defineProperty(scope, request);

        JavaScriptProperty response = new JavaScriptProperty("response");
        HttpServletResponse servletResponse = (HttpServletResponse) context.getProperty(SERVLET_RESPONSE);
        response.setValue(cx.newObject(scope, "Response", new Object[]{servletResponse}));
        response.setAttribute(ScriptableObject.READONLY);
        RhinoEngine.defineProperty(scope, response);

        JavaScriptProperty session = new JavaScriptProperty("session");
        session.setValue(cx.newObject(scope, "Session", new Object[]{servletRequest.getSession()}));
        session.setAttribute(ScriptableObject.READONLY);
        RhinoEngine.defineProperty(scope, session);

        JavaScriptProperty application = new JavaScriptProperty("application");
        ServletContext servletConext = (ServletContext) context.getProperty(Constants.SERVLET_CONTEXT);
        application.setValue(cx.newObject(scope, "Application", new Object[]{servletConext}));
        application.setAttribute(ScriptableObject.READONLY);
        RhinoEngine.defineProperty(scope, application);

        if (isWebSocket(servletRequest)) {
            JavaScriptProperty websocket = new JavaScriptProperty("webSocket");
            websocket.setValue(cx.newObject(scope, "WebSocket", new Object[0]));
            websocket.setAttribute(ScriptableObject.READONLY);
            RhinoEngine.defineProperty(scope, websocket);
        }

    }

    private static JaggeryContext createJaggeryContext(Context cx, OutputStream out, String scriptPath,
                                                       HttpServletRequest request, HttpServletResponse response) {
        ServletContext servletContext = request.getServletContext();
        JaggeryContext context = clonedJaggeryContext(servletContext);
        CommonManager.setJaggeryContext(context);
        context.addProperty(SERVLET_REQUEST, request);
        context.addProperty(SERVLET_RESPONSE, response);
        CommonManager.getCallstack(context).push(scriptPath);
        CommonManager.getIncludes(context).put(scriptPath, true);
        context.addProperty(CommonManager.JAGGERY_OUTPUT_STREAM, out);
        defineProperties(cx, context, context.getScope());
        return context;
    }

    protected static ScriptCachingContext getScriptCachingContext(HttpServletRequest request, String scriptPath)
            throws ScriptException {
        JaggeryContext jaggeryContext = CommonManager.getJaggeryContext();
        String tenantId = jaggeryContext.getTenantId();
        String[] parts = getKeys(request.getContextPath(), scriptPath, scriptPath);
        /**
         * tenantId = tenantId
         * context = webapp context
         * path = relative path to the directory of *.js file
         * cacheKey = name of the *.js file being cached
         */
        ScriptCachingContext sctx = new ScriptCachingContext(tenantId, parts[0], parts[1], parts[2]);
        ServletContext servletContext = request.getServletContext();
        sctx.setSecurityDomain(new JaggerySecurityDomain(getNormalizedScriptPath(parts), servletContext));
        long lastModified = getScriptLastModified(servletContext, scriptPath);
        sctx.setSourceModifiedTime(lastModified);
        return sctx;
    }

    /**
     * @param context    in the form of /foo
     * @param parent     in the form of /foo/bar/ or /foo/bar/dar.jss
     * @param scriptPath in the form of /foo/bar/mar.jss or bar/mar.jss
     * @return String[] with keys
     */
    public static String[] getKeys(String context, String parent, String scriptPath) {
        String path;
        String normalizedScriptPath;
        context = context.equals("") ? "/" : context;
        normalizedScriptPath = scriptPath.startsWith("/") ?
                FilenameUtils.normalize(scriptPath, true) :
                FilenameUtils.normalize(FilenameUtils.getFullPath(parent) + scriptPath, true);
        path = FilenameUtils.getFullPath(normalizedScriptPath);
        //remove trailing "/"
        path = path.equals("/") ? path : path.substring(0, path.length() - 1);
        normalizedScriptPath = "/" + FilenameUtils.getName(normalizedScriptPath);
        return new String[]{
                context,
                path,
                normalizedScriptPath
        };
    }

    public static long getScriptLastModified(ServletContext context, String scriptPath) throws ScriptException {
        long result = -1;
        URLConnection uc = null;
        try {
            URL scriptUrl = context.getResource(canonicalURI(scriptPath));
            if (scriptUrl == null) {
                String msg = "Requested resource " + scriptPath + " cannot be found";
                log.error(msg);
                throw new ScriptException(msg);
            }
            uc = scriptUrl.openConnection();
            if (uc instanceof JarURLConnection) {
                result = ((JarURLConnection) uc).getJarEntry().getTime();
            } else {
                result = uc.getLastModified();
            }
        } catch (IOException e) {
            log.warn("Error getting last modified time for " + scriptPath, e);
            result = -1;
        } finally {
            if (uc != null) {
                try {
                    uc.getInputStream().close();
                } catch (IOException e) {
                    log.error("Error closing input stream for script " + scriptPath, e);
                }
            }
        }
        return result;
    }

    private static String canonicalURI(String s) {
        if (s == null) {
            return null;
        }
        StringBuilder result = new StringBuilder();
        final int len = s.length();
        int pos = 0;
        while (pos < len) {
            char c = s.charAt(pos);
            if (isPathSeparator(c)) {
                /*
                * multiple path separators.
                * 'foo///bar' -> 'foo/bar'
                */
                while (pos + 1 < len && isPathSeparator(s.charAt(pos + 1))) {
                    ++pos;
                }

                if (pos + 1 < len && s.charAt(pos + 1) == '.') {
                    /*
                    * a single dot at the end of the path - we are done.
                    */
                    if (pos + 2 >= len) {
                        break;
                    }

                    switch (s.charAt(pos + 2)) {
                        /*
                        * self directory in path
                        * foo/./bar -> foo/bar
                        */
                        case '/':
                        case '\\':
                            pos += 2;
                            continue;

                            /*
                            * two dots in a path: go back one hierarchy.
                            * foo/bar/../baz -> foo/baz
                            */
                        case '.':
                            // only if we have exactly _two_ dots.
                            if (pos + 3 < len && isPathSeparator(s.charAt(pos + 3))) {
                                pos += 3;
                                int separatorPos = result.length() - 1;
                                while (separatorPos >= 0 &&
                                        !isPathSeparator(result
                                                .charAt(separatorPos))) {
                                    --separatorPos;
                                }
                                if (separatorPos >= 0) {
                                    result.setLength(separatorPos);
                                }
                                continue;
                            }
                    }
                }
            }
            result.append(c);
            ++pos;
        }
        return result.toString();
    }

    private static boolean isPathSeparator(char c) {
        return (c == '/' || c == '\\');
    }

    public static boolean isWebSocket(ServletRequest request) {
        isWebSocket = "websocket".equals(((HttpServletRequest) request).getHeader("Upgrade"));
        return "websocket".equals(((HttpServletRequest) request).getHeader("Upgrade"));
    }
}
TOP

Related Classes of org.jaggeryjs.jaggery.core.manager.WebAppManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.