/*
* Copyright (c) 2013 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.cst.functions.groovy.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import eu.esdihumboldt.cst.MultiValue;
import eu.esdihumboldt.cst.functions.groovy.GroovyConstants;
import eu.esdihumboldt.cst.functions.groovy.helper.DefaultHelperContext;
import eu.esdihumboldt.cst.functions.groovy.helper.HelperContext;
import eu.esdihumboldt.cst.functions.groovy.helper.HelperFunctions;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.CellUtil;
import eu.esdihumboldt.hale.common.align.model.Entity;
import eu.esdihumboldt.hale.common.align.model.ParameterValue;
import eu.esdihumboldt.hale.common.align.model.Type;
import eu.esdihumboldt.hale.common.align.model.impl.TypeEntityDefinition;
import eu.esdihumboldt.hale.common.align.transformation.function.ExecutionContext;
import eu.esdihumboldt.hale.common.align.transformation.function.TransformationException;
import eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractTransformationFunction;
import eu.esdihumboldt.hale.common.align.transformation.function.impl.NoResultException;
import eu.esdihumboldt.hale.common.align.transformation.report.TransformationLog;
import eu.esdihumboldt.hale.common.core.io.Text;
import eu.esdihumboldt.hale.common.core.io.project.ProjectInfoService;
import eu.esdihumboldt.hale.common.instance.groovy.InstanceBuilder;
import eu.esdihumboldt.hale.common.instance.index.spatial.SpatialIndexService;
import eu.esdihumboldt.hale.common.instance.model.MutableInstance;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.util.groovy.sandbox.GroovyService;
import eu.esdihumboldt.util.groovy.sandbox.GroovyService.ResultProcessor;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import groovy.transform.CompileStatic;
/**
* Groovy function utilities.
*
* @author Simon Templer
*/
@CompileStatic
public class GroovyUtil implements GroovyConstants {
/**
* Get the script string.
*
* @param function the transformation function the script is associated to
* @return the script string
* @throws TransformationException if getting the script parameter from the
* function fails
*/
public static String getScriptString(AbstractTransformationFunction<?> function)
throws TransformationException {
ParameterValue scriptValue = function.getParameterChecked(PARAMETER_SCRIPT);
String script;
// try retrieving as text
Text text = scriptValue.as(Text.class);
if (text != null) {
script = text.getText();
}
else {
// fall back to string value
script = scriptValue.as(String.class);
}
return script;
}
/**
* Get the compiled script.
*
* @param function the transformation function the script is associated to
* @param binding the binding to set on the script
* @param service the Groovy service
* @return the script
* @throws TransformationException if getting the script parameter from the
* function fails
*/
public static Script getScript(AbstractTransformationFunction<?> function, Binding binding,
GroovyService service) throws TransformationException {
return getScript(function, binding, service, false);
}
/**
* Get the compiled script.
*
* @param function the transformation function the script is associated to
* @param binding the binding to set on the script
* @param service the Groovy service
* @param functionCached if the script should be cached per function instead
* of per cell
* @return the script
* @throws TransformationException if getting the script parameter from the
* function fails
*/
@SuppressWarnings("unchecked")
public static Script getScript(AbstractTransformationFunction<?> function, Binding binding,
GroovyService service, boolean functionCached) throws TransformationException {
/*
* The compiled script is stored in a ThreadLocal variable in the
* execution context, so it needs only to be created once per
* transformation thread.
*/
ThreadLocal<Script> localScript;
Map<Object, Object> context = (functionCached)
? (function.getExecutionContext().getFunctionContext())
: (function.getExecutionContext().getCellContext());
synchronized (context) {
Object tmp = context.get(CONTEXT_SCRIPT);
if (tmp instanceof ThreadLocal<?>) {
localScript = (ThreadLocal<Script>) tmp;
}
else {
localScript = new ThreadLocal<Script>();
context.put(CONTEXT_SCRIPT, localScript);
}
}
Script groovyScript = localScript.get();
if (groovyScript == null) {
// create the script
String script = getScriptString(function);
groovyScript = service.parseScript(script, null);
localScript.set(groovyScript);
}
groovyScript.setBinding(binding);
return groovyScript;
}
/**
* Evaluate a Groovy type script.
*
* @param script the script
* @param builder the instance builder
* @param type the type of the instance to create
* @param service the Groovy service
* @return the created instance
* @throws TransformationException if the target binding does not contain
* exactly one result after script evaluation or an internal
* error occurs
* @throws NoResultException if the script implies that no result should be
* created
*/
public static MutableInstance evaluate(Script script, final InstanceBuilder builder,
final TypeDefinition type, GroovyService service)
throws TransformationException, NoResultException {
Iterable<MutableInstance> results = evaluateAll(script, builder, type, service);
Iterator<MutableInstance> it = results.iterator();
MutableInstance result;
if (it.hasNext()) {
result = it.next();
if (it.hasNext()) {
throw new TransformationException(
"Cell script does not produce exactly one result.");
}
}
else {
throw new NoResultException();
}
return result;
}
/**
* Evaluate a Groovy type script.
*
* @param script the script
* @param builder the instance builder
* @param type the type of the instance to create
* @param service the Groovy service
* @return the created instance
* @throws TransformationException if an internal error occurs
* @throws NoResultException if the script implies that no result should be
* created
*/
public static Iterable<MutableInstance> evaluateAll(Script script,
final InstanceBuilder builder, final TypeDefinition type, GroovyService service)
throws TransformationException, NoResultException {
try {
return service.evaluate(script, new ResultProcessor<Iterable<MutableInstance>>() {
@Override
public Iterable<MutableInstance> process(Script script, Object returnValue)
throws Exception {
// get target binding
Object result = script.getBinding().getVariable(BINDING_TARGET);
// collector or closure
if (result instanceof TargetCollector) {
if (((TargetCollector) result).size() <= 0) {
// TODO warn?
}
MultiValue instances = ((TargetCollector) result).toMultiValue(builder,
type);
Collection<MutableInstance> multiResult = new ArrayList<>(instances.size());
for (Object instance : instances) {
multiResult.add((MutableInstance) instance);
}
return multiResult;
}
else {
result = builder.createInstance(type, (Closure<?>) result);
}
return Collections.singleton((MutableInstance) result);
}
});
} catch (RuntimeException | TransformationException | NoResultException e) {
throw e;
} catch (Exception e) {
throw new TransformationException(e.getMessage(), e);
}
}
/**
* Creates a basic binding used by all Groovy functions.
*
* @param builder the instance builder, may be <code>null</code>
* @param cell the cell of the function
* @param typeCell the type cell the function works on, may be
* <code>null</code>
* @param log the transformation log
* @param executionContext the execution context
* @param targetInstanceType the type of the target instance to create
* @return a basic binding
*/
public static Binding createBinding(InstanceBuilder builder, Cell cell, Cell typeCell,
TransformationLog log, ExecutionContext executionContext,
TypeDefinition targetInstanceType) {
Binding binding = new Binding();
HelperContext helperContext = new DefaultHelperContext(executionContext, executionContext,
cell, typeCell);
binding.setVariable(BINDING_HELPER_FUNCTIONS, HelperFunctions.createDefault(helperContext));
binding.setVariable(BINDING_BUILDER, builder);
binding.setVariable(BINDING_CELL, cell);
TransformationLogWrapper cellLog = new TransformationLogWrapper(log);
binding.setVariable(BINDING_LOG, cellLog);
binding.setVariable(BINDING_CELL_CONTEXT,
SynchronizedContextProvider.getContextClosure(executionContext.getCellContext()));
binding.setVariable(BINDING_FUNCTION_CONTEXT, SynchronizedContextProvider
.getContextClosure(executionContext.getFunctionContext()));
binding.setVariable(BINDING_TRANSFORMATION_CONTEXT, SynchronizedContextProvider
.getContextClosure(executionContext.getTransformationContext()));
// init type cell types
ArrayList<TypeEntityDefinition> sourceTypes = null;
TypeEntityDefinition targetType = null;
if (typeCell != null) {
targetType = ((Type) CellUtil.getFirstEntity(typeCell.getTarget())).getDefinition();
if (typeCell.getSource() != null) {
Collection<? extends Entity> sources = typeCell.getSource().values();
sourceTypes = new ArrayList<>(sources.size());
for (Object entity : sources) {
sourceTypes.add(((Type) entity).getDefinition());
}
}
}
binding.setVariable(BINDING_SOURCE_TYPES, sourceTypes);
binding.setVariable(BINDING_TARGET_TYPE, targetType);
binding.setVariable(BINDING_TARGET, new TargetCollector(builder, targetInstanceType));
binding.setVariable(BINDING_PROJECT, new ProjectAccessor(
executionContext.getService(ProjectInfoService.class), cellLog, executionContext));
binding.setVariable(BINDING_SPATIAL_INDEX,
executionContext.getService(SpatialIndexService.class));
return binding;
}
}