/*
* Copyright (c) 2012 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.scripting.scripts.groovy;
import javax.script.ScriptException;
import org.springframework.core.convert.ConversionException;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import eu.esdihumboldt.hale.common.align.model.ChildContext;
import eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition;
import eu.esdihumboldt.hale.common.align.transformation.function.PropertyValue;
import eu.esdihumboldt.hale.common.core.service.ServiceProvider;
import eu.esdihumboldt.hale.common.instance.model.Instance;
import eu.esdihumboldt.hale.common.scripting.Script;
import eu.esdihumboldt.util.groovy.sandbox.GroovyService;
import eu.esdihumboldt.util.groovy.sandbox.GroovyService.ResultProcessor;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.MissingPropertyException;
/**
* Groovy script implementation.
*
* @author Kai Schwierczek
*/
public class GroovyScript implements Script {
/**
* ID that identifies Groovy scripts.
*/
public static final String GROOVY_SCRIPT_ID = "eu.esdihumboldt.hale.common.scripting.groovy";
/**
* @see Script#evaluate(String, Iterable, ServiceProvider)
*/
@Override
public Object evaluate(String script, Iterable<PropertyValue> variables,
ServiceProvider provider) throws ScriptException {
Binding binding = createGroovyBinding(variables, true);
GroovyService service = provider.getService(GroovyService.class);
Object result;
try {
result = service.evaluate(service.parseScript(script, binding),
new ResultProcessor<Object>() {
@Override
public Object process(groovy.lang.Script script, Object returnValue)
throws Exception {
return returnValue;
}
});
} catch (Exception e) {
throw new ScriptException(e);
} catch (Throwable t) {
throw new ScriptException(t.toString());
}
if (result == null) {
// XXX throw new NoResultException(); ? as cause for SE?
throw new ScriptException("no result");
}
return result;
}
/**
* Create a Groovy binding from the list of variables.
*
* FIXME why is here an additional implementation of this as already used in
* GroovyTransformation? Could the implementation in GroovyTransformation be
* used instead? FIXME It could (should?) only be the other way around
* (because of dependencies); or it should be at another "common"-place
*
* @param variables the variables
* @param useNullForMissingBindings if the binding should provide
* <code>null</code> values for variables that are not provided
* in the given variable list
* @return the binding for use with {@link GroovyShell}
*/
private Binding createGroovyBinding(Iterable<PropertyValue> variables,
boolean useNullForMissingBindings) {
Binding binding;
if (useNullForMissingBindings) {
binding = new Binding() {
@Override
public Object getVariable(String name) {
try {
return super.getVariable(name);
} catch (MissingPropertyException mpe) {
// use null value for variables that are not defined
return null;
}
}
};
}
else
binding = new Binding();
for (PropertyValue var : variables) {
// add the variable to the environment
// determine the variable value
Object value = var.getValue();
if (value instanceof Instance) {
value = ((Instance) value).getValue(); // XXX check if there are
// any properties?
}
if (value instanceof Number) {
// use numbers as is
}
else {
// try conversion to String as default
try {
value = var.getValueAs(String.class);
} catch (ConversionException ce) {
// XXX currently ignored conversion exception
continue;
}
}
// add with short name, if it does not override something
String name = var.getProperty().getDefinition().getName().getLocalPart();
if (!binding.getVariables().containsKey(name))
binding.setVariable(name, value);
// add with full name
binding.setVariable(getVariableName(var.getProperty()), value);
}
return binding;
}
/**
* @see eu.esdihumboldt.hale.common.scripting.Script#getVariableName(eu.esdihumboldt.hale.common.align.model.impl.PropertyEntityDefinition)
*/
@Override
public String getVariableName(PropertyEntityDefinition entityDefinition) {
return Joiner.on('_').join(
Lists.transform(entityDefinition.getPropertyPath(),
new Function<ChildContext, String>() {
/**
* @see com.google.common.base.Function#apply(java.lang.Object)
*/
@Override
public String apply(ChildContext input) {
return input.getChild().getName().getLocalPart();
}
}));
}
/**
* @see Script#validate(String, Iterable, ServiceProvider)
*/
@Override
public String validate(String script, Iterable<PropertyValue> variables,
ServiceProvider provider) {
Binding binding = createGroovyBinding(variables, false);
GroovyService service = provider.getService(GroovyService.class);
try {
service.evaluate(service.parseScript(script, binding), null);
} catch (Exception e) {
return e.getLocalizedMessage();
}
return null;
}
/**
* @see eu.esdihumboldt.hale.common.scripting.Script#getId()
*/
@Override
public String getId() {
return GROOVY_SCRIPT_ID;
}
}