001/* 002 * (C) Copyright 2015 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.DocumentWrapper; 047import org.nuxeo.ecm.automation.core.scripting.PrincipalWrapper; 048import org.nuxeo.ecm.core.api.CoreSession; 049import org.nuxeo.ecm.core.api.DocumentModel; 050import org.nuxeo.ecm.core.api.DocumentModelList; 051import org.nuxeo.ecm.core.api.NuxeoException; 052import org.nuxeo.ecm.core.api.NuxeoPrincipal; 053import org.nuxeo.runtime.api.Framework; 054 055/** 056 * @since 7.2 057 */ 058public class AutomationScriptingServiceImpl implements AutomationScriptingService { 059 060 protected String jsWrapper = null; 061 062 protected ScriptOperationContext operationContext; 063 064 protected String getJSWrapper(boolean refresh) throws OperationException { 065 if (jsWrapper == null || refresh) { 066 StringBuffer sb = new StringBuffer(); 067 AutomationService as = Framework.getService(AutomationService.class); 068 Map<String, List<String>> opMap = new HashMap<>(); 069 List<String> flatOps = new ArrayList<>(); 070 List<String> ids = new ArrayList<>(); 071 for (OperationType op : as.getOperations()) { 072 ids.add(op.getId()); 073 if (op.getAliases() != null) { 074 Collections.addAll(ids, op.getAliases()); 075 } 076 } 077 // Create js object related to operation categories 078 for (String id : ids) { 079 parseAutomationIDSForScripting(opMap, flatOps, id); 080 } 081 for (String obName : opMap.keySet()) { 082 List<String> ops = opMap.get(obName); 083 sb.append("\nvar ").append(obName).append("={};"); 084 for (String opId : ops) { 085 generateFunction(sb, opId); 086 } 087 } 088 for (String opId : flatOps) { 089 generateFlatFunction(sb, opId); 090 } 091 jsWrapper = sb.toString(); 092 } 093 return jsWrapper; 094 } 095 096 @Override 097 public void setOperationContext(ScriptOperationContext ctx) { 098 this.operationContext = operationContexts.get(); 099 this.operationContext = wrapContext(ctx); 100 } 101 102 protected ScriptOperationContext wrapContext(ScriptOperationContext ctx) { 103 for (String entryId : ctx.keySet()) { 104 Object entry = ctx.get(entryId); 105 if (entry instanceof DocumentModel) { 106 ctx.put(entryId, new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) entry)); 107 } 108 if (entry instanceof DocumentModelList) { 109 List<DocumentWrapper> docs = new ArrayList<>(); 110 for (DocumentModel doc : (DocumentModelList) entry) { 111 docs.add(new DocumentWrapper(ctx.getCoreSession(), doc)); 112 } 113 ctx.put(entryId, docs); 114 } 115 } 116 return ctx; 117 } 118 119 @Override 120 public String getJSWrapper() throws OperationException { 121 return getJSWrapper(false); 122 } 123 124 protected final ThreadLocal<ScriptEngine> engines = new ThreadLocal<ScriptEngine>() { 125 @Override 126 protected ScriptEngine initialValue() { 127 return Framework.getService(ScriptEngineManager.class).getEngineByName( 128 AutomationScriptingConstants.NX_NASHORN); 129 } 130 }; 131 132 protected final ThreadLocal<ScriptOperationContext> operationContexts = new ThreadLocal<ScriptOperationContext>() { 133 @Override 134 protected ScriptOperationContext initialValue() { 135 return new ScriptOperationContext(); 136 } 137 }; 138 139 @Override 140 public void run(InputStream in, CoreSession session) throws ScriptException, OperationException { 141 try { 142 run(IOUtils.toString(in, "UTF-8"), session); 143 } catch (IOException e) { 144 throw new NuxeoException(e); 145 } 146 } 147 148 @Override 149 public void run(String script, CoreSession session) throws ScriptException, OperationException { 150 ScriptEngine engine = engines.get(); 151 engine.setContext(new SimpleScriptContext()); 152 engine.eval(getJSWrapper()); 153 154 // Initialize Operation Context 155 if (operationContext == null) { 156 operationContext = operationContexts.get(); 157 operationContext.setCoreSession(session); 158 } 159 160 // Injecting Automation Mapper 'automation' 161 AutomationMapper automationMapper = new AutomationMapper(session, operationContext); 162 engine.put(AutomationScriptingConstants.AUTOMATION_MAPPER_KEY, automationMapper); 163 164 // Inject operation context vars in 'Context' 165 engine.put(AutomationScriptingConstants.AUTOMATION_CTX_KEY, automationMapper.ctx.getVars()); 166 // Session injection 167 engine.put("Session", automationMapper.ctx.getCoreSession()); 168 // User injection 169 PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) automationMapper.ctx.getPrincipal()); 170 engine.put("CurrentUser", principalWrapper); 171 engine.put("currentUser", principalWrapper); 172 // Env Properties injection 173 engine.put("Env", Framework.getProperties()); 174 // DateWrapper injection 175 engine.put("CurrentDate", new DateWrapper()); 176 // Workflow variables injection 177 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW) != null) { 178 engine.put(Constants.VAR_WORKFLOW, automationMapper.ctx.get(Constants.VAR_WORKFLOW)); 179 } 180 if (automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE) != null) { 181 engine.put(Constants.VAR_WORKFLOW_NODE, automationMapper.ctx.get(Constants.VAR_WORKFLOW_NODE)); 182 } 183 184 // Helpers injection 185 ContextService contextService = Framework.getService(ContextService.class); 186 Map<String, ContextHelper> helperFunctions = contextService.getHelperFunctions(); 187 for (String helperFunctionsId : helperFunctions.keySet()) { 188 engine.put(helperFunctionsId, helperFunctions.get(helperFunctionsId)); 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}