org.labkey.nashorn.NashornController.java Source code

Java tutorial

Introduction

Here is the source code for org.labkey.nashorn.NashornController.java

Source

/*
 * Copyright (c) 2013 LabKey Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.labkey.nashorn;

import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.runtime.ScriptObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
import org.labkey.api.action.ApiAction;
import org.labkey.api.action.ApiJsonWriter;
import org.labkey.api.action.FormApiAction;
import org.labkey.api.action.Marshal;
import org.labkey.api.action.Marshaller;
import org.labkey.api.action.NullSafeBindException;
import org.labkey.api.action.SpringActionController;
import org.labkey.api.module.Module;
import org.labkey.api.module.ModuleLoader;
import org.labkey.api.reader.Readers;
import org.labkey.api.security.RequiresNoPermission;
import org.labkey.api.security.RequiresSiteAdmin;
import org.labkey.api.settings.AppProps;
import org.labkey.api.util.Pair;
import org.labkey.api.util.SessionHelper;
import org.labkey.api.view.JspView;
import org.labkey.api.view.NavTree;
import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.UnauthorizedException;
import org.labkey.nashorn.env.Console;
import org.labkey.nashorn.env.Errors;
import org.labkey.nashorn.env.Request;
import org.springframework.beans.PropertyValue;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.util.concurrent.Callable;

public class NashornController extends SpringActionController {
    static final Logger _log = Logger.getLogger(NashornController.class);
    final String resolvedName;

    private static final DefaultActionResolver _actionResolver = new DefaultActionResolver(
            NashornController.class) {
        @Override
        public Controller resolveActionName(Controller actionController, String name) {
            NashornController me = (NashornController) actionController;
            if (me.resolvedName.equals("nashorn")) {
                Controller c = super.resolveActionName(actionController, name);
                if (null != c)
                    return c;
            }
            return super.resolveActionName(actionController, "action");
        }
    };

    public NashornController() {
        setActionResolver(_actionResolver);
        resolvedName = "nashorn";
    }

    public NashornController(String name) {
        setActionResolver(_actionResolver);
        this.resolvedName = name;
    }

    @RequiresNoPermission
    @Marshal(Marshaller.Jackson)
    public class ActionAction extends ApiAction<JSONObject> {
        @NotNull
        @Override
        protected Pair<JSONObject, BindException> populateForm() throws Exception {
            // CONSIDER supporting strongly typed form objects
            if (StringUtils.contains(getViewContext().getRequest().getContentType(),
                    ApiJsonWriter.CONTENT_TYPE_JSON))
                return populateJSONObjectForm();
            else {
                JSONObject json = new JSONObject();
                BindException ex = new NullSafeBindException(json, "form");
                for (PropertyValue pv : getViewContext().getBindPropertyValues().getPropertyValues()) {
                    json.put(pv.getName(), pv.getValue());
                }
                return new Pair(json, ex);
            }
        }

        @Override
        public Object execute(JSONObject json, BindException errors) throws Exception {
            HttpServletRequest req = getViewContext().getRequest();

            String controllerName = getViewContext().getActionURL().getController();
            String scriptName = null;
            boolean useSessionEngine = true;
            if (controllerName.contains("-")) {
                scriptName = controllerName.substring(controllerName.indexOf("-") + 1);
                useSessionEngine = false;
            }
            String actionName = getViewContext().getActionURL().getAction();
            if (StringUtils.equalsIgnoreCase("action", actionName) && null != req.getParameter("action"))
                actionName = StringUtils.stripToEmpty(req.getParameter("action"));
            String method = req.getMethod();

            Pair<ScriptEngine, ScriptContext> nashorn = getNashorn(useSessionEngine);

            // evaluate controller script
            NashornCacheHandler.ScriptWrapper w = NashornModule.getControllerScript((controllerName).toLowerCase());
            if (null != w) {
                try (InputStream is = w.getInputStream()) {
                    nashorn.first.eval(Readers.getReader(is), nashorn.second);
                }
            }

            Bindings bindings = nashorn.second.getBindings(ScriptContext.ENGINE_SCOPE);
            if (!bindings.containsKey("actions")) {
                throw new NotFoundException("exports variable 'actions' not found");
            }

            ScriptObjectMirror actions = (ScriptObjectMirror) bindings.get("actions");

            if (null == actions || !actions.containsKey(actionName))
                throw new NotFoundException("action not found: nashorn." + actionName);

            Request envRequest = new Request(getViewContext().getRequest(), getViewContext().getActionURL());

            ScriptObjectMirror action = (ScriptObjectMirror) actions.get(actionName);

            String validateFn = null;
            if (action.containsKey("validate"))
                validateFn = "validate";

            String executeFn = "execute";
            if (action.containsKey("execute_" + method))
                executeFn = "execute_" + method;

            if (null != validateFn)
                action.callMember(validateFn, envRequest, json, new Errors(errors));

            if (errors.hasErrors())
                return null;

            Object result = action.callMember(executeFn, envRequest, json, new Errors(errors));

            if (errors.hasErrors())
                return null;

            return result;
        }
    }

    public static class ScriptForm {
        String script;

        public String getScript() {
            return script;
        }

        public void setScript(String script) {
            this.script = script;
        }
    }

    @RequiresSiteAdmin
    public class ScriptAction extends FormApiAction<ScriptForm> {
        @Override
        public ModelAndView getView(ScriptForm scriptForm, BindException errors) throws Exception {
            if (!getUser().isDeveloper() || !AppProps.getInstance().isDevMode())
                throw new UnauthorizedException();
            JspView<Object> view = new JspView<>("/org/labkey/nashorn/script.jsp", null, errors);
            return view;
        }

        @Override
        public Object execute(ScriptForm scriptForm, BindException errors) throws Exception {
            if (!getUser().isDeveloper() || !AppProps.getInstance().isDevMode())
                throw new UnauthorizedException();

            JSONObject ret = new JSONObject();
            ret.put("success", true);

            String script = StringUtils.trimToNull(scriptForm.getScript());
            if (null == script) {
                return ret;
            }
            try {
                Pair<ScriptEngine, ScriptContext> nashorn = getNashorn(true);
                Object result = nashorn.first.eval(scriptForm.getScript(), nashorn.second);
                if (_log.isDebugEnabled()) {
                    ScriptContext context = nashorn.second;
                    Bindings b = context.getBindings(ScriptContext.GLOBAL_SCOPE);
                    if (null != b) {
                        _log.debug("globals:");
                        for (String s : b.keySet())
                            _log.debug("  " + s);
                    }
                    b = context.getBindings(ScriptContext.ENGINE_SCOPE);
                    _log.debug("engine:");
                    for (String s : b.keySet()) {
                        StringBuilder entry = new StringBuilder();
                        entry.append("  ").append(s);
                        if (_log.isTraceEnabled()) {
                            Object v = b.get(s);
                            entry.append(": ").append(String.valueOf(v));
                            if (v instanceof ScriptObjectMirror) {
                                ScriptObjectMirror so = (ScriptObjectMirror) v;
                                for (String p : so.getOwnKeys(true)) {
                                    entry.append("\n    ").append(p).append(": ").append(String.valueOf(so.get(p)))
                                            .append(" ");
                                }
                            }
                        }
                        _log.debug(entry);
                    }
                }
                ret.put("result", result);
            } catch (ScriptException x) {
                ret.put("success", false);
                ret.put("message", x.getMessage());
                ret.put("lineNumber", x.getLineNumber());
                ret.put("columnNumber", x.getColumnNumber());
            }
            return ret;
        }

        @Override
        public NavTree appendNavTrail(NavTree root) {
            return null;
        }

    }

    private Pair<ScriptEngine, ScriptContext> getNashorn(boolean useSession) throws Exception {
        ScriptEngineManager engineManager = new ScriptEngineManager();
        ScriptEngine engine;
        ScriptContext context;

        Callable<ScriptEngine> createContext = new Callable<ScriptEngine>() {
            @Override
            @NotNull
            public ScriptEngine call() throws Exception {
                NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
                ScriptEngine engine = factory.getScriptEngine(new String[] { "--global-per-engine" });
                return engine;
            }
        };

        if (useSession) {
            HttpServletRequest req = getViewContext().getRequest();
            engine = SessionHelper.getAttribute(req, this.getClass().getName() + "#scriptEngine", createContext);
        } else {
            engine = createContext.call();
        }

        Bindings engineScope = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        engineScope.put("LABKEY", new org.labkey.nashorn.env.LABKEY(getViewContext()));

        Bindings globalScope = engine.getBindings(ScriptContext.GLOBAL_SCOPE);
        // null==engine.getBindings(ScriptContext.GLOBAL_SCOPE), because of --global-per-engine
        // some docs mention enginScope.get("nashorn.global"), but that is also null
        if (null == globalScope)
            globalScope = (Bindings) engineScope.get("nashorn.global");
        if (null == globalScope)
            globalScope = engineScope;
        globalScope.put("console", new Console());

        return new Pair<>(engine, engine.getContext());
    }
}

/*
// example script
    
    
(function(){
    
   var beginAction =
   {
   validate:function(json,errors)                     // optional
   {
   },
    
   execute:function(json,errors)                      // required (or execute_POST)
   {
      var name = json.name || json.Name;
      if (name)
          return {message:'Hello ' + name};
       else
          return {message:'Hello World'};
   },
    
   execute_POST:function(json,errors)                 // optional
   {
       var ret = this.execute(json,errors);
       ret['method'] = 'POST';
       return ret;
   },
    
   execute_GET:function(json,errors)                  // optional
   {
       var ret = this.execute(json,errors);
       ret['method'] = 'GET';
       return ret;
   },
    
   methodsAllowed:['POST','GET'],          // default = ['POST']
    
   requiresPermission: "ReadPermission"    // required
   };
    
   return actions =
   {
   begin: beginAction
   };
    
})();
    
    
 */