001/* 002 * (C) Copyright 2015-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Stephane Lacoin <slacoin@nuxeo.com> 018 * Vladimir Pasquier <vpasquier@nuxeo.com> 019 */ 020package org.nuxeo.automation.scripting.internals; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.util.ArrayList; 025import java.util.Collections; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import javax.script.Invocable; 031import javax.script.ScriptEngine; 032import javax.script.ScriptEngineManager; 033import javax.script.ScriptException; 034import javax.script.SimpleScriptContext; 035 036import org.apache.commons.io.IOUtils; 037import org.nuxeo.automation.scripting.api.AutomationScriptingConstants; 038import org.nuxeo.automation.scripting.api.AutomationScriptingService; 039import org.nuxeo.ecm.automation.AutomationService; 040import org.nuxeo.ecm.automation.OperationException; 041import org.nuxeo.ecm.automation.OperationType; 042import org.nuxeo.ecm.automation.context.ContextHelper; 043import org.nuxeo.ecm.automation.context.ContextService; 044import org.nuxeo.ecm.automation.core.Constants; 045import org.nuxeo.ecm.automation.core.scripting.DateWrapper; 046import org.nuxeo.ecm.automation.core.scripting.PrincipalWrapper; 047import org.nuxeo.ecm.core.api.CoreSession; 048import org.nuxeo.ecm.core.api.NuxeoException; 049import org.nuxeo.ecm.core.api.NuxeoPrincipal; 050import org.nuxeo.runtime.api.Framework; 051 052/** 053 * @since 7.2 054 */ 055public class AutomationScriptingServiceImpl implements AutomationScriptingService { 056 057 protected String jsWrapper = null; 058 059 protected ScriptOperationContext operationContext; 060 061 protected String getJSWrapper(boolean refresh) throws OperationException { 062 if (jsWrapper == null || refresh) { 063 StringBuffer sb = new StringBuffer(); 064 AutomationService as = Framework.getService(AutomationService.class); 065 Map<String, List<String>> opMap = new HashMap<>(); 066 List<String> flatOps = new ArrayList<>(); 067 List<String> ids = new ArrayList<>(); 068 for (OperationType op : as.getOperations()) { 069 ids.add(op.getId()); 070 if (op.getAliases() != null) { 071 Collections.addAll(ids, op.getAliases()); 072 } 073 } 074 // Create js object related to operation categories 075 for (String id : ids) { 076 parseAutomationIDSForScripting(opMap, flatOps, id); 077 } 078 for (String obName : opMap.keySet()) { 079 List<String> ops = opMap.get(obName); 080 sb.append("\nvar ").append(obName).append("={};"); 081 for (String opId : ops) { 082 generateFunction(sb, opId); 083 } 084 } 085 for (String opId : flatOps) { 086 generateFlatFunction(sb, opId); 087 } 088 jsWrapper = sb.toString(); 089 } 090 return jsWrapper; 091 } 092 093 @Override 094 public void setOperationContext(ScriptOperationContext ctx) { 095 this.operationContext = operationContexts.get(); 096 this.operationContext = wrapContext(ctx); 097 } 098 099 protected ScriptOperationContext wrapContext(ScriptOperationContext ctx) { 100 ctx.replaceAll((key, value) -> WrapperHelper.wrap(value, ctx.getCoreSession())); 101 return ctx; 102 } 103 104 @Override 105 public String getJSWrapper() throws OperationException { 106 return getJSWrapper(false); 107 } 108 109 protected final ThreadLocal<ScriptEngine> engines = new ThreadLocal<ScriptEngine>() { 110 @Override 111 protected ScriptEngine initialValue() { 112 return Framework.getService(ScriptEngineManager.class).getEngineByName( 113 AutomationScriptingConstants.NX_NASHORN); 114 } 115 }; 116 117 protected final ThreadLocal<ScriptOperationContext> operationContexts = new ThreadLocal<ScriptOperationContext>() { 118 @Override 119 protected ScriptOperationContext initialValue() { 120 return new ScriptOperationContext(); 121 } 122 }; 123 124 @Override 125 public void run(InputStream in, CoreSession session) throws ScriptException, OperationException { 126 try { 127 run(IOUtils.toString(in, "UTF-8"), session); 128 } catch (IOException e) { 129 throw new NuxeoException(e); 130 } 131 } 132 133 @Override 134 public void run(String script, CoreSession session) throws ScriptException, OperationException { 135 ScriptEngine engine = engines.get(); 136 engine.setContext(new SimpleScriptContext()); 137 engine.eval(getJSWrapper()); 138 139 // Initialize Operation Context 140 if (operationContext == null) { 141 operationContext = operationContexts.get(); 142 operationContext.setCoreSession(session); 143 } 144 145 // Injecting Automation Mapper 'automation' 146 AutomationMapper automationMapper = new AutomationMapper(session, operationContext); 147 engine.put(AutomationScriptingConstants.AUTOMATION_MAPPER_KEY, automationMapper); 148 149 // Inject operation context vars in 'Context' 150 engine.put(AutomationScriptingConstants.AUTOMATION_CTX_KEY, automationMapper.ctx.getVars()); 151 // Session injection 152 engine.put("Session", automationMapper.ctx.getCoreSession()); 153 // User injection 154 PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) automationMapper.ctx.getPrincipal()); 155 engine.put("CurrentUser", principalWrapper); 156 engine.put("currentUser", principalWrapper); 157 // Env Properties injection 158 engine.put("Env", Framework.getProperties()); 159 // DateWrapper injection 160 engine.put("CurrentDate", new DateWrapper()); 161 // Workflow variables injection 162 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW) != null) { 163 engine.put(Constants.VAR_WORKFLOW, automationMapper.ctx.get(Constants.VAR_WORKFLOW)); 164 } 165 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE) != null) { 166 engine.put(Constants.VAR_WORKFLOW_NODE, automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE)); 167 } 168 169 // Helpers injection 170 ContextService contextService = Framework.getService(ContextService.class); 171 Map<String, ContextHelper> helperFunctions = contextService.getHelperFunctions(); 172 for (String helperFunctionsId : helperFunctions.keySet()) { 173 engine.put(helperFunctionsId, helperFunctions.get(helperFunctionsId)); 174 } 175 engine.eval(script); 176 } 177 178 @Override 179 public <T> T getInterface(Class<T> scriptingOperationInterface, String script, CoreSession session) 180 throws ScriptException, OperationException { 181 run(script, session); 182 Invocable inv = (Invocable) engines.get(); 183 return inv.getInterface(scriptingOperationInterface); 184 } 185 186 protected void parseAutomationIDSForScripting(Map<String, List<String>> opMap, List<String> flatOps, String id) { 187 if (id.split("\\.").length > 2) { 188 return; 189 } 190 int idx = id.indexOf("."); 191 if (idx > 0) { 192 String obName = id.substring(0, idx); 193 List<String> ops = opMap.get(obName); 194 if (ops == null) { 195 ops = new ArrayList<>(); 196 } 197 ops.add(id); 198 opMap.put(obName, ops); 199 } else { 200 // Flat operation: no need of category 201 flatOps.add(id); 202 } 203 } 204 205 protected void generateFunction(StringBuffer sb, String opId) { 206 sb.append("\n" + replaceDashByUnderscore(opId) + " = function(input,params) {"); 207 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 208 sb.append("\n};"); 209 } 210 211 protected void generateFlatFunction(StringBuffer sb, String opId) { 212 sb.append("\nvar " + replaceDashByUnderscore(opId) + " = function(input,params) {"); 213 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 214 sb.append("\n};"); 215 } 216 217 /** 218 * Prevents dashes in operation/chain ids. Only used to avoid javascript issues. 219 * 220 * @since 7.3 221 */ 222 public static String replaceDashByUnderscore(String id) { 223 return id.replaceAll("[\\s\\-()]", "_"); 224 } 225 226}