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.OperationContext; 039import org.nuxeo.ecm.automation.OperationException; 040import org.nuxeo.ecm.automation.OperationType; 041import org.nuxeo.ecm.automation.context.ContextHelper; 042import org.nuxeo.ecm.automation.context.ContextService; 043import org.nuxeo.ecm.automation.core.Constants; 044import org.nuxeo.ecm.automation.core.scripting.DateWrapper; 045import org.nuxeo.ecm.automation.core.scripting.DocumentWrapper; 046import org.nuxeo.ecm.automation.core.scripting.PrincipalWrapper; 047import org.nuxeo.ecm.core.api.CoreSession; 048import org.nuxeo.ecm.core.api.DocumentModel; 049import org.nuxeo.ecm.core.api.DocumentModelList; 050import org.nuxeo.ecm.core.api.NuxeoException; 051import org.nuxeo.ecm.core.api.NuxeoPrincipal; 052import org.nuxeo.runtime.api.Framework; 053 054/** 055 * @since 7.2 056 */ 057public class AutomationScriptingServiceImpl implements AutomationScriptingService { 058 059 protected String jsWrapper = null; 060 061 protected OperationContext operationContext; 062 063 protected String getJSWrapper(boolean refresh) throws OperationException { 064 if (jsWrapper == null || refresh) { 065 StringBuffer sb = new StringBuffer(); 066 AutomationService as = Framework.getService(AutomationService.class); 067 Map<String, List<String>> opMap = new HashMap<>(); 068 List<String> flatOps = new ArrayList<>(); 069 List<String> ids = new ArrayList<>(); 070 for (OperationType op : as.getOperations()) { 071 ids.add(op.getId()); 072 if (op.getAliases() != null) { 073 Collections.addAll(ids, op.getAliases()); 074 } 075 } 076 // Create js object related to operation categories 077 for (String id : ids) { 078 parseAutomationIDSForScripting(opMap, flatOps, id); 079 } 080 for (String obName : opMap.keySet()) { 081 List<String> ops = opMap.get(obName); 082 sb.append("\nvar ").append(obName).append("={};"); 083 for (String opId : ops) { 084 generateFunction(sb, opId); 085 } 086 } 087 for (String opId : flatOps) { 088 generateFlatFunction(sb, opId); 089 } 090 jsWrapper = sb.toString(); 091 } 092 return jsWrapper; 093 } 094 095 @Override 096 public void setOperationContext(OperationContext ctx) { 097 this.operationContext = operationContexts.get(); 098 this.operationContext = wrapContext(ctx); 099 } 100 101 protected OperationContext wrapContext(OperationContext ctx) { 102 for (String entryId : ctx.keySet()) { 103 Object entry = ctx.get(entryId); 104 if (entry instanceof DocumentModel) { 105 ctx.put(entryId, new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) entry)); 106 } 107 if (entry instanceof DocumentModelList) { 108 List<DocumentWrapper> docs = new ArrayList<>(); 109 for (DocumentModel doc : (DocumentModelList) entry) { 110 docs.add(new DocumentWrapper(ctx.getCoreSession(), doc)); 111 } 112 ctx.put(entryId, docs); 113 } 114 } 115 return ctx; 116 } 117 118 @Override 119 public String getJSWrapper() throws OperationException { 120 return getJSWrapper(false); 121 } 122 123 protected final ThreadLocal<ScriptEngine> engines = new ThreadLocal<ScriptEngine>() { 124 @Override 125 protected ScriptEngine initialValue() { 126 return Framework.getService(ScriptEngineManager.class).getEngineByName( 127 AutomationScriptingConstants.NX_NASHORN); 128 } 129 }; 130 131 protected final ThreadLocal<OperationContext> operationContexts = new ThreadLocal<OperationContext>() { 132 @Override 133 protected OperationContext initialValue() { 134 return new OperationContext(); 135 } 136 }; 137 138 @Override 139 public void run(InputStream in, CoreSession session) throws ScriptException, OperationException { 140 try { 141 run(IOUtils.toString(in, "UTF-8"), session); 142 } catch (IOException e) { 143 throw new NuxeoException(e); 144 } 145 } 146 147 @Override 148 public void run(String script, CoreSession session) throws ScriptException, OperationException { 149 ScriptEngine engine = engines.get(); 150 engine.setContext(new SimpleScriptContext()); 151 engine.eval(getJSWrapper()); 152 153 // Initialize Operation Context 154 if (operationContext == null) { 155 operationContext = operationContexts.get(); 156 operationContext.setCoreSession(session); 157 } 158 159 // Injecting Automation Mapper 'automation' 160 AutomationMapper automationMapper = new AutomationMapper(session, operationContext); 161 engine.put(AutomationScriptingConstants.AUTOMATION_MAPPER_KEY, automationMapper); 162 163 // Inject operation context vars in 'Context' 164 engine.put(AutomationScriptingConstants.AUTOMATION_CTX_KEY, automationMapper.ctx.getVars()); 165 // Session injection 166 engine.put("Session", automationMapper.ctx.getCoreSession()); 167 // User injection 168 PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) automationMapper.ctx.getPrincipal()); 169 engine.put("CurrentUser", principalWrapper); 170 engine.put("currentUser", principalWrapper); 171 // Env Properties injection 172 engine.put("Env", Framework.getProperties()); 173 // DateWrapper injection 174 engine.put("CurrentDate", new DateWrapper()); 175 // Workflow variables injection 176 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW) != null) { 177 engine.put(Constants.VAR_WORKFLOW, automationMapper.ctx.get(Constants.VAR_WORKFLOW)); 178 } 179 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE) != null) { 180 engine.put(Constants.VAR_WORKFLOW_NODE, automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE)); 181 } 182 183 // Helpers injection 184 ContextService contextService = Framework.getService(ContextService.class); 185 Map<String, ContextHelper> helperFunctions = contextService.getHelperFunctions(); 186 for(String helperFunctionsId: helperFunctions.keySet()){ 187 engine.put(helperFunctionsId,helperFunctions.get(helperFunctionsId)); 188 } 189 190 engine.eval(script); 191 } 192 193 @Override 194 public <T> T getInterface(Class<T> scriptingOperationInterface, String script, CoreSession session) 195 throws ScriptException, OperationException { 196 run(script, session); 197 Invocable inv = (Invocable) engines.get(); 198 return inv.getInterface(scriptingOperationInterface); 199 } 200 201 protected void parseAutomationIDSForScripting(Map<String, List<String>> opMap, List<String> flatOps, String id) { 202 if (id.split("\\.").length > 2) { 203 return; 204 } 205 int idx = id.indexOf("."); 206 if (idx > 0) { 207 String obName = id.substring(0, idx); 208 List<String> ops = opMap.get(obName); 209 if (ops == null) { 210 ops = new ArrayList<>(); 211 } 212 ops.add(id); 213 opMap.put(obName, ops); 214 } else { 215 // Flat operation: no need of category 216 flatOps.add(id); 217 } 218 } 219 220 protected void generateFunction(StringBuffer sb, String opId) { 221 sb.append("\n" + replaceDashByUnderscore(opId) + " = function(input,params) {"); 222 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 223 sb.append("\n};"); 224 } 225 226 protected void generateFlatFunction(StringBuffer sb, String opId) { 227 sb.append("\nvar " + replaceDashByUnderscore(opId) + " = function(input,params) {"); 228 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 229 sb.append("\n};"); 230 } 231 232 /** 233 * Prevents dashes in operation/chain ids. Only used to avoid javascript issues. 234 * @since 7.3 235 */ 236 public static String replaceDashByUnderscore(String id) { 237 return id.replaceAll("[\\s\\-()]", "_"); 238 } 239}