/*
* Copyright (c) 2015 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.hale.common.align.custom.groovy;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import eu.esdihumboldt.cst.functions.groovy.GroovyTransformation;
import eu.esdihumboldt.cst.functions.groovy.internal.GroovyUtil;
import eu.esdihumboldt.cst.functions.groovy.internal.InstanceAccessorArrayList;
import eu.esdihumboldt.hale.common.align.custom.CustomPropertyFunctionType;
import eu.esdihumboldt.hale.common.align.custom.DefaultCustomPropertyFunction;
import eu.esdihumboldt.hale.common.align.custom.DefaultCustomPropertyFunctionEntity;
import eu.esdihumboldt.hale.common.align.extension.function.ParameterDefinition;
import eu.esdihumboldt.hale.common.align.model.Cell;
import eu.esdihumboldt.hale.common.align.model.ParameterValue;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.model.impl.mdexpl.ParameterBinding;
import eu.esdihumboldt.hale.common.align.transformation.engine.TransformationEngine;
import eu.esdihumboldt.hale.common.align.transformation.function.ExecutionContext;
import eu.esdihumboldt.hale.common.align.transformation.function.PropertyTransformation;
import eu.esdihumboldt.hale.common.align.transformation.function.PropertyValue;
import eu.esdihumboldt.hale.common.align.transformation.function.TransformationException;
import eu.esdihumboldt.hale.common.align.transformation.function.impl.AbstractSingleTargetPropertyTransformation;
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.Value;
import eu.esdihumboldt.hale.common.instance.groovy.InstanceBuilder;
import eu.esdihumboldt.hale.common.schema.model.TypeDefinition;
import eu.esdihumboldt.util.groovy.sandbox.GroovyService;
import groovy.lang.Binding;
import groovy.lang.Script;
/**
* Implementation of a custom transformation as Groovy transformation.
*
* @author Simon Templer
*/
@SuppressWarnings("restriction")
public class CustomGroovyTransformation
extends AbstractSingleTargetPropertyTransformation<TransformationEngine>
implements PropertyTransformation<TransformationEngine> {
/**
* Binding parameter name for function parameters.
*/
public static final String BINDING_PARAMS = "_params";
private final DefaultCustomPropertyFunction customFunction;
/**
* @param customFunction the custom function definition
*/
public CustomGroovyTransformation(DefaultCustomPropertyFunction customFunction) {
this.customFunction = customFunction;
}
@Override
protected Object evaluate(String transformationIdentifier, TransformationEngine engine,
ListMultimap<String, PropertyValue> variables, String resultName,
PropertyEntityDefinition resultProperty, Map<String, String> executionParameters,
TransformationLog log) throws TransformationException, NoResultException {
// store script as parameter
if (!CustomPropertyFunctionType.GROOVY.equals(customFunction.getFunctionType())) {
throw new TransformationException("Custom function is not of type groovy");
}
Value scriptValue = customFunction.getFunctionDefinition();
if (scriptValue.isEmpty()) {
throw new NoResultException("Script not defined");
}
ListMultimap<String, ParameterValue> params = ArrayListMultimap.create();
params.put(GroovyTransformation.PARAMETER_SCRIPT, new ParameterValue(scriptValue));
setParameters(params);
// instance builder
InstanceBuilder builder = GroovyTransformation.createBuilder(resultProperty);
// create the script binding
Binding binding = createGroovyBinding(variables, getCell(), getTypeCell(), builder, log,
getExecutionContext(), resultProperty.getDefinition().getPropertyType());
Object result;
try {
GroovyService service = getExecutionContext().getService(GroovyService.class);
Script groovyScript = GroovyUtil.getScript(this, binding, service, true);
// evaluate the script
result = GroovyTransformation.evaluate(groovyScript, builder,
resultProperty.getDefinition().getPropertyType(), service);
} catch (TransformationException | NoResultException e) {
throw e;
} catch (Throwable e) {
throw new TransformationException("Error evaluating the custom function script", e);
}
if (result == null) {
throw new NoResultException();
}
return result;
}
private Binding createGroovyBinding(ListMultimap<String, PropertyValue> variables, Cell cell,
Cell typeCell, InstanceBuilder builder, TransformationLog log,
ExecutionContext executionContext, TypeDefinition targetInstanceType) {
Binding binding = GroovyUtil.createBinding(builder, cell, typeCell, log, executionContext,
targetInstanceType);
// create bindings for inputs
for (DefaultCustomPropertyFunctionEntity source : customFunction.getSources()) {
String varName = source.getName();
boolean useInstanceVariable = useInstanceVariableForSource(source);
List<PropertyValue> values = variables.get(varName);
if (source.isEager() || source.getMaxOccurrence() > 1
|| source.getMaxOccurrence() == ParameterDefinition.UNBOUNDED) {
// multiple values
InstanceAccessorArrayList<Object> valueList = new InstanceAccessorArrayList<>();
for (PropertyValue value : values) {
valueList.add(GroovyTransformation.getUseValue(value.getValue(),
useInstanceVariable));
}
binding.setVariable(varName, valueList);
}
else {
// single value
if (values.isEmpty()) {
// no value
// -> use null value for missing variable
binding.setVariable(varName, null);
}
else {
// value
binding.setVariable(varName, GroovyTransformation
.getUseValue(values.get(0).getValue(), useInstanceVariable));
}
}
}
// create binding(s) for parameters
binding.setVariable(BINDING_PARAMS,
new ParameterBinding(cell, customFunction.getDescriptor()));
return binding;
}
/**
* Determine if for a given input variable an instance value should be used.
*
* @param source the input variable definition
* @return if instance values should be used
*/
public static boolean useInstanceVariableForSource(DefaultCustomPropertyFunctionEntity source) {
if (source.getBindingClass() == null && source.getBindingType() == null) {
// no setting for source at all -> can be anything -> use instance
return true;
}
boolean sourceHasChildren = source.getBindingType() != null
&& !source.getBindingType().getChildren().isEmpty();
return sourceHasChildren;
}
}