/* (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.wps;
import static org.apache.commons.io.FilenameUtils.getBaseName;
import static org.apache.commons.io.FilenameUtils.getExtension;
import java.awt.RenderingHints.Key;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resource.Type;
import org.geoserver.platform.resource.Resources;
import org.geoserver.script.ScriptFactory;
import org.geoserver.script.ScriptManager;
import org.geotools.data.Parameter;
import org.geotools.feature.NameImpl;
import org.geotools.process.Process;
import org.geotools.process.ProcessFactory;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.SoftValueHashMap;
import org.geotools.util.logging.Logging;
import org.opengis.feature.type.Name;
import org.opengis.util.InternationalString;
/**
* Process factory that creates processes from scripts located in the data directory.
*/
public class ScriptProcessFactory extends ScriptFactory implements ProcessFactory {
/** logger */
static Logger LOGGER = Logging.getLogger(ScriptProcessFactory.class);
/**
* softly cached process objects
*/
SoftValueHashMap<Name, ScriptProcess> processes = new SoftValueHashMap<Name,ScriptProcess>(10);
public ScriptProcessFactory() {
super(null);
}
public ScriptProcessFactory(ScriptManager scriptMgr) {
super(scriptMgr);
}
abstract class CollectProcessNames {
public CollectProcessNames(Resource directory, ScriptManager scriptMgr, Set<Name> names) {
for (Resource f : directory.list()) {
if (Resources.isHidden(f) || f.getType() == Type.DIRECTORY) {
continue;
}
WpsHook hook = scriptMgr.lookupWpsHook(f);
if (hook == null) {
LOGGER.fine("Skipping " + f.name() + ", no hook found");
} else {
// the base name is the process name, the namespace depends on the
// condition
NameImpl name = new NameImpl(getScriptNamespace(f), getBaseName(f.name()));
if (names.contains(name)) {
throw new RuntimeException("Script " + f.path()
+ " conflicts with an already existing process named " + name);
}
names.add(name);
}
}
}
abstract String getScriptNamespace(Resource f);
}
public Set<Name> getNames() {
LOGGER.fine("Performing process lookup");
ScriptManager scriptMgr = scriptMgr();
Set<Name> names = new TreeSet<Name>();
try {
// load the scripts in the root, the extension is the namespace
Resource wpsRoot = scriptMgr.wps();
new CollectProcessNames(wpsRoot, scriptMgr, names) {
@Override
String getScriptNamespace(Resource f) {
return getExtension(f.name());
}
};
// go over all directories, the directory name is the namespace instead
for (final Resource directory : wpsRoot.list()) {
if (Resources.isHidden(directory) || directory.getType() != Type.DIRECTORY) {
continue;
}
new CollectProcessNames(directory, scriptMgr, names) {
@Override
String getScriptNamespace(Resource f) {
return directory.name();
}
};
}
}
catch (IOException e) {
LOGGER.log(Level.WARNING, "Error looking up processes", e);
}
return names;
}
public InternationalString getTitle() {
return new SimpleInternationalString("script");
}
public InternationalString getTitle(Name name) {
try {
return new SimpleInternationalString(process(name).getTitle());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getVersion(Name name) {
try {
return process(name).getVersion();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Get description given a process identifier. Returns null if process
* has no description (WPS spec says process abstract is optional).
*/
public InternationalString getDescription(Name name) {
String desc;
try {
desc = process(name).getDescription();
} catch (Exception e) {
throw new RuntimeException(e);
}
return desc != null ? new SimpleInternationalString(desc) : null;
}
public Map<String, Parameter<?>> getParameterInfo(Name name) {
try {
return process(name).getInputs();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Map<String, Parameter<?>> getResultInfo(Name name, Map<String, Object> parameters)
throws IllegalArgumentException {
try {
return process(name).getOutputs();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean supportsProgress(Name name) {
return false;
}
public Process create(Name name) {
return process(name);
}
public boolean isAvailable() {
return true;
}
public Map<Key, ?> getImplementationHints() {
return null;
}
ScriptProcess process(Name name) {
ScriptProcess process = processes.get(name);
if (process == null) {
synchronized(this) {
process = processes.get(name);
if (process == null) {
try {
ScriptManager scriptMgr = scriptMgr();
// see if the process is a root level one
String localName = name.getLocalPart();
String namespace = name.getNamespaceURI();
Resource f = scriptMgr.wps().get(localName + "." + namespace);
if (!Resources.exists(f)) {
// see if it's nested in a directory then
Resource directory = scriptMgr.wps().get(namespace);
if (!Resources.exists(directory)) {
throw new FileNotFoundException("Could not find script file "
+ f.name() + " nor a directory of scripts named "
+ directory.name());
}
Resource script = null;
for (Resource file : directory.list()) {
if(Resources.isHidden(file) || file.getType() != Type.RESOURCE) {
continue;
}
if (localName.equals(getBaseName(file.name()))) {
script = file;
}
}
if (script == null) {
throw new FileNotFoundException("Could not find script file "
+ f.name() + " nor a script named " + localName
+ " in the " + directory.name() + " sub-directory");
}
f = script;
}
process = new ScriptProcess(name, f, scriptMgr);
}
catch (IOException e) {
throw new RuntimeException(e);
}
processes.put(name, process);
}
}
}
return process;
}
}