Java tutorial
/** * Copyright (c) 2014-2015 Eclectic Logic LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.eclecticlogic.pedal.loader.impl; import groovy.lang.Binding; import groovy.lang.Closure; import groovy.lang.GroovyShell; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import javax.persistence.EntityManager; import org.apache.commons.io.IOUtils; import com.eclecticlogic.pedal.loader.LoaderExecutor; import com.eclecticlogic.pedal.loader.Script; /** * @author kabram. */ public class ScriptExecutor implements LoaderExecutor { private String scriptDirectory; private Stack<ScriptContext> scriptContextStack = new Stack<>(); private Stack<Map<String, Object>> scriptInputs = new Stack<>(); // used to pass inputs to scripts when defined using withInput private Map<String, Object> inputs = new HashMap<>(); private EntityManager entityManager; private Map<String, Closure<Object>> customMethods; public ScriptExecutor(EntityManager manager) { this.entityManager = manager; } public void setScriptDirectory(String scriptDirectory) { this.scriptDirectory = scriptDirectory; } public void setInputs(Map<String, Object> inputs) { this.inputs = inputs; } public void setCustomMethods(Map<String, Closure<Object>> customMethods) { this.customMethods = customMethods; } @Override public Map<String, Object> load(String loadScript, String... additionalScripts) { List<String> scripts = new ArrayList<>(); scripts.add(loadScript); if (additionalScripts != null) { for (int i = 0; i < additionalScripts.length; i++) { scripts.add(additionalScripts[i]); } } return load(scripts); } @Override public Map<String, Object> load(Script script, Script... additionalScripts) { List<Script> scripts = new ArrayList<>(); scripts.add(script); if (additionalScripts != null) { for (int i = 0; i < additionalScripts.length; i++) { scripts.add(additionalScripts[i]); } } return loadNamespaced(scripts); } @Override public Map<String, Object> load(Collection<String> loadScripts) { List<Script> scripts = new ArrayList<>(); for (String script : loadScripts) { scripts.add(Script.script(script)); } return loadNamespaced(scripts); } @Override public Map<String, Object> loadNamespaced(Collection<Script> scripts) { Map<String, Object> variables = new HashMap<>(); // Add overall variables Map<String, Object> inputVariables = scriptInputs.isEmpty() ? inputs : scriptInputs.pop(); for (Entry<String, Object> var : inputVariables.entrySet()) { variables.put(var.getKey(), var.getValue()); } for (Script script : scripts) { NamespacedBinding binding = create(); // Bind inputs. for (String key : variables.keySet()) { binding.setVariable(key, variables.get(key)); } binding.startCapture(); String filename = scriptDirectory == null || scriptDirectory.trim().length() == 0 ? script.getName() : scriptDirectory + File.separator + script.getName(); try (InputStream stream = Thread.currentThread().getContextClassLoader() .getResourceAsStream(filename)) { List<String> lines = IOUtils.readLines(stream); StringBuilder builder = new StringBuilder(); for (String line : lines) { builder.append(line).append("\n"); } execute(builder.toString(), binding); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } // Add output variables if (script.getNamespace() == null || script.getNamespace().trim().length() == 0) { for (String key : binding.getNamespacedVariables().keySet()) { variables.put(key, binding.getNamespacedVariables().get(key)); } } else { variables.put(script.getNamespace(), binding.getNamespacedVariables()); } } return variables; } @SuppressWarnings("serial") private NamespacedBinding create() { Closure<List<Object>> table = new Closure<List<Object>>(this) { @SuppressWarnings("unchecked") @Override public List<Object> call(Object... args) { if (args == null || args.length != 3) { throw new RuntimeException("The table method expects JPA entity class reference, " + "list of bean properties and a closure"); } return invokeWithClosure((Class<?>) args[0], (List<String>) args[1], (Closure<Void>) args[2]); } }; Closure<Object> row = new Closure<Object>(this) { @Override public Object call(Object... args) { return invokeRowClosure(args); } }; Closure<Object> defaultRow = new Closure<Object>(this) { @SuppressWarnings("unchecked") @Override public Object call(Object... args) { invokeDefaultRowClosure((Closure<Object>) args[0]); return null; } }; Closure<Object> find = new Closure<Object>(this) { @SuppressWarnings("unchecked") @Override public Object call(Object... args) { return invokeFindClosure((Class<? extends Serializable>) args[0], (Serializable) args[1]); }; }; Closure<Object> withInput = new Closure<Object>(this) { @Override public Object call(Object... args) { return invokeWithInputClosure(args[0]); }; }; Closure<Object> load = new Closure<Object>(this) { @Override public Object call(Object... args) { return invokeLoadClosure(args); }; }; Closure<Object> flush = new Closure<Object>(this) { @Override public Object call(Object... args) { entityManager.flush(); return null; }; }; NamespacedBinding binding = new NamespacedBinding(); binding.setVariable("table", table); binding.setVariable("row", row); binding.setVariable("defaultRow", defaultRow); binding.setVariable("find", find); binding.setVariable("withInput", withInput); binding.setVariable("load", load); binding.setVariable("flush", flush); // Custom methods for (Entry<String, Closure<Object>> entry : customMethods.entrySet()) { binding.setVariable(entry.getKey(), entry.getValue()); } return binding; } @SuppressWarnings("unchecked") public Map<String, Object> execute(String script, Binding binding) { GroovyShell shell = new GroovyShell(getClass().getClassLoader(), binding); shell.evaluate(script); return binding.getVariables(); } private <V> List<Object> invokeWithClosure(Class<?> clz, List<String> attributes, Closure<V> callable) { ScriptContext context = new ScriptContext(); context.setEntityClass(clz); context.setAttributes(attributes); scriptContextStack.push(context); callable.call(); scriptContextStack.pop(); return context.getCreatedEntities(); } private void invokeDefaultRowClosure(Closure<Object> closure) { scriptContextStack.peek().setDefaultRowClosure(closure); } private Object invokeRowClosure(Object... attributeValues) { Serializable instance = instantiate(); DelegatingGroovyObjectSupport<Serializable> delegate = new DelegatingGroovyObjectSupport<Serializable>( instance); for (int i = 0; i < scriptContextStack.peek().getAttributes().size(); i++) { delegate.setProperty(scriptContextStack.peek().getAttributes().get(i), attributeValues[i]); } scriptContextStack.peek().getDefaultRowClosure().call(instance); entityManager.persist(instance); scriptContextStack.peek().getCreatedEntities().add(instance); return instance; } private Object invokeFindClosure(Class<? extends Serializable> clz, Serializable id) { return entityManager.find(clz, id); } @SuppressWarnings("unchecked") private Object invokeWithInputClosure(Object arg) { Map<String, Object> scriptInput = (Map<String, Object>) arg; scriptInputs.push(scriptInput); // Return a constrained set of operations that are possible. Returning owner will result in the entire set // of methods in this class being exposed. return new Object() { @SuppressWarnings("unused") public Map<String, Object> load(String loadScript, String... additionalScripts) { return ScriptExecutor.this.load(loadScript, additionalScripts); } @SuppressWarnings("unused") public Map<String, Object> load(Map<String, String> namespacedScripts) { List<Script> scripts = new ArrayList<>(); for (Entry<String, String> entry : namespacedScripts.entrySet()) { scripts.add(Script.with(entry.getValue(), entry.getKey())); } return ScriptExecutor.this.loadNamespaced(scripts); } }; } @SuppressWarnings("unchecked") private Object invokeLoadClosure(Object[] args) { if (args.length == 0) { throw new IllegalArgumentException("The load() method should be called with a map of namespace and " + "script names or a list of one or more script names."); } else if (args[0] instanceof Map) { Map<String, String> scriptMap = (Map<String, String>) args[0]; List<Script> scripts = new ArrayList<>(); for (String namespace : scriptMap.keySet()) { scripts.add(Script.with(scriptMap.get(namespace), namespace)); } return loadNamespaced(scripts); } else { // Assuming these are simply script names. List<String> scripts = new ArrayList<>(); for (int i = 0; i < args.length; i++) { scripts.add((String) args[i]); } return load(scripts); } } private Serializable instantiate() { try { return (Serializable) scriptContextStack.peek().getEntityClass().newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e.getMessage(), e); } } }