Package com.baasbox.service.scripting

Source Code of com.baasbox.service.scripting.ScriptingService

/*
* Copyright (c) 2014.
*
* BaasBox - info@baasbox.com
*
* 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 com.baasbox.service.scripting;

import com.baasbox.dao.ScriptsDao;
import com.baasbox.dao.exception.ScriptException;
import com.baasbox.dao.exception.SqlInjectionException;
import com.baasbox.service.webservices.HttpClientService;
import com.baasbox.service.scripting.base.*;
import com.baasbox.service.scripting.js.Json;
import com.baasbox.util.QueryParams;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.orientechnologies.orient.core.record.impl.ODocument;

import play.Logger;
import play.libs.EventSource;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang3.exception.ExceptionUtils;
import play.libs.WS;

/**
* Created by Andrea Tortorella on 10/06/14.
*/
public class ScriptingService {
    private static Map<String,List<EventSource>> connectedLogListeners = new HashMap<String,List<EventSource>>();

    private static final ThreadLocal<String> MAIN = new ThreadLocal<>();

    /*
     * Script lifecycle
     *
     *  |  Install  |
     *       |
     *       v
     *  | Activate |<---.
     *       |          |
     *       v          |
     *    /LIVE/        |
     *       \          |
     *       v          |
     *  | Deactivate |--'
     *       |
     *       v
     *  | Uninstall |
     */

    public static ODocument resetStore(String name,JsonNode data) throws ScriptException {
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = dao.getByName(name);
        if (script == null) throw new ScriptException("Script not found");
        ODocument emdedded = new ODocument().fromJSON(data.toString());
        script.field(ScriptsDao.LOCAL_STORAGE,emdedded);
        dao.save(script);
        return emdedded;
    }

    public static ODocument getStore(String name) throws ScriptException {
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = dao.getByName(name);
        if (script == null) throw new ScriptException("Script not found");
        return script.<ODocument>field(ScriptsDao.LOCAL_STORAGE);
    }

    private static ODocument updateStorageLocked(String name,boolean before,JsonCallback updaterFn) throws ScriptException {
        final ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = null;
        try {
            script = dao.getByNameLocked(name);
            if (script == null) throw new ScriptException("Script not found");
            ODocument retScript = before ? script.copy() : script;

            ODocument storage = script.<ODocument>field(ScriptsDao.LOCAL_STORAGE);

            Optional<ODocument> storage1 = Optional.ofNullable(storage);

            JsonNode current = storage1.map(ODocument::toJSON)
                    .map(Json.mapper()::readTreeOrMissing)
                    .orElse(NullNode.getInstance());
            if (current.isMissingNode()) throw new ScriptEvalException("Error reading local storage as json");

            JsonNode updated = updaterFn.call(current);

            ODocument result = new ODocument().fromJSON(updated.toString());
            script.field(ScriptsDao.LOCAL_STORAGE, result);
            dao.save(script);
            ODocument field = retScript.field(ScriptsDao.LOCAL_STORAGE);
            return field;
        } finally {
            if (script != null) {
                script.unlock();
            }
        }
    }

    public static ODocument swap(String name,JsonCallback callback) throws ScriptException {
        return updateStorageLocked(name,false,callback);
    }

    public static ODocument trade(String name,JsonCallback updater) throws ScriptException {
        return updateStorageLocked(name,true,updater);
    }


    public static List<ODocument> list(QueryParams paramsFromQueryString) throws SqlInjectionException {
        ScriptsDao dao = ScriptsDao.getInstance();
        List<ODocument> scripts = dao.getAll(paramsFromQueryString);
        return scripts;
    }

    public static ScriptStatus update(String name,JsonNode code) throws ScriptException{
        if (code == null) throw new ScriptException("missing code");
        JsonNode codeNode = code.get(ScriptsDao.CODE);
        if (codeNode == null|| !codeNode.isTextual()){
            throw new ScriptException("missing code");
        }
        String source = codeNode.asText();
        return update(name,source);
    }

    public static ScriptStatus update(String name,String code) throws ScriptException {
        ScriptsDao dao = ScriptsDao.getInstance();
        updateCacheVersion();
        ODocument updated = dao.update(name,code);
        compile(updated);

        ScriptStatus status;
        ScriptCall install = ScriptCall.install(updated);
        try {
            ScriptResult result = invoke(install);
            status = result.toScriptStatus();
            if (!status.ok){
                updateCacheVersion();
                dao.revertToLastVersion(updated);

            }
        } catch (ScriptEvalException e){
            if (Logger.isDebugEnabled()) Logger.debug("Script installation failed: deleting");
            updateCacheVersion();
            dao.invalidate(updated);
            dao.revertToLastVersion(updated);
            throw e;
        }
        return status;
    }

    /**
     * Creates a new script object
     * @param script
     * @return
     * @throws ScriptException
     */
    public static ScriptStatus create(JsonNode script) throws ScriptException {
        if (Logger.isTraceEnabled()) Logger.trace("Method start");

        if (Logger.isDebugEnabled()) Logger.debug("Creating script");
        ScriptsDao dao = ScriptsDao.getInstance();

        ODocument doc = createScript(dao,script);
        compile(doc);
        if (Logger.isDebugEnabled())Logger.debug("Script created");

        if (Logger.isDebugEnabled())Logger.debug("Script installing");
        ScriptStatus status;
        ScriptCall installation = ScriptCall.install(doc);
        try {

            ScriptResult res = invoke(installation);
            status = res.toScriptStatus();
            if (!status.ok){
                if (Logger.isDebugEnabled()) Logger.debug("Script installation aborted by the script");
                doc.delete();
            }
        } catch (ScriptEvalException e){
            if (Logger.isDebugEnabled()) Logger.debug("Script installation failed: deleting - " + ExceptionUtils.getStackTrace(e));
            doc.delete();
            throw new ScriptException(e);
        }
        if (Logger.isTraceEnabled()) Logger.trace("Method end");
        return status;
    }

    /**
     * Returns a script object corresponding to name
     * @param name
     * @return
     */
    public static ODocument get(String name) {
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = dao.getByName(name);
        return script;
    }

    public static ODocument get(String name,boolean onlyvalid,boolean active) throws ScriptException {
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = dao.getByName(name);
        if (script != null){
            if (onlyvalid && script.<Boolean>field(ScriptsDao.INVALID)){
                throw new ScriptException("Script is in invalid state");
            }
            if (active && !(script.<Boolean>field(ScriptsDao.ACTIVE))){
                throw new ScriptEvalException("Script is not active");
            }
        }
        return script;
    }

    /**
     * Deletes a script object with name
     * @param name
     * @return
     */
    public static boolean delete(String name) throws ScriptException{
        updateCacheVersion();
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument script = dao.getByName(name);
        //script not found
        if (script == null){
            return false;
        }

        ScriptCall uninstall = ScriptCall.uninstall(script);
        try {
            invoke(uninstall);
            return dao.delete(name);
        } catch (ScriptException e){
            dao.invalidate(script);
            throw  e;
        }
    }

    public static boolean forceDelete(String name) throws ScriptException {
        updateCacheVersion();
        ScriptsDao dao = ScriptsDao.getInstance();
        return dao.delete(name);
    }


    public static Boolean activate(String name, boolean activate) {
        updateCacheVersion();
        ScriptsDao dao = ScriptsDao.getInstance();
        ODocument doc = dao.getByName(name);
        if (doc == null){
            return null;
        }
        return dao.activate(doc,activate);
    }

    public static ScriptResult invoke(ScriptCall call) throws ScriptEvalException{
        if (Logger.isDebugEnabled()) Logger.debug("Invoking script: " + call.scriptName);
        MAIN.set(call.scriptName);
        BaasboxScriptEngine engine = call.engine();
        try {
            ScriptResult res = engine.eval(call);
            return res;
        } catch (Exception e){
            if (e instanceof ScriptEvalException){
                throw (ScriptEvalException)e;
            } else {
                throw new ScriptEvalException(e.getMessage(),e);
            }
        }finally {
            MAIN.set(null);
        }
    }

//    public static void publishLog(String to,JsonNode message){
//        List<EventSource> listeners = connectedLogListeners.get(to);
//        if (Logger.isTraceEnabled())Logger.trace("Publishing message");
//        if (listeners==null||listeners.isEmpty()) return;
//
//        for (EventSource s:listeners){
//            if (s!=null){
//
//                s.sendData(message.toString());
//            }
//        }
//    }
//
//    public static void disconnectLogListener(String name, EventSource current) {
//        List<EventSource> logs = connectedLogListeners.get(name);
//        if (logs == null){
//            return;
//        } else if (logs.contains(current)){
//            logs.remove(current);
//            if (Logger.isTraceEnabled()) Logger.trace("Disconnected: "+name);
//            connectedLogListeners.put(name,logs);
//        }
//    }

    /**
     * Compiles a script without emitting any event
     * @param doc
     * @throws ScriptEvalException
     */
    private static void compile(ODocument doc) throws ScriptEvalException {
        if (Logger.isDebugEnabled()) Logger.debug("Start Compile");
        ScriptCall compile = ScriptCall.compile(doc);
        try {
            invoke(compile);
            if (Logger.isDebugEnabled()) Logger.debug("End Compile");
        } catch (ScriptEvalException e){
            Logger.error("Failed Script compilation");
            doc.delete();
            if (Logger.isDebugEnabled()) Logger.debug("Script delete");
            throw e;
        }
    }


    private static ODocument createScript(ScriptsDao dao,JsonNode node) throws ScriptException {
        updateCacheVersion();
        String lang = node.get(ScriptsDao.LANG).asText();
        ScriptLanguage language = ScriptLanguage.forName(lang);
        String name = node.get(ScriptsDao.NAME).asText();
        String code = node.get(ScriptsDao.CODE).asText();
        JsonNode initialStorage = node.get(ScriptsDao.LOCAL_STORAGE);
        JsonNode library = node.get(ScriptsDao.LIB);
        boolean isLibrary =library==null?false:library.asBoolean();
        JsonNode activeNode = node.get(ScriptsDao.ACTIVE);
        boolean active = activeNode == null?false:activeNode.asBoolean();
        ODocument doc = dao.create(name, language.name, code, isLibrary, active, initialStorage);
        return doc;
    }


    public static JsonNode callJsonSync(JsonNode req) throws Exception{
        return callJsonSync(req.get("url").asText(),
                req.get("method").asText(),
                mapJson(req.get("params")),
                mapJson(req.get("headers")),
                req.get("body"));
    }


    private static Map<String,List<String>> mapJson(JsonNode node){
        if (node == null){
            return null;
        }
        if (node.isObject()){
            Map<String,List<String>> ret = new LinkedHashMap<>();
            node.fieldNames().forEachRemaining((field)->{
                JsonNode jsonNode = node.get(field);
                List<String> cur = ret.get(field);
                if (cur == null){
                    cur = new LinkedList<>();
                    ret.put(field, cur);
                }
                append(cur, jsonNode);
            });
            return ret;
        }
        return null;
    }

    private static void append(List<String> list,JsonNode node){
        if (node==null||node.isNull()||node.isMissingNode()||node.isObject()) return;
        if (node.isValueNode()) list.add(node.asText());
        if (node.isArray()){
            node.forEach((n)->{
                if (n!=null && (!n.isNull()) && (!n.isMissingNode()) &&n.isValueNode())list.add(n.toString());
            });
        }
    }

    private static JsonNode callJsonSync(String url,String method,Map<String,List<String>> params,Map<String,List<String>> headers,JsonNode body) throws Exception{
        try {
            ObjectNode node = Json.mapper().createObjectNode();
            WS.Response resp = HttpClientService.callSync(url, method, params, headers, body.isValueNode() ? body.toString() : body);

            int status = resp.getStatus();
            node.put("status",status);

            String header = resp.getHeader("Content-Type");
            if (header==null ||  header.startsWith("text")){
                node.put("body",resp.getBody());
            } else if (header.startsWith("application/json")){
                node.put("body",resp.asJson());
            } else {
                node.put("body",resp.getBody());
            }

            return node;
        } catch (Exception e) {
            Logger.error("failed to connect: "+e.getMessage());
            throw e;
        }

    }


    /// cache management

    private static final AtomicLong SCRIPT_UPDATE_COUNTER = new AtomicLong(Long.MIN_VALUE);

    private static void updateCacheVersion(){
        SCRIPT_UPDATE_COUNTER.incrementAndGet();
    }


    public static long getCacheVersion() {
        return SCRIPT_UPDATE_COUNTER.get();
    }

    public static String main() {
        return MAIN.get();
    }

}
TOP

Related Classes of com.baasbox.service.scripting.ScriptingService

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.