/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.script.py; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Logger; import javax.script.ScriptEngine; import javax.script.ScriptException; import org.geoserver.script.wps.WpsHook; import org.geotools.data.Parameter; import org.geotools.text.Text; import org.geotools.util.Converters; import org.geotools.util.logging.Logging; import org.python.core.Py; import org.python.core.PyDictionary; import org.python.core.PyList; import org.python.core.PyObject; import org.python.core.PyTuple; import org.python.core.PyType; /** * Python wps hook. * * @author Justin Deoliveira, OpenGeo */ public class PyWpsHook extends WpsHook { static Logger LOGGER = Logging.getLogger(PyWpsHook.class); public PyWpsHook(PythonPlugin plugin) { super(plugin); } @Override public String getTitle(ScriptEngine engine) throws ScriptException { return str(process(engine).__getattr__("title")); } @Override public String getDescription(ScriptEngine engine) throws ScriptException { return str(process(engine).__getattr__("description")); } @Override public Map<String, Parameter<?>> getInputs(ScriptEngine engine) throws ScriptException { engine.eval("import inspect"); PyList args = (PyList) engine.eval("inspect.getargspec(run.func_closure[0].cell_contents)[0]"); PyTuple defaults = (PyTuple) engine .eval("inspect.getargspec(run.func_closure[0].cell_contents)[3]"); PyDictionary inputs = (PyDictionary) process(engine).__getattr__("inputs"); if (args.size() != inputs.size()) { throw new RuntimeException(String.format("process function specified %d arguments but"+ " describes %d inputs", args.size(), inputs.size())); } Map<String, Parameter<?>> map = new LinkedHashMap<String, Parameter<?>>(); for (int i = 0; i < args.size(); i++) { String arg = args.get(i).toString(); PyTuple input = (PyTuple) inputs.get(arg); if (input == null) { throw new RuntimeException(String.format("process function specified argument %s" + " but does not specify it as an input", arg)); } int min = 1; int max = 1; Object defaultValue = null; Map metadata = null; if (input.size() == 3) { PyDictionary meta = (PyDictionary) input.get(2); min = getParameter(meta, "min", Integer.class, 1); max = getParameter(meta, "max", Integer.class, 1); List<String> options = getParameter(meta, "domain", List.class, null); if (options != null) { metadata = new HashMap(); metadata.put(Parameter.OPTIONS, options); } // map every other key as parameter metadata entry HashSet<String> otherKeys = new HashSet<String>(meta.keySet()); otherKeys.remove("min"); otherKeys.remove("max"); otherKeys.remove("domain"); if(!otherKeys.isEmpty()) { if (metadata == null) { metadata = new HashMap(); } for (String key : otherKeys) { metadata.put(key, meta.get(key)); } } } if (defaults != null) { // from the python guide: // defaults is a tuple of default argument values or None if there are no // default arguments; if this tuple has n elements, // they correspond to the last n elements listed in args. int defaultIdx = defaults.size() - (args.size() - i); if (defaultIdx >= 0) { defaultValue = defaults.get(defaultIdx); } } Parameter parameter = parameter(arg, input.__getitem__(0), min, max, input.__getitem__(1), defaultValue, metadata); map.put(arg, parameter); } return map; } private <T> T getParameter(PyDictionary meta, String key, Class<T> targetType, T defaultValue) { if (!meta.containsKey(key)) { return defaultValue; } Object value = meta.get(key); return (T) Converters.convert(value, targetType); } @Override public Map<String, Parameter<?>> getOutputs(ScriptEngine engine) throws ScriptException { PyDictionary outputs = (PyDictionary) process(engine).__getattr__("outputs"); Map<String,Parameter<?>> map = new TreeMap<String, Parameter<?>>(); for (String name : (List<String>)outputs.keys()) { PyTuple output = (PyTuple) outputs.get(name); Object type = output.__getitem__(0); Object desc = output.__getitem__(1); // map every key in the optional dictionary as parameter metadata entry Map<String, Object> metadata = null; if (output.size() == 3) { PyDictionary meta = (PyDictionary) output.get(2); if(meta != null && !meta.isEmpty()) { metadata = new HashMap<String, Object>(); for (Object key : meta.keySet()) { metadata.put((String) key, meta.get(key)); } } } map.put(name, parameter(name, type, 1, 1, desc, null, metadata)); } return map; } @Override public Map<String, Object> run(Map<String, Object> inputs, ScriptEngine engine) throws ScriptException { PyObject run = process(engine); List<PyObject> args = new ArrayList(); List<String> kw = new ArrayList(); for (Map.Entry<String,Object> input : inputs.entrySet()) { args.add(Py.java2py(input.getValue())); kw.add(input.getKey()); } PyObject r = run.__call__(args.toArray(new PyObject[args.size()]), kw.toArray(new String[kw.size()])); Map<String,Parameter<?>> outputs = getOutputs(engine); Map<String,?> result = null; if (r instanceof Map) { result = (Map<String, ?>) r; } else { result = Collections.singletonMap(outputs.keySet().iterator().next(), r); } if (result.size() != outputs.size()) { throw new IllegalStateException(String.format("Process returned %d values, should have " + "returned %d", result.size(), outputs.size())); } Map<String,Object> results = new LinkedHashMap<String, Object>(); for (Map.Entry<String, Parameter<?>> e : outputs.entrySet()) { String key = e.getKey(); Parameter output = e.getValue(); Object obj = result.get(key); if (obj instanceof PyObject) { obj = ((PyObject) obj).__tojava__(output.type); } if (obj != null && !output.type.isInstance(obj)) { LOGGER.warning(String.format("Output %s declared type %s but returned %s", output.getName(), output.getType().getSimpleName(), obj.getClass().getSimpleName())); } results.put(output.key, obj); } return results; } PyObject process(ScriptEngine engine) { return (PyObject) engine.get("run"); } String str(Object obj) { return obj != null ? obj.toString() : null; } Parameter parameter(String name, Object type, int min, int max, Object desc, Object defaultValue, Map metadata) { Class clazz = null; if (type != null && type instanceof PyType) { clazz = PythonPlugin.toJavaClass((PyType)type); } if (clazz == null) { clazz = Object.class; } desc = desc != null ? desc : name; return new Parameter(name, clazz, Text.text(name), Text.text(desc.toString()), min > 0, min, max, defaultValue, metadata); } }