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