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 * Thierry Delprat <tdelprat@nuxeo.com> 018 * Vladimir Pasquier <vpasquier@nuxeo.com> 019 */ 020package org.nuxeo.automation.scripting.internals; 021 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Optional; 029import java.util.Set; 030import java.util.function.BiConsumer; 031import java.util.function.BiFunction; 032import java.util.function.Function; 033import java.util.function.Supplier; 034import java.util.stream.Collectors; 035import java.util.stream.Stream; 036 037import javax.script.Bindings; 038import javax.script.Compilable; 039import javax.script.CompiledScript; 040import javax.script.ScriptException; 041import javax.script.SimpleBindings; 042 043import org.nuxeo.automation.scripting.api.AutomationScriptingConstants; 044import org.nuxeo.ecm.automation.AutomationService; 045import org.nuxeo.ecm.automation.OperationContext; 046import org.nuxeo.ecm.automation.OperationParameters; 047import org.nuxeo.ecm.automation.OperationType; 048import org.nuxeo.ecm.automation.context.ContextHelper; 049import org.nuxeo.ecm.automation.context.ContextService; 050import org.nuxeo.ecm.automation.core.scripting.DateWrapper; 051import org.nuxeo.ecm.automation.core.scripting.PrincipalWrapper; 052import org.nuxeo.ecm.core.api.NuxeoException; 053import org.nuxeo.ecm.core.api.NuxeoPrincipal; 054import org.nuxeo.runtime.api.Framework; 055 056import jdk.nashorn.api.scripting.ScriptObjectMirror; 057 058/** 059 * Class injected/published in Nashorn engine to execute automation service. 060 * 061 * @since 7.2 062 */ 063public class AutomationMapper implements Bindings { 064 065 protected final OperationContext ctx; 066 067 protected final Map<String, Supplier<Object>> automatic = new HashMap<>(); 068 069 protected final Bindings bindings = new SimpleBindings(); 070 071 protected final Map<String, Object> wrapped = new HashMap<>(); 072 073 public static CompiledScript compile(Compilable compilable) { 074 try { 075 return new ScriptBuilder().build(compilable); 076 } catch (ScriptException cause) { 077 throw new NuxeoException("Cannot compile mapper initialization script", cause); 078 } 079 } 080 081 public AutomationMapper(OperationContext ctx) { 082 this.ctx = ctx; 083 automatic.put("Session", () -> ctx.getCoreSession()); 084 automatic.put(AutomationScriptingConstants.AUTOMATION_CTX_KEY, () -> ctx.getVars()); 085 automatic.put(AutomationScriptingConstants.AUTOMATION_MAPPER_KEY, () -> this); 086 automatic.put("CurrentUser", () -> new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal())); 087 automatic.put("currentUser", () -> new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal())); 088 automatic.put("Env", () -> Framework.getProperties()); 089 automatic.put("CurrentDate", () -> new DateWrapper()); 090 // Helpers injection 091 ContextService contextService = Framework.getService(ContextService.class); 092 Map<String, ContextHelper> helperFunctions = contextService.getHelperFunctions(); 093 for (String helperFunctionsId : helperFunctions.keySet()) { 094 automatic.put(helperFunctionsId, () -> helperFunctions.get(helperFunctionsId)); 095 } 096 } 097 098 public void flush() { 099 wrapped.forEach((k, v) -> ctx.put(k, unwrap(v))); 100 wrapped.clear(); 101 } 102 103 public Object unwrap(Object wrapped) { 104 return DocumentScriptingWrapper.unwrap(wrapped); 105 } 106 107 public Object wrap(Object unwrapped) { 108 return DocumentScriptingWrapper.wrap(unwrapped, this); 109 } 110 111 public Object executeOperation(String opId, Object input, ScriptObjectMirror parameters) throws Exception { 112 flush(); 113 ctx.setInput(input = DocumentScriptingWrapper.unwrap(input)); 114 AutomationService automation = Framework.getService(AutomationService.class); 115 Class<?> typeof = input == null ? Void.TYPE : input.getClass(); 116 OperationParameters args = new OperationParameters(opId, DocumentScriptingWrapper.unwrap(parameters)); 117 Object output = automation.compileChain(typeof, args).invoke(ctx); 118 return wrap(output); 119 } 120 121 @Override 122 public int size() { 123 return Stream 124 .concat(automatic.keySet().stream(), Stream.concat(bindings.keySet().stream(), ctx.keySet().stream())) 125 .distinct().collect(Collectors.counting()).intValue(); 126 } 127 128 @Override 129 public boolean isEmpty() { 130 return false; 131 } 132 133 @Override 134 public boolean containsKey(Object key) { 135 return automatic.containsKey(key) || bindings.containsKey(key) || ctx.containsKey(key); 136 } 137 138 @Override 139 public boolean containsValue(Object value) { 140 return automatic.containsValue(value) || bindings.containsValue(value) || ctx.containsValue(value); 141 } 142 143 @Override 144 public Object get(Object key) { 145 return automatic.getOrDefault(key, 146 () -> bindings.computeIfAbsent((String) key, k -> wrap(ctx.get(k)))) 147 .get(); 148 } 149 150 @Override 151 public Object put(String key, Object value) { 152 bindings.put(key, value); 153 wrapped.put(key, value); 154 return value; 155 } 156 157 @Override 158 public Object remove(Object key) { 159 Object wrapped = bindings.remove(key); 160 Object unwrapped = ctx.remove(key); 161 if (wrapped == null) { 162 wrapped = wrap(unwrapped); 163 } 164 return wrapped; 165 } 166 167 @Override 168 public void putAll(Map<? extends String, ? extends Object> m) { 169 bindings.putAll(m); 170 wrapped.putAll(m); 171 } 172 173 @Override 174 public void clear() { 175 bindings.clear(); 176 wrapped.clear(); 177 ctx.clear(); 178 } 179 180 @Override 181 public Set<String> keySet() { 182 throw new UnsupportedOperationException(); 183 } 184 185 @Override 186 public Collection<Object> values() { 187 throw new UnsupportedOperationException(); 188 } 189 190 @Override 191 public Set<java.util.Map.Entry<String, Object>> entrySet() { 192 throw new UnsupportedOperationException(); 193 } 194 195 @Override 196 public Object getOrDefault(Object key, Object defaultValue) { 197 return Optional.ofNullable(get(key)).orElse(defaultValue); 198 } 199 200 @Override 201 public void forEach(BiConsumer<? super String, ? super Object> action) { 202 throw new UnsupportedOperationException(); 203 } 204 205 @Override 206 public void replaceAll(BiFunction<? super String, ? super Object, ? extends Object> function) { 207 throw new UnsupportedOperationException(); 208 } 209 210 @Override 211 public Object putIfAbsent(String key, Object value) { 212 throw new UnsupportedOperationException(); 213 } 214 215 @Override 216 public boolean remove(Object key, Object value) { 217 throw new UnsupportedOperationException(); 218 } 219 220 @Override 221 public boolean replace(String key, Object oldValue, Object newValue) { 222 throw new UnsupportedOperationException(); 223 } 224 225 @Override 226 public Object replace(String key, Object value) { 227 throw new UnsupportedOperationException(); 228 } 229 230 @Override 231 public Object computeIfAbsent(String key, Function<? super String, ? extends Object> mappingFunction) { 232 throw new UnsupportedOperationException(); 233 } 234 235 @Override 236 public Object computeIfPresent(String key, 237 BiFunction<? super String, ? super Object, ? extends Object> remappingFunction) { 238 throw new UnsupportedOperationException(); 239 } 240 241 @Override 242 public Object compute(String key, BiFunction<? super String, ? super Object, ? extends Object> remappingFunction) { 243 throw new UnsupportedOperationException(); 244 } 245 246 @Override 247 public Object merge(String key, Object value, 248 BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 249 throw new UnsupportedOperationException(); 250 } 251 252 public static class ScriptBuilder { 253 254 public CompiledScript build(Compilable compilable) throws ScriptException { 255 return compilable.compile(source()); 256 } 257 258 public String source() { 259 StringBuffer sb = new StringBuffer(); 260 AutomationService as = Framework.getService(AutomationService.class); 261 Map<String, List<String>> opMap = new HashMap<>(); 262 List<String> flatOps = new ArrayList<>(); 263 List<String> ids = new ArrayList<>(); 264 for (OperationType op : as.getOperations()) { 265 ids.add(op.getId()); 266 if (op.getAliases() != null) { 267 Collections.addAll(ids, op.getAliases()); 268 } 269 } 270 // Create js object related to operation categories 271 for (String id : ids) { 272 parseAutomationIDSForScripting(opMap, flatOps, id); 273 } 274 for (String obName : opMap.keySet()) { 275 List<String> ops = opMap.get(obName); 276 sb.append("\nvar ").append(obName).append("={};"); 277 for (String opId : ops) { 278 generateFunction(sb, opId); 279 } 280 } 281 for (String opId : flatOps) { 282 generateFlatFunction(sb, opId); 283 } 284 return sb.toString(); 285 } 286 287 protected void parseAutomationIDSForScripting(Map<String, List<String>> opMap, List<String> flatOps, 288 String id) { 289 if (id.split("\\.").length > 2) { 290 return; 291 } 292 int idx = id.indexOf("."); 293 if (idx > 0) { 294 String obName = id.substring(0, idx); 295 List<String> ops = opMap.get(obName); 296 if (ops == null) { 297 ops = new ArrayList<>(); 298 } 299 ops.add(id); 300 opMap.put(obName, ops); 301 } else { 302 // Flat operation: no need of category 303 flatOps.add(id); 304 } 305 } 306 307 protected void generateFunction(StringBuffer sb, String opId) { 308 sb.append("\n" + replaceDashByUnderscore(opId) + " = function(input,params) {"); 309 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 310 sb.append("\n};"); 311 } 312 313 protected void generateFlatFunction(StringBuffer sb, String opId) { 314 sb.append("\nvar " + replaceDashByUnderscore(opId) + " = function(input,params) {"); 315 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 316 sb.append("\n};"); 317 } 318 319 protected String replaceDashByUnderscore(String id) { 320 return id.replaceAll("[\\s\\-()]", "_"); 321 } 322 323 } 324 325}