Java tutorial
/* * 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 com.baasbox.service.logging.BaasBoxLogger; 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 embedded; if (data == null || data.isNull()) { embedded = null; script.removeField(ScriptsDao.LOCAL_STORAGE); } else { embedded = new ODocument().fromJSON(data.toString()); script.field(ScriptsDao.LOCAL_STORAGE, embedded); } dao.save(script); return embedded; } 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; if (updated == null || updated.isNull()) { script.removeField(ScriptsDao.LOCAL_STORAGE); } else { 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, false); 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 (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.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 (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.trace("Method start"); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Creating script"); ScriptsDao dao = ScriptsDao.getInstance(); ODocument doc = createScript(dao, script); compile(doc, true); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Script created"); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Script installing"); ScriptStatus status; ScriptCall installation = ScriptCall.install(doc); try { ScriptResult res = invoke(installation); status = res.toScriptStatus(); if (!status.ok) { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Script installation aborted by the script"); doc.delete(); } } catch (ScriptEvalException e) { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Script installation failed: deleting - " + ExceptionUtils.getStackTrace(e)); doc.delete(); throw new ScriptException(e); } if (BaasBoxLogger.isTraceEnabled()) BaasBoxLogger.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 (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.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(ExceptionUtils.getMessage(e), 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, boolean dropOnFailure) throws ScriptEvalException { if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("Start Compile"); ScriptCall compile = ScriptCall.compile(doc); try { invoke(compile); if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.debug("End Compile"); } catch (ScriptEvalException e) { BaasBoxLogger.error("Failed Script compilation"); if (dropOnFailure) { doc.delete(); } else { ScriptsDao dao = ScriptsDao.getInstance(); dao.revertToLastVersion(doc); } if (BaasBoxLogger.isDebugEnabled()) BaasBoxLogger.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(); JsonNode encoded = node.get(ScriptsDao.ENCODED); ODocument doc; if (encoded != null && encoded.isTextual()) { String encodedValue = encoded.asText(); doc = dao.create(name, language.name, code, isLibrary, active, initialStorage, encodedValue); } else { doc = dao.create(name, language.name, code, isLibrary, active, initialStorage); } return doc; } public static JsonNode callJsonSync(JsonNode req) throws Exception { JsonNode url = req.get("url"); JsonNode method = req.get("method"); JsonNode timeout = req.get("timeout"); if (url == null || url instanceof NullNode) throw new IllegalArgumentException("Missing URL to call"); if (method == null || method instanceof NullNode) throw new IllegalArgumentException("Missing method to use when calling the URL"); return callJsonSync(url.asText(), method.asText(), mapJson(req.get("params")), mapJson(req.get("headers")), req.get("body"), (timeout != null && timeout.isNumber()) ? timeout.asInt() : null); } private static Map<String, List<String>> mapJson(JsonNode node) { if (node == null || node instanceof NullNode) { 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, Integer timeout) throws Exception { try { ObjectNode node = Json.mapper().createObjectNode(); WS.Response resp = null; long startTime = System.nanoTime(); if (timeout == null) { resp = HttpClientService.callSync(url, method, params, headers, body == null ? null : (body.isValueNode() ? body.toString() : body)); } else { resp = HttpClientService.callSync(url, method, params, headers, body == null ? null : (body.isValueNode() ? body.toString() : body), timeout); } long endTime = System.nanoTime(); int status = resp.getStatus(); node.put("status", status); node.put("execution_time", (endTime - startTime) / 1000000L); 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) { BaasBoxLogger.error("failed to connect: " + ExceptionUtils.getMessage(e)); 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(); } }