/* (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.gwc.web.layer;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.FormComponentPanel;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
import org.geoserver.catalog.CatalogInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.gwc.GWC;
import org.geoserver.gwc.layer.StyleParameterFilter;
import org.geoserver.gwc.web.GWCIconFactory;
import org.geoserver.web.wicket.GeoServerAjaxFormLink;
import org.geoserver.web.wicket.Icon;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geotools.util.logging.Logging;
import org.geowebcache.filter.parameters.FloatParameterFilter;
import org.geowebcache.filter.parameters.IntegerParameterFilter;
import org.geowebcache.filter.parameters.ParameterFilter;
import org.geowebcache.filter.parameters.RegexParameterFilter;
import org.geowebcache.filter.parameters.StringParameterFilter;
class ParameterFilterEditor extends FormComponentPanel<Set<ParameterFilter>> {
private static final Logger LOGGER = Logging.getLogger(ParameterFilterEditor.class);
private static final long serialVersionUID = 5098470663723800345L;
private static final List<String> COMMON_KEYS = Arrays.asList(
"ENV",
"FORMAT_OPTIONS",
"ANGLE",
"BGCOLOR",
"BUFFER",
"CQL_FILTER",
"ELEVATION",
"FEATUREID",
"FILTER",
"PALETTE",
"STARTINDEX",
"MAXFEATURES",
"TIME",
"VIEWPARAMS",
"FEATUREVERSION");
private final WebMarkupContainer table;
private final ListView<ParameterFilter> filters;
private final ParameterListValidator validator;
private final DropDownChoice<Class<? extends ParameterFilter>> availableFilterTypes;
private final TextField<String> newFilterKey;
private class ParameterListValidator implements IValidator<Set<ParameterFilter>> {
private static final long serialVersionUID = 1L;
private boolean validate;
public ParameterListValidator() {
this.setEnabled(true);
}
@Override
public void validate(IValidatable<Set<ParameterFilter>> validatable) {
if (!validate) {
return;
}
Set<ParameterFilter> paramFilters = validatable.getValue();
if (paramFilters == null) {
return;
}
Set<String> keys = new TreeSet<String>();
for (ParameterFilter filter : paramFilters) {
// TODO Validate
final String key = filter.getKey();
if (key == null) {
throw new IllegalStateException("ParameterFilter key is null");
} else {
if (keys.contains(key)) {
error(validatable, "ParameterFilterEditor.validation.duplicateKey");
return;
} else {
keys.add(key);
}
}
}
}
private void error(IValidatable<Set<ParameterFilter>> validatable, final String resourceKey,
final String... params) {
ValidationError error = new ValidationError();
String message;
if (params == null) {
message = new ResourceModel(resourceKey).getObject();
} else {
message = new ParamResourceModel(resourceKey, ParameterFilterEditor.this,
(Object[]) params).getObject();
}
error.setMessage(message);
validatable.error(error);
}
public void setEnabled(boolean validate) {
this.validate = validate;
}
}
public ParameterFilterEditor(final String id, final IModel<Set<ParameterFilter>> model, final IModel<? extends CatalogInfo> layerModel) {
super(id, model);
add(validator = new ParameterListValidator());
// container for ajax updates
final WebMarkupContainer container = new WebMarkupContainer("container");
container.setOutputMarkupId(true);
add(container);
// the link list
table = new WebMarkupContainer("table");
table.setOutputMarkupId(true);
container.add(table);
filters = new ListView<ParameterFilter>("parameterFilters", new ArrayList<ParameterFilter>(
model.getObject())) {
private static final long serialVersionUID = 1L;
@Override
protected void onBeforeRender() {
//let's remove the correct child quickly before wicket just removes the last one on the list.
for (final Iterator<Component> iterator = iterator(); iterator.hasNext();){
final ListItem<?> child = (ListItem<?>) iterator.next();
if (child != null) {
if (!getList().contains(child.get("subform").getDefaultModelObject())) {
iterator.remove();
}
}
}
super.onBeforeRender();
}
@Override
protected void populateItem(final ListItem<ParameterFilter> item) {
// odd/even style
final int index = item.getIndex();
item.add(AttributeModifier.replace("class", index % 2 == 0 ? "even" : "odd"));
//Create form
final Label keyLabel;
keyLabel = new Label("key", new PropertyModel<String>(item.getModel(),"key"));
item.add(keyLabel);
final Component subForm = getSubform("subform", new Model<ParameterFilter> (item.getModelObject()));
item.add(subForm);
final AjaxSubmitLink removeLink;
removeLink = new AjaxSubmitLink("removeLink") {
private static final long serialVersionUID = 1L;
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
getList().remove((ParameterFilter) getDefaultModelObject());
target.add(container);
}
};
removeLink.add(new Icon("removeIcon", GWCIconFactory.DELETE_ICON));
removeLink.setDefaultModel(item.getModel());
removeLink.add(new AttributeModifier("title", new ResourceModel(
"ParameterFilterEditor.removeLink")));
item.add(removeLink);
}
};
filters.setOutputMarkupId(true);
// this is necessary to avoid loosing item contents on edit/validation checks
filters.setReuseItems(true);
Form<?> filtersForm = new Form<>("filtersForm", filters.getDefaultModel());
filtersForm.add(filters);
table.add(filtersForm);
List<String> parameterKeys = new ArrayList<String>(GWC.get().getGridSetBroker().getNames());
for (ParameterFilter filter : model.getObject()) {
parameterKeys.remove(filter.getKey());
}
Collections.sort(parameterKeys);
GeoServerAjaxFormLink addStyleFilterLink = new GeoServerAjaxFormLink("addStyleFilter") {
private static final long serialVersionUID = 1L;
@Override
protected void onClick(AjaxRequestTarget target, Form<?> form) {
StyleParameterFilter newFilter = new StyleParameterFilter();
newFilter.setLayer((LayerInfo)layerModel.getObject());
addFilter(newFilter);
target.add(container);
}
};
addStyleFilterLink.add(new Icon("addIcon", GWCIconFactory.ADD_ICON));
add(addStyleFilterLink);
// FIXME: make this extensible so new kinds of filter can be supported by
ArrayList<Class<? extends ParameterFilter>> filterTypes =
new ArrayList<Class<? extends ParameterFilter>>();
filterTypes.add(StringParameterFilter.class);
filterTypes.add(FloatParameterFilter.class);
filterTypes.add(IntegerParameterFilter.class);
filterTypes.add(RegexParameterFilter.class);
availableFilterTypes = new DropDownChoice<Class<? extends ParameterFilter>>
( "availableFilterTypes",
new Model<Class<? extends ParameterFilter>>(),
new Model<ArrayList<Class<? extends ParameterFilter>>>(filterTypes),
new ChoiceRenderer<Class<? extends ParameterFilter>>() {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
@Override
public Object getDisplayValue(
Class<? extends ParameterFilter> object) {
String resource = "ParameterFilterEditor.filtername."
+object.getCanonicalName();
try {
// Try to look up a localized name for the class
return getLocalizer().getString(resource,
ParameterFilterEditor.this);
} catch (MissingResourceException ex) {
// Use the simple name as a backup
if(LOGGER.isLoggable(Level.CONFIG))
LOGGER.log(Level.CONFIG, "Could not find localization resource"+
" for ParameterFilter subclass "+object.getCanonicalName());
return object.getSimpleName();
}
}
@Override
public String getIdValue(
Class<? extends ParameterFilter> object,
int index) {
return Integer.toString(index);
}
});
availableFilterTypes.setOutputMarkupId(true);
add(availableFilterTypes);
newFilterKey = new TextField<String>("newFilterKey", Model.of(""));
add(newFilterKey);
// TODO update this to eliminate keys that are in use
final RepeatingView commonKeys = new RepeatingView("commonKeys");
for(String key: COMMON_KEYS) {
commonKeys.add(new Label(commonKeys.newChildId(), key));
}
add(commonKeys);
GeoServerAjaxFormLink addFilterLink = new GeoServerAjaxFormLink("addFilter") {
private static final long serialVersionUID = 1L;
@Override
protected void onClick(AjaxRequestTarget target, Form<?> form) {
availableFilterTypes.processInput();
newFilterKey.processInput();
String key = newFilterKey.getModelObject();
if(key == null || key.isEmpty()){
ParamResourceModel rm = new ParamResourceModel("ParameterFilterEditor.nonEmptyFilter", null, "");
error(rm.getString());
}else{
Class<? extends ParameterFilter> type = availableFilterTypes.getModelObject();
try {
ParameterFilter newFilter = type.getConstructor().newInstance();
newFilter.setKey(key);
addFilter(newFilter);
newFilterKey.setModel(Model.of("")); // Reset the key field
} catch (NoSuchMethodException ex) {
LOGGER.log(Level.WARNING, "No Default Constructor for "+type ,ex);
} catch (InvocationTargetException | SecurityException | InstantiationException | IllegalAccessException ex) {
LOGGER.log(Level.WARNING, "Could not execute default Constructor for "+type ,ex);
}
}
target.add(container);
}
};
addFilterLink.add(new Icon("addIcon", GWCIconFactory.ADD_ICON));
add(addFilterLink);
}
/**
* Returns an appropriate subform for the given ParameterFilter model
* @param model
*
*/
@SuppressWarnings("unchecked")
private Component getSubform(String id, IModel<? extends ParameterFilter> model){
if (model.getObject() instanceof RegexParameterFilter) {
return new RegexParameterFilterSubform(id, (IModel<RegexParameterFilter>) model);
}
if (model.getObject() instanceof StyleParameterFilter) {
return new StyleParameterFilterSubform(id, (IModel<StyleParameterFilter>) model);
}
if (model.getObject() instanceof StringParameterFilter) {
return new StringParameterFilterSubform(id, (IModel<StringParameterFilter>) model);
}
if (model.getObject() instanceof FloatParameterFilter) {
return new FloatParameterFilterSubform(id, (IModel<FloatParameterFilter>) model);
}
if (model.getObject() instanceof IntegerParameterFilter) {
return new IntegerParameterFilterSubform(id, (IModel<IntegerParameterFilter>) model);
}
return new DefaultParameterFilterSubform(id, (IModel<ParameterFilter>) model);
}
@Override
public void convertInput() {
filters.visitChildren((component, visit) -> {
if (component instanceof FormComponent) {
FormComponent<?> formComponent = (FormComponent<?>) component;
formComponent.processInput();
}
});
List<ParameterFilter> info = filters.getModelObject();
HashSet<ParameterFilter> convertedInput = new HashSet<ParameterFilter>(info);
setConvertedInput(convertedInput);
}
private boolean hasFilter(String key) {
for(ParameterFilter existing: filters.getModelObject()) {
if(existing.getKey().equalsIgnoreCase(key)) return true;
}
return false;
}
private boolean addFilter(ParameterFilter filter) {
if(hasFilter(filter.getKey())) return false;
filters.getModelObject().add(filter);
return true;
}
/**
*/
@Override
protected void onBeforeRender() {
super.onBeforeRender();
}
public void setValidating(final boolean validate) {
validator.setEnabled(validate);
}
}