001/* 002 * Copyright (c) 2006-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 Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * bstefanescu, jcarsique 011 */ 012package org.nuxeo.ecm.automation.core.scripting; 013 014import groovy.lang.Binding; 015 016import java.io.ByteArrayOutputStream; 017import java.io.IOException; 018import java.io.InputStream; 019import java.io.PrintStream; 020import java.io.Serializable; 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.concurrent.ConcurrentHashMap; 027 028import org.apache.commons.io.Charsets; 029import org.apache.commons.io.IOUtils; 030import org.mvel2.MVEL; 031 032import org.nuxeo.ecm.automation.OperationContext; 033import org.nuxeo.ecm.automation.OperationException; 034import org.nuxeo.ecm.automation.context.ContextService; 035import org.nuxeo.ecm.automation.core.Constants; 036import org.nuxeo.ecm.core.api.DocumentModel; 037import org.nuxeo.ecm.core.api.DocumentModelList; 038import org.nuxeo.ecm.core.api.NuxeoPrincipal; 039import org.nuxeo.runtime.api.Framework; 040 041/** 042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 043 */ 044public class Scripting { 045 046 protected static final Map<String, Script> cache = new ConcurrentHashMap<>(); 047 048 protected static final GroovyScripting gscripting = new GroovyScripting(); 049 050 public static Expression newExpression(String expr) { 051 return new MvelExpression(expr); 052 } 053 054 public static Expression newTemplate(String expr) { 055 return new MvelTemplate(expr); 056 } 057 058 public static void run(OperationContext ctx, URL script) throws OperationException, IOException { 059 String key = script.toExternalForm(); 060 Script cs = cache.get(key); 061 if (cs != null) { 062 cs.eval(ctx); 063 return; 064 } 065 String path = script.getPath(); 066 int p = path.lastIndexOf('.'); 067 if (p == -1) { 068 throw new OperationException("Script files must have an extension: " + script); 069 } 070 String ext = path.substring(p + 1).toLowerCase(); 071 try (InputStream in = script.openStream()) { 072 if ("mvel".equals(ext)) { 073 Serializable c = MVEL.compileExpression(IOUtils.toString(in, Charsets.UTF_8)); 074 cs = new MvelScript(c); 075 } else if ("groovy".equals(ext)) { 076 cs = new GroovyScript(IOUtils.toString(in, Charsets.UTF_8)); 077 } else { 078 throw new OperationException("Unsupported script file: " + script 079 + ". Only MVEL and Groovy scripts are supported"); 080 } 081 cache.put(key, cs); 082 cs.eval(ctx); 083 } 084 } 085 086 public static Map<String, Object> initBindings(OperationContext ctx) { 087 Object input = ctx.getInput(); // get last output 088 Map<String, Object> map = new HashMap<>(ctx); 089 map.put("CurrentDate", new DateWrapper()); 090 map.put("Context", ctx); 091 if (ctx.get(Constants.VAR_WORKFLOW) != null) { 092 map.put(Constants.VAR_WORKFLOW, ctx.get(Constants.VAR_WORKFLOW)); 093 } 094 if (ctx.get(Constants.VAR_WORKFLOW_NODE) != null) { 095 map.put(Constants.VAR_WORKFLOW_NODE, ctx.get(Constants.VAR_WORKFLOW_NODE)); 096 } 097 map.put("This", input); 098 map.put("Session", ctx.getCoreSession()); 099 PrincipalWrapper principalWrapper = new PrincipalWrapper((NuxeoPrincipal) ctx.getPrincipal()); 100 map.put("CurrentUser", principalWrapper); 101 // Alias 102 map.put("currentUser", principalWrapper); 103 map.put("Env", Framework.getProperties()); 104 105 // Helpers injection 106 ContextService contextService = Framework.getService(ContextService.class); 107 map.putAll(contextService.getHelperFunctions()); 108 109 if (input instanceof DocumentModel) { 110 DocumentWrapper documentWrapper = new DocumentWrapper(ctx.getCoreSession(), (DocumentModel) input); 111 map.put("Document", documentWrapper); 112 // Alias 113 map.put("currentDocument", documentWrapper); 114 } 115 if (input instanceof DocumentModelList) { 116 List<DocumentWrapper> docs = new ArrayList<>(); 117 for (DocumentModel doc : (DocumentModelList) input) { 118 docs.add(new DocumentWrapper(ctx.getCoreSession(), doc)); 119 } 120 map.put("Documents", docs); 121 if (docs.size() >= 1) { 122 map.put("Document", docs.get(0)); 123 } 124 } 125 return map; 126 } 127 128 public interface Script { 129 // protected long lastModified; 130 Object eval(OperationContext ctx); 131 } 132 133 public static class MvelScript implements Script { 134 final Serializable c; 135 136 public static MvelScript compile(String script) { 137 return new MvelScript(MVEL.compileExpression(script)); 138 } 139 140 public MvelScript(Serializable c) { 141 this.c = c; 142 } 143 144 @Override 145 public Object eval(OperationContext ctx) { 146 return MVEL.executeExpression(c, Scripting.initBindings(ctx)); 147 } 148 } 149 150 public static class GroovyScript implements Script { 151 final groovy.lang.Script c; 152 153 public GroovyScript(String c) { 154 this.c = gscripting.getScript(c, new Binding()); 155 } 156 157 @Override 158 public Object eval(OperationContext ctx) { 159 Binding binding = new Binding(); 160 for (Map.Entry<String, Object> entry : initBindings(ctx).entrySet()) { 161 binding.setVariable(entry.getKey(), entry.getValue()); 162 } 163 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 164 binding.setVariable("out", new PrintStream(baos)); 165 c.setBinding(binding); 166 c.run(); 167 return baos; 168 } 169 } 170 171}