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