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