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 <[email protected]> 018 * Vladimir Pasquier <[email protected]> 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(), Stream.concat(bindings.keySet().stream(), ctx.keySet().stream())) 124 .distinct().collect(Collectors.counting()).intValue(); 125 } 126 127 @Override 128 public boolean isEmpty() { 129 return false; 130 } 131 132 @Override 133 public boolean containsKey(Object key) { 134 return automatic.containsKey(key) || bindings.containsKey(key) || ctx.containsKey(key); 135 } 136 137 @Override 138 public boolean containsValue(Object value) { 139 return automatic.containsValue(value) || bindings.containsValue(value) || ctx.containsValue(value); 140 } 141 142 @Override 143 public Object get(Object key) { 144 return automatic.getOrDefault(key, 145 () -> bindings.computeIfAbsent((String) key, k -> wrap(ctx.get(k)))) 146 .get(); 147 } 148 149 @Override 150 public Object put(String key, Object value) { 151 bindings.put(key, value); 152 wrapped.put(key, value); 153 return value; 154 } 155 156 @Override 157 public Object remove(Object key) { 158 Object wrapped = bindings.remove(key); 159 Object unwrapped = ctx.remove(key); 160 if (wrapped == null) { 161 wrapped = wrap(unwrapped); 162 } 163 return wrapped; 164 } 165 166 @Override 167 public void putAll(Map<? extends String, ? extends Object> m) { 168 bindings.putAll(m); 169 wrapped.putAll(m); 170 } 171 172 @Override 173 public void clear() { 174 bindings.clear(); 175 wrapped.clear(); 176 ctx.clear(); 177 } 178 179 @Override 180 public Set<String> keySet() { 181 throw new UnsupportedOperationException(); 182 } 183 184 @Override 185 public Collection<Object> values() { 186 throw new UnsupportedOperationException(); 187 } 188 189 @Override 190 public Set<java.util.Map.Entry<String, Object>> entrySet() { 191 throw new UnsupportedOperationException(); 192 } 193 194 @Override 195 public Object getOrDefault(Object key, Object defaultValue) { 196 return Optional.ofNullable(get(key)).orElse(defaultValue); 197 } 198 199 @Override 200 public void forEach(BiConsumer<? super String, ? super Object> action) { 201 throw new UnsupportedOperationException(); 202 } 203 204 @Override 205 public void replaceAll(BiFunction<? super String, ? super Object, ? extends Object> function) { 206 throw new UnsupportedOperationException(); 207 } 208 209 @Override 210 public Object putIfAbsent(String key, Object value) { 211 throw new UnsupportedOperationException(); 212 } 213 214 @Override 215 public boolean remove(Object key, Object value) { 216 throw new UnsupportedOperationException(); 217 } 218 219 @Override 220 public boolean replace(String key, Object oldValue, Object newValue) { 221 throw new UnsupportedOperationException(); 222 } 223 224 @Override 225 public Object replace(String key, Object value) { 226 throw new UnsupportedOperationException(); 227 } 228 229 @Override 230 public Object computeIfAbsent(String key, Function<? super String, ? extends Object> mappingFunction) { 231 throw new UnsupportedOperationException(); 232 } 233 234 @Override 235 public Object computeIfPresent(String key, 236 BiFunction<? super String, ? super Object, ? extends Object> remappingFunction) { 237 throw new UnsupportedOperationException(); 238 } 239 240 @Override 241 public Object compute(String key, BiFunction<? super String, ? super Object, ? extends Object> remappingFunction) { 242 throw new UnsupportedOperationException(); 243 } 244 245 @Override 246 public Object merge(String key, Object value, 247 BiFunction<? super Object, ? super Object, ? extends Object> remappingFunction) { 248 throw new UnsupportedOperationException(); 249 } 250 251 public static class ScriptBuilder { 252 253 public CompiledScript build(Compilable compilable) throws ScriptException { 254 return compilable.compile(source()); 255 } 256 257 public String source() { 258 StringBuffer sb = new StringBuffer(); 259 AutomationService as = Framework.getService(AutomationService.class); 260 Map<String, List<String>> opMap = new HashMap<>(); 261 List<String> flatOps = new ArrayList<>(); 262 List<String> ids = new ArrayList<>(); 263 for (OperationType op : as.getOperations()) { 264 ids.add(op.getId()); 265 if (op.getAliases() != null) { 266 Collections.addAll(ids, op.getAliases()); 267 } 268 } 269 // Create js object related to operation categories 270 for (String id : ids) { 271 parseAutomationIDSForScripting(opMap, flatOps, id); 272 } 273 for (String obName : opMap.keySet()) { 274 List<String> ops = opMap.get(obName); 275 sb.append("\nvar ").append(obName).append("={};"); 276 for (String opId : ops) { 277 generateFunction(sb, opId); 278 } 279 } 280 for (String opId : flatOps) { 281 generateFlatFunction(sb, opId); 282 } 283 return sb.toString(); 284 } 285 286 protected void parseAutomationIDSForScripting(Map<String, List<String>> opMap, List<String> flatOps, 287 String id) { 288 if (id.split("\\.").length > 2) { 289 return; 290 } 291 int idx = id.indexOf("."); 292 if (idx > 0) { 293 String obName = id.substring(0, idx); 294 List<String> ops = opMap.get(obName); 295 if (ops == null) { 296 ops = new ArrayList<>(); 297 } 298 ops.add(id); 299 opMap.put(obName, ops); 300 } else { 301 // Flat operation: no need of category 302 flatOps.add(id); 303 } 304 } 305 306 protected void generateFunction(StringBuffer sb, String opId) { 307 sb.append("\n" + replaceDashByUnderscore(opId) + " = function(input,params) {"); 308 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 309 sb.append("\n};"); 310 } 311 312 protected void generateFlatFunction(StringBuffer sb, String opId) { 313 sb.append("\nvar " + replaceDashByUnderscore(opId) + " = function(input,params) {"); 314 sb.append("\nreturn automation.executeOperation('" + opId + "', input , params);"); 315 sb.append("\n};"); 316 } 317 318 protected String replaceDashByUnderscore(String id) { 319 return id.replaceAll("[\\s\\-()]", "_"); 320 } 321 322 } 323 324}