/* (c) 2014 - 2016 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.wps; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.geoserver.catalog.MetadataMap; import org.geoserver.config.GeoServer; import org.geoserver.config.util.XStreamPersister; import org.geoserver.config.util.XStreamServiceLoader; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.wps.validator.MaxSizeValidator; import org.geoserver.wps.validator.MultiplicityValidator; import org.geoserver.wps.validator.NumberRangeValidator; import org.geoserver.wps.validator.WPSInputValidator; import org.geotools.feature.NameImpl; import org.geotools.util.Converters; import org.geotools.util.NumberRange; import org.geotools.util.Version; import org.opengis.feature.type.Name; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter; import com.thoughtworks.xstream.converters.collections.CollectionConverter; import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; import com.thoughtworks.xstream.converters.reflection.ReflectionProvider; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.ClassAliasingMapper; import com.thoughtworks.xstream.mapper.Mapper; /** * Service loader for the Web Processing Service * * @author Lucas Reed, Refractions Research Inc * @author Justin Deoliveira, The Open Planning Project */ public class WPSXStreamLoader extends XStreamServiceLoader<WPSInfo> { public WPSXStreamLoader(GeoServerResourceLoader resourceLoader) { super(resourceLoader, "wps"); } public Class<WPSInfo> getServiceClass() { return WPSInfo.class; } protected WPSInfo createServiceFromScratch(GeoServer gs) { WPSInfo wps = new WPSInfoImpl(); wps.setName("WPS"); wps.setGeoServer(gs); wps.getVersions().add(new Version("1.0.0")); wps.setMaxAsynchronousProcesses(Runtime.getRuntime().availableProcessors() * 2); wps.setMaxSynchronousProcesses(Runtime.getRuntime().availableProcessors() * 2); return wps; } @Override protected void initXStreamPersister(XStreamPersister xp, GeoServer gs) { super.initXStreamPersister(xp, gs); XStream xs = xp.getXStream(); // Use custom converter to manage previous wps.xml configuration format xs.registerConverter(new ProcessGroupConverter(xs.getMapper(), xs.getReflectionProvider())); xs.alias("wps", WPSInfo.class, WPSInfoImpl.class); xs.alias("processGroup", ProcessGroupInfoImpl.class); xs.alias("name", NameImpl.class); xs.alias("name", Name.class, NameImpl.class); xs.alias("accessInfo", ProcessInfoImpl.class); xs.registerConverter(new NameConverter()); ClassAliasingMapper mapper = new ClassAliasingMapper(xs.getMapper()); mapper.addClassAlias("role", String.class); xs.registerLocalConverter(ProcessGroupInfoImpl.class, "roles", new CollectionConverter( mapper)); xs.registerLocalConverter(ProcessInfoImpl.class, "roles", new CollectionConverter(mapper)); xs.registerLocalConverter(ProcessInfoImpl.class, "validators", new XStreamPersister.MultimapConverter(mapper)); xs.alias("maxSizeValidator", MaxSizeValidator.class); xs.alias("maxMultiplicityValidator", MultiplicityValidator.class); xs.alias("rangeValidator", NumberRangeValidator.class); xs.registerLocalConverter(NumberRangeValidator.class, "range", new NumberRangeConverter(xs.getMapper(), xs.getReflectionProvider())); xs.allowTypeHierarchy(ProcessGroupInfo.class); xs.allowTypeHierarchy(WPSInputValidator.class); } @Override protected WPSInfo initialize(WPSInfo service) { // TODO: move this code block to the parent class if (service.getKeywords() == null) { ((WPSInfoImpl) service).setKeywords(new ArrayList()); } if (service.getExceptionFormats() == null) { ((WPSInfoImpl) service).setExceptionFormats(new ArrayList()); } if (service.getMetadata() == null) { ((WPSInfoImpl) service).setMetadata(new MetadataMap()); } if (service.getClientProperties() == null) { ((WPSInfoImpl) service).setClientProperties(new HashMap()); } if (service.getVersions() == null) { ((WPSInfoImpl) service).setVersions(new ArrayList()); } if (service.getVersions().isEmpty()) { service.getVersions().add(new Version("1.0.0")); } if (service.getConnectionTimeout() == 0) { // timeout has not yet been specified. Use default service.setConnectionTimeout(WPSInfoImpl.DEFAULT_CONNECTION_TIMEOUT); } if (service.getProcessGroups() == null) { ((WPSInfoImpl) service).setProcessGroups(new ArrayList()); } else { for (ProcessGroupInfo pg : service.getProcessGroups()) { if (pg.getRoles() == null) { pg.setRoles(new ArrayList<String>()); } if (pg.getMetadata() == null) { ((ProcessGroupInfoImpl) pg).setMetadata(new MetadataMap()); } if (pg.getFilteredProcesses() == null) { ((ProcessGroupInfoImpl) pg).setFilteredProcesses(new ArrayList<ProcessInfo>()); } else { for (ProcessInfo pi : pg.getFilteredProcesses()) { if (pi.getRoles() == null) { ((ProcessInfoImpl) pi).setRoles(new ArrayList<String>()); } if (pi.getValidators() == null) { Multimap<String, WPSInputValidator> validators = ArrayListMultimap .create(); ((ProcessInfoImpl) pi).setValidators(validators); } if (pi.getMetadata() == null) { ((ProcessInfoImpl) pi).setMetadata(new MetadataMap()); } } } } } if (service.getName() == null) { service.setName("WPS"); } return service; } /** * Converter for {@link Name} * */ public static class NameConverter extends AbstractSingleValueConverter { @Override public boolean canConvert(Class type) { return Name.class.isAssignableFrom(type); } @Override public String toString(Object obj) { Name name = (Name) obj; return name.getNamespaceURI() + ":" + name.getLocalPart(); } @Override public Object fromString(String str) { int idx = str.indexOf(":"); if (idx == -1) { return new NameImpl(str); } else { String prefix = str.substring(0, idx); String local = str.substring(idx + 1); return new NameImpl(prefix, local); } } } /** * Manages unmarshalling of {@link ProcessGroupInfoImpl} taking into account previous wps.xml * format in witch {@link ProcessGroupInfoImpl #getFilteredProcesses()} is a collection of * {@link NameImpl} */ public static class ProcessGroupConverter extends ReflectionConverter { public ProcessGroupConverter(Mapper mapper, ReflectionProvider reflectionProvider) { super(mapper, reflectionProvider); } @Override public boolean canConvert(Class clazz) { return ProcessGroupInfoImpl.class == clazz; } @Override public Object doUnmarshal(Object result, HierarchicalStreamReader reader, UnmarshallingContext context) { ProcessGroupInfo converted = (ProcessGroupInfo) super.doUnmarshal(result, reader, context); if (converted.getFilteredProcesses() != null) { List<ProcessInfo> newFilteredProcesses = new ArrayList<ProcessInfo>(); for (Object fp : converted.getFilteredProcesses()) { if (fp instanceof NameImpl) { NameImpl ni = (NameImpl) fp; ProcessInfo pi = new ProcessInfoImpl(); pi.setName(ni); pi.setEnabled(false); newFilteredProcesses.add(pi); } else { break; } } if (!newFilteredProcesses.isEmpty()) { converted.getFilteredProcesses().clear(); converted.getFilteredProcesses().addAll(newFilteredProcesses); } } return converted; } } public static class NumberRangeConverter extends ReflectionConverter { public NumberRangeConverter(Mapper mapper, ReflectionProvider reflectionProvider) { super(mapper, reflectionProvider); } @Override public boolean canConvert(Class type) { return NumberRange.class.isAssignableFrom(type); } @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { NumberRange<?> range = (NumberRange<?>) source; writer.startNode("minValue"); writer.setValue(String.valueOf(range.getMinValue())); writer.endNode(); writer.startNode("maxValue"); writer.setValue(String.valueOf(range.getMaxValue())); writer.endNode(); if (!range.isMinIncluded()) { writer.startNode("isMinIncluded"); writer.setValue("false"); writer.endNode(); } if (!range.isMaxIncluded()) { writer.startNode("isMaxIncluded"); writer.setValue("false"); writer.endNode(); } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { reader.moveDown(); double min = Converters.convert(reader.getValue(), Double.class); reader.moveUp(); reader.moveDown(); double max = Converters.convert(reader.getValue(), Double.class); reader.moveUp(); NumberRange<Double> range = new NumberRange<>(Double.class, min, max); return range; } } }