/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wps.web; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.wicket.Component; import org.apache.wicket.Page; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.SubmitLink; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.repeater.DefaultItemReuseStrategy; import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerSecuredPage; import org.geoserver.web.wicket.GeoServerDataProvider; import org.geoserver.web.wicket.GeoServerDataProvider.Property; import org.geoserver.web.wicket.GeoServerTablePanel; import org.geoserver.web.wicket.ParamResourceModel; import org.geoserver.wps.ppio.ProcessParameterIO; import org.geoserver.wps.process.GeoServerProcessors; 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.geoserver.wps.web.FilteredProcessesProvider.FilteredProcess; import org.geotools.data.Parameter; import org.geotools.process.ProcessFactory; import org.springframework.context.ApplicationContext; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; /** * Allows the admin to edit the limits for a specific process * * @author Andrea Aime - GeoSolutions */ public class ProcessLimitsPage extends GeoServerSecuredPage { private static final Set<Class<?>> PRIMITIVE_NUMBERS = new ImmutableSet.Builder<Class<?>>() .add(byte.class).add(char.class).add(double.class).add(float.class).add(int.class) .add(long.class).add(short.class).build(); private GeoServerTablePanel<InputLimit> table; private FilteredProcess process; public ProcessLimitsPage(Page returnPage, final FilteredProcess process) { setReturnPage(returnPage); this.process = process; Form form = new Form("form"); add(form); final List<InputLimit> inputLimits = buildInputLimits(process); GeoServerDataProvider<InputLimit> inputLimitsProvider = new GeoServerDataProvider<ProcessLimitsPage.InputLimit>() { @Override protected List<org.geoserver.web.wicket.GeoServerDataProvider.Property<InputLimit>> getProperties() { List<Property<InputLimit>> result = new ArrayList<>(); result.add(new BeanProperty<InputLimit>("name", "name")); result.add(new PropertyPlaceholder("type")); result.add(new BeanProperty<InputLimit>("editor", "validator")); return result; } @Override protected List<InputLimit> getItems() { return inputLimits; } }; table = new GeoServerTablePanel<InputLimit>("table", inputLimitsProvider) { @Override protected Component getComponentForProperty(String id, IModel<InputLimit> itemModel, Property<InputLimit> property) { InputLimit limit = (InputLimit) itemModel.getObject(); String propertyName = property.getName(); if (propertyName.equals("type")) { String type; try { String key = "type." + limit.getValidator().getClass().getName(); type = new ParamResourceModel(key, ProcessLimitsPage.this).getString(); } catch (Exception e) { type = limit.validator.getClass().getSimpleName(); } return new Label(id, type); } else if (propertyName.equals("editor")) { WPSInputValidator validator = limit.getValidator(); if (validator instanceof MaxSizeValidator) { Fragment f = new Fragment(id, "textEditor", ProcessLimitsPage.this); PropertyModel maxSizeModel = new PropertyModel(itemModel, "validator.maxSizeMB"); TextField<Integer> text = new TextField<Integer>("text", maxSizeModel, Integer.class); f.add(text); return f; } else if (validator instanceof MultiplicityValidator) { Fragment f = new Fragment(id, "textEditor", ProcessLimitsPage.this); PropertyModel maxMultiplicityModel = new PropertyModel(itemModel, "validator.maxInstances"); TextField<Integer> text = new TextField<Integer>("text", maxMultiplicityModel, Integer.class); f.add(text); return f; } else if (validator instanceof NumberRangeValidator) { Fragment f = new Fragment(id, "rangeEditor", ProcessLimitsPage.this); PropertyModel rangeModel = new PropertyModel(itemModel, "validator.range"); RangePanel rangeEditor = new RangePanel("range", rangeModel); f.add(rangeEditor); return f; } } // have the base class create a label for us return null; } }; table.setOutputMarkupId(true); table.setFilterable(false); table.setPageable(false); table.setItemReuseStrategy(new DefaultItemReuseStrategy()); table.setSortable(false); form.add(table); SubmitLink apply = new SubmitLink("apply") { @Override public void onSubmit() { // super.onSubmit(); process.setValidators(buildValidators(inputLimits)); doReturn(); } }; form.add(apply); Link cancel = new Link("cancel") { @Override public void onClick() { doReturn(); } }; form.add(cancel); } /** * Turn the input limits into the UI into a set of operational validators, filtering out those * that have empty or default input * */ protected Multimap<String, WPSInputValidator> buildValidators(List<InputLimit> inputLimits) { Multimap<String, WPSInputValidator> result = ArrayListMultimap.create(); for (InputLimit inputLimit : inputLimits) { String name = inputLimit.getName(); WPSInputValidator validator = inputLimit.getValidator(); // skip validators that are not going to do anything if (validator.isUnset()) { continue; } result.put(name, validator); } return result; } /** * Go from the available process validator to a UI representation, adding also the possible * validators that are not yet set * * @param process * */ private List<InputLimit> buildInputLimits(FilteredProcess process) { ApplicationContext applicationContext = GeoServerApplication.get().getApplicationContext(); Multimap<String, WPSInputValidator> validators = process.getValidators(); ProcessFactory pf = GeoServerProcessors.createProcessFactory(process.getName(), false); Map<String, Parameter<?>> parameters = pf.getParameterInfo(process.getName()); List<InputLimit> result = new ArrayList<>(); for (Parameter param : parameters.values()) { String name = param.getName(); Collection<WPSInputValidator> paramValidators = validators != null ? validators .get(name) : null; // add the existing validators and collect their types Set<Class> validatorTypes = new HashSet<>(); if (paramValidators != null) { for (WPSInputValidator validator : paramValidators) { validatorTypes.add(validator.getClass()); // we deep clone the validator to avoid changing the WPS state until // the main security page gets submitted result.add(new InputLimit(name, validator.copy())); } } // see if we need to add some missing validator if (param.getMaxOccurs() > 1 && !validatorTypes.contains(MultiplicityValidator.class)) { int max = 0; if (param.getMaxOccurs() < Integer.MAX_VALUE) { max = param.getMaxOccurs(); } result.add(new InputLimit(name, new MultiplicityValidator(max))); } boolean isComplex = ProcessParameterIO.isComplex(param, applicationContext); if (isComplex) { if (!validatorTypes.contains(MaxSizeValidator.class)) { result.add(new InputLimit(name, new MaxSizeValidator(0))); } } else { if (isNumeric(param) && !validatorTypes.contains(NumberRangeValidator.class)) { result.add(new InputLimit(name, new NumberRangeValidator(null))); } } } return result; } private boolean isNumeric(Parameter param) { return Number.class.isAssignableFrom(param.getType()) || PRIMITIVE_NUMBERS.contains(param.getType()); } protected String getTitle() { return new ParamResourceModel("title", this, process.getName().getURI()).getString(); } /** * The input limit * * @author Andrea Aime - GeoSolutions */ static final class InputLimit implements Serializable { private static final long serialVersionUID = -4763254264009929615L; String name; WPSInputValidator validator; public InputLimit(String name, WPSInputValidator validator) { this.name = name; this.validator = validator; } @Override public String toString() { return "InputLimit [name=" + name + ", validator=" + validator + "]"; } public String getName() { return name; } public WPSInputValidator getValidator() { return validator; } } }