/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2008, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.process.function; import java.lang.reflect.Array; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.geotools.data.Parameter; import org.geotools.filter.capability.FunctionNameImpl; import org.geotools.process.Process; import org.geotools.process.ProcessException; import org.geotools.process.Processors; import org.geotools.util.Converters; import org.opengis.feature.type.Name; import org.opengis.filter.capability.FunctionName; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.ExpressionVisitor; import org.opengis.filter.expression.Function; import org.opengis.filter.expression.Literal; /** * A function wrapping a {@link Process} with a single output. All inputs to the function are * supposed to evaluate to Map<String, Object> where the key is the name of an argument and the * value is the argument value * * @author Andrea Aime - GeoSolutions */ class ProcessFunction implements Function { String name; Literal fallbackValue; List<Expression> inputExpressions; Map<String, Parameter<?>> parameters; Process process; Name processName; ProcessFunction(String name, Name processName, List<Expression> inputExpressions, Map<String, Parameter<?>> parameters, Process process, Literal fallbackValue) { super(); this.name = name; this.processName = processName; this.inputExpressions = inputExpressions; this.parameters = parameters; this.process = process; this.fallbackValue = fallbackValue; } public Literal getFallbackValue() { return fallbackValue; } public String getName() { return name; } public FunctionName getFunctionName() { return new FunctionNameImpl( name, inputExpressions.size() ); } public List<Expression> getParameters() { return inputExpressions; } public Object accept(ExpressionVisitor visitor, Object extraData) { return visitor.visit(this, extraData); } public <T> T evaluate(Object object, Class<T> context) { Object o = evaluate(object); return Converters.convert(o, context); } public Object evaluate(Object object) { // collect the entries Map<String, Object> processInputs = new HashMap<String, Object>(); for (Expression input : inputExpressions) { Object result = input.evaluate(object, Map.class); if (result != null) { Map map = (Map) result; if (map.size() > 1) { throw new InvalidParameterException("The parameters to a ProcessFunction " + "must all be maps with a single entry, " + "the key is the process argument name, " + "the value is the argument value"); } else { // handle the key/value Iterator it = map.entrySet().iterator(); Map.Entry<String, Object> entry = (Entry<String, Object>) it.next(); final String paramName = entry.getKey(); final Object paramValue = entry.getValue(); // see if we have a parameter with that name Parameter param = parameters.get(paramName); if (param == null) { throw new InvalidParameterException("Parameter " + paramName + " is not among the process parameters: " + parameters.keySet()); } else { // if the value is not null, convert to the param target type and add // to the process invocation params if (paramValue != null) { Object converted; if(param.maxOccurs > 1) { // converter will work if the have to convert the array type, but not if // they have to deal with two conversions, from single to multi, from type to type if(!(paramValue instanceof Collection) && !(paramValue.getClass().isArray())) { List<Object> collection = Collections.singletonList(paramValue); converted = Converters.convert(collection, Array.newInstance(param.type, 0).getClass()); } else { converted = Converters.convert(paramValue, Array.newInstance(param.type, 0).getClass()); } } else { converted = Converters.convert(paramValue, param.type); } if (converted == null) { if(param.maxOccurs > 1 && Collection.class.isAssignableFrom(paramValue.getClass())) { final Collection collection = (Collection) paramValue; Collection convertedCollection = new ArrayList(collection.size()); for (Object original : collection) { Object convertedItem = Converters.convert(original, param.type); if(original != null && convertedItem == null) { throw new InvalidParameterException("Could not convert the value " + original + " into the expected type " + param.type + " for parameter " + paramName); } convertedCollection.add(convertedItem); } Object array = Array.newInstance(param.type, convertedCollection.size()); int i = 0; for (Object item : convertedCollection) { Array.set(array, i, item); i++; } converted = array; } else { throw new InvalidParameterException("Could not convert the value " + paramValue + " into the expected type " + param.type + " for parameter " + paramName); } } processInputs.put(paramName, converted); } } } } } // execute the process try { ExceptionProgressListener listener = new ExceptionProgressListener(); Map<String, Object> results = process.execute(processInputs, listener); // some processes have the bad habit of not throwing exceptions, but to // report them to the listener if(listener.getExceptions().size() > 0) { // uh oh, an exception occurred during processing Throwable t = listener.getExceptions().get(0); throw new RuntimeException("Failed to evaluate process function, error is: " + t.getMessage(), t); } return getResult(results, processInputs); } catch (ProcessException e) { throw new RuntimeException("Failed to evaluate the process function, error is: " + e.getMessage(), e); } } private Object getResult(Map<String, Object> results, Map<String, Object> processInputs) { if (results.size() == 1) { return results.values().iterator().next(); } // return the sole value returned Map<String, Parameter<?>> resultInfo = Processors.getResultInfo(processName, processInputs); String primary = getPrimary(resultInfo); return results.get(primary); } private String getPrimary(Map<String, Parameter<?>> resultInfo) { if(resultInfo.size() == 1) { return resultInfo.get(0).getName(); } else { for (Parameter<?> param : resultInfo.values()) { if(param.isRequired()) { return param.getName(); } } } return null; } }