/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012-2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.processing.chain;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotoolkit.cql.CQL;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.filter.function.groovy.GroovyFunctionFactory;
import org.geotoolkit.filter.function.javascript.JavaScriptFunctionFactory;
import org.geotoolkit.processing.AbstractProcess;
import org.geotoolkit.processing.AbstractProcessDescriptor;
import org.geotoolkit.process.Process;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.process.ProcessFinder;
import org.geotoolkit.processing.chain.model.Chain;
import org.geotoolkit.processing.chain.model.ElementProcess;
import org.geotoolkit.processing.chain.model.Constant;
import org.geotoolkit.processing.chain.model.DataLink;
import org.geotoolkit.processing.chain.model.Element;
import org.geotoolkit.processing.chain.model.ElementCondition;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.UnconvertibleObjectException;
import org.apache.sis.util.ObjectConverter;
import org.apache.sis.util.logging.Logging;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.NoSuchIdentifierException;
/**
*
* @author Johann Sorel (Geomatys)
* @author Guilhem Legal (Geomatys)
*/
public class ChainProcess extends AbstractProcess {
protected static final Logger LOGGER = Logging.getLogger("org.geotoolkit.processing.chain");
private Process currentProcess;
public ChainProcess(final ChainProcessDescriptor desc, final ParameterValueGroup input) {
super(desc, input);
}
/**
* Returns a {@linkplain ChainProcessDescriptor descriptor} of this chain.
*/
@Override
public ChainProcessDescriptor getDescriptor() {
return (ChainProcessDescriptor)super.getDescriptor();
}
/**
* {@inheritDoc}
*/
@Override
protected void execute() throws ProcessException {
final Chain model = getDescriptor().getModel();
// processing progress
final float part = 100 / model.getElements().size();
int i = 1;
final Collection<FlowNode> nodes = Flow.createFlow(model);
List<List<FlowNode>> ranked = Flow.sortByRank(nodes);
//prepare all parameters for each process step
final Map<Integer, ParameterValueGroup> configs = new HashMap<Integer, ParameterValueGroup>();
for (FlowNode node : nodes) {
final Object obj = node.getObject();
if(obj == ElementProcess.END) {
configs.put(Integer.MAX_VALUE, outputParameters);
} else if(obj == ElementProcess.BEGIN){
// do nothing
} else if(obj instanceof ElementProcess){
final ElementProcess element = (ElementProcess) obj;
final ProcessDescriptor desc;
try {
desc = getProcessDescriptor(element);
} catch (NoSuchIdentifierException ex) {
throw new ProcessException("Sub process "+element.getAuthority()+"."+element.getCode()+" not found.", this, ex);
}
configs.put(element.getId(), desc.getInputDescriptor().createValue());
} else if (obj instanceof ElementCondition) {
final ElementCondition element = (ElementCondition) obj;
if (element.getFailed().isEmpty() || element.getSuccess().isEmpty()) {
throw new ProcessException("A conditional element should have at least one success AND one failed execution link.", this, null);
}
configs.put(element.getId(), ChainProcessDescriptor.createParams(element.getInputs(), "conditionInput", true).createValue());
}
}
//set all constant values in configurations
for(Constant cst : model.getConstants()){
//copy constant in children nodes
final Object value = ConstantUtilities.stringToValue(cst.getValue(), cst.getType());
for(DataLink link : model.getInputLinks(cst.getId())){
setValue(value, configs.get(link.getTargetId()).parameter(link.getTargetCode()));
}
}
// Will contain all the versions of processes used
final StringBuilder processVersion = new StringBuilder();
//run processes in order
for (int j = 0; j < ranked.size(); j++) {
final List<FlowNode> rank = ranked.get(j);
for(FlowNode node : rank){
final Object obj = node.getObject();
if (obj == ElementProcess.BEGIN) {
//copy input params in children nodes
for(DataLink link : model.getInputLinks(Integer.MIN_VALUE)){
final Object value = inputParameters.parameter(link.getSourceCode()).getValue();
setValue(value, configs.get(link.getTargetId()).parameter(link.getTargetCode()));
}
} else if (obj == ElementProcess.END) {
// do nothing
} else if(obj instanceof ElementProcess) {
// handle process cancel
if (isCanceled()) {
throw new ProcessException("Process Canceled by user", this, null);
}
// handle process pause
if (isPaused()) {
fireProcessPaused(descriptor.getIdentifier().getCode() + " paused", i * part);
while (isPaused()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "Interruption while process is in pause", ex);
}
}
fireProcessResumed(descriptor.getIdentifier().getCode() + " resumed", i * part);
}
//execute process
final ElementProcess element = (ElementProcess) obj;
final ParameterValueGroup config = configs.get(element.getId());
final ProcessDescriptor pdesc;
try {
pdesc = getProcessDescriptor(element);
} catch (NoSuchIdentifierException ex) {
throw new ProcessException("Sub process not found", this, ex);
}
currentProcess = pdesc.createProcess(config);
final String processId = pdesc.getIdentifier().getCode();
// Fill process version with values coming from the current process.
if (processVersion.length() > 0) {
processVersion.append(", ");
}
processVersion.append(processId).append(" ")
.append(((AbstractProcessDescriptor)currentProcess.getDescriptor()).getVersion());
final ParameterValueGroup result = currentProcess.call();
fireProgressing(pdesc.getIdentifier().getCode() + " completed", i * part, false);
i++;
//set result in children
for(DataLink link : model.getInputLinks(element.getId())){
final Object value = result.parameter(link.getSourceCode()).getValue();
setValue(value, configs.get(link.getTargetId()).parameter(link.getTargetCode()));
}
} else if (obj instanceof ElementCondition) {
final ElementCondition condition = (ElementCondition) obj;
final Boolean result = executeConditionalElement(condition, configs.get(condition.getId()));
final Chain updateModel = new Chain(model);
if (result) {
updateModel.getFlowLinks().removeAll(condition.getFailed());
} else {
updateModel.getFlowLinks().removeAll(condition.getSuccess());
}
ranked = Flow.sortByRank(Flow.createFlow(updateModel));
}
}
}
}
private boolean executeConditionalElement(final ElementCondition condition, final ParameterValueGroup inputs) throws ProcessException {
final FilterFactory ff = FactoryFinder.getFilterFactory(null);
final String statement = condition.getExpression();
final String syntax = condition.getSyntax();
Filter filter = null;
if("CQL".equalsIgnoreCase(syntax)){
try{
filter = CQL.parseFilter(statement);
}catch(Exception ex){
//maybe it's just an expression
}
if(filter == null){
try{
final Expression exp = CQL.parseExpression(statement);
filter = ff.equals(exp, ff.literal(true));
}catch(Exception ex){
throw new ProcessException("Unvalid CQL : "+statement, this, null);
}
}
}else if("JAVASCRIPT".equalsIgnoreCase(syntax)){
final Expression exp = ff.function(JavaScriptFunctionFactory.JAVASCRIPT, ff.literal(statement));
filter = ff.equals(exp, ff.literal(true));
}else if("GROOVY".equalsIgnoreCase(syntax)){
final Expression exp = ff.function(GroovyFunctionFactory.GROOVY, ff.literal(statement));
filter = ff.equals(exp, ff.literal(true));
}else{
throw new ProcessException("Unknwoned syntax : "+syntax+" supported sysntaxes are : CQL,Javascript,Groovy", this, null);
}
return filter.evaluate(inputs);
}
private ProcessDescriptor getProcessDescriptor(final ElementProcess desc) throws NoSuchIdentifierException{
return ProcessFinder.getProcessDescriptor(getDescriptor().getFactories().iterator(),
desc.getAuthority(), desc.getCode());
}
private void setValue(Object value, ParameterValue param){
final Class cs = param.getDescriptor().getValueClass();
value = convert(value, cs);
param.setValue(value);
}
public static <T> T convert(final Object candidate, final Class<T> target) {
if (candidate == null) {
return null;
}
if (target == null) {
return (T) candidate;
}
return (T) convert(candidate, (Class) candidate.getClass(), target);
}
private static <S,T> T convert(final S candidate, final Class<S> source, final Class<T> target) {
// handle case of source being an instance of target up front
if (target.isAssignableFrom(source) ) {
return (T) candidate;
}
final ObjectConverter<? super S, ? extends T> converter;
try {
converter = ObjectConverters.find(source, target);
return converter.apply(candidate);
} catch (UnconvertibleObjectException ex) {
LOGGER.log(Level.INFO, "convert", ex);
return null;
}
}
@Override
public void cancelProcess() {
super.cancelProcess();
if (currentProcess instanceof AbstractProcess) {
((AbstractProcess)currentProcess).cancelProcess();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[ChainProcess]");
final Chain chain = getDescriptor().getModel();
if (chain != null) {
sb.append("Chain: \n");
sb.append("elements:\n");
for (Element element : chain.getElements()) {
sb.append(element).append('\n');
}
}
return sb.toString();
}
}