/* (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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
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.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
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.gwc.GWC;
import org.geoserver.gwc.web.GWCIconFactory;
import org.geoserver.web.wicket.GeoServerAjaxFormLink;
import org.geoserver.web.wicket.Icon;
import org.geoserver.web.wicket.ImageAjaxLink;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geowebcache.config.XMLGridSubset;
import org.geowebcache.grid.BoundingBox;
import org.geowebcache.grid.GridSet;
import org.geowebcache.grid.GridSetBroker;
class GridSubsetsEditor extends FormComponentPanel<Set<XMLGridSubset>> {
private static final long serialVersionUID = 5098470663723800345L;
private final WebMarkupContainer table;
private final ListView<XMLGridSubset> grids;
private final DropDownChoice<String> availableGridSets;
private final GridSubsetListValidator validator;
private class GridSubsetListValidator implements IValidator<Set<XMLGridSubset>> {
private static final long serialVersionUID = -2646310164736911748L;
private boolean validate;
public GridSubsetListValidator() {
this.setEnabled(true);
}
@Override
public void validate(IValidatable<Set<XMLGridSubset>> validatable) {
if (!validate) {
return;
}
Set<XMLGridSubset> gridSubsets = validatable.getValue();
if (gridSubsets == null || gridSubsets.size() == 0) {
error(validatable, "GridSubsetsEditor.validation.empty");
return;
}
final GWC gwc = GWC.get();
for (XMLGridSubset subset : gridSubsets) {
final String gridSetName = subset.getGridSetName();
final Integer zoomStart = subset.getZoomStart();
final Integer zoomStop = subset.getZoomStop();
final BoundingBox extent = subset.getExtent();
if (gridSetName == null) {
throw new IllegalStateException("GridSet name is null");
}
if (zoomStart != null && zoomStop != null) {
if (zoomStart.intValue() > zoomStop.intValue()) {
error(validatable, "GridSubsetsEditor.validation.zoomLevelsError");
return;
}
}
final GridSetBroker gridSetBroker = gwc.getGridSetBroker();
final GridSet gridSet = gridSetBroker.get(gridSetName);
if (null == gridSet) {
error(validatable, "GridSubsetsEditor.validation.gridSetNotFound", gridSetName);
return;
}
if (extent != null) {
if (extent.isNull() || !extent.isSane()) {
error(validatable, "GridSubsetsEditor.validation.invalidBounds");
}
final BoundingBox fullBounds = gridSet.getOriginalExtent();
final boolean intersects = fullBounds.intersects(extent);
if (!intersects) {
error(validatable, "GridSubsetsEditor.validation.boundsOutsideCoverage");
}
}
}
}
private void error(IValidatable<Set<XMLGridSubset>> 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, GridSubsetsEditor.this,
(Object[]) params).getObject();
}
error.setMessage(message);
validatable.error(error);
}
public void setEnabled(boolean validate) {
this.validate = validate;
}
}
public GridSubsetsEditor(final String id, final IModel<Set<XMLGridSubset>> model) {
super(id, model);
add(validator = new GridSubsetListValidator());
// 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);
grids = new ListView<XMLGridSubset>("gridSubsets", new ArrayList<XMLGridSubset>(
model.getObject())) {
private static final long serialVersionUID = 1L;
@Override
protected void onBeforeRender() {
super.onBeforeRender();
}
@Override
protected void populateItem(final ListItem<XMLGridSubset> item) {
// odd/even style
final int index = item.getIndex();
item.add(AttributeModifier.replace("class", index % 2 == 0 ? "even" : "odd"));
final XMLGridSubset gridSubset = item.getModelObject();
GridSetBroker gridSetBroker = GWC.get().getGridSetBroker();
String gridsetDescription = null;
int gridsetLevels;
boolean gridsetExists;
{
final GridSet gridSet = gridSetBroker.get(gridSubset.getGridSetName());
gridsetExists = gridSet != null;
if (gridsetExists) {
gridsetLevels = gridSet.getNumLevels();
gridsetDescription = gridSet.getDescription();
} else {
gridsetLevels = gridSubset.getZoomStop() == null ? 1 : gridSubset
.getZoomStop().intValue();
}
}
final Label gridSetLabel;
final Component gridSetBounds;
gridSetLabel = new Label("gridSet", new PropertyModel<String>(item.getModel(),
"gridSetName"));
if(!gridsetExists){
gridSetLabel.add(new AttributeModifier("style", new Model<String>("color:red;text-decoration:line-through;")));
getPage().warn("GridSet " + gridSubset.getGridSetName() + " does not exist");
}
item.add(gridSetLabel);
if (null != gridsetDescription) {
gridSetLabel.add(new AttributeModifier("title", new Model<String>(
gridsetDescription)));
}
final Component removeLink;
final int maxZoomLevel = gridsetLevels - 1;
final ArrayList<Integer> zoomLevels = new ArrayList<Integer>(maxZoomLevel + 1);
for (int z = 0; z <= maxZoomLevel; z++) {
zoomLevels.add(Integer.valueOf(z));
}
// zoomStart has all zoom levels as choices
// zoomStop choices start at zoomStart's selection
// minCachedLevel start at zoomStart's selection and ends at zoomStop's selection
// maxCachedLevel start at minCachedLevels' and ends at zoomStop's selection
final IModel<Integer> zoomStartModel;
final IModel<Integer> zoomStopModel;
final IModel<Integer> minCachedLevelModel;
final IModel<Integer> maxCachedLevelModel;
final ZoomLevelDropDownChoice zoomStart;
final ZoomLevelDropDownChoice zoomStop;
final ZoomLevelDropDownChoice minCachedLevel;
final ZoomLevelDropDownChoice maxCachedLevel;
zoomStartModel = new PropertyModel<Integer>(item.getModel(), "zoomStart");
zoomStopModel = new PropertyModel<Integer>(item.getModel(), "zoomStop");
minCachedLevelModel = new PropertyModel<Integer>(item.getModel(), "minCachedLevel");
maxCachedLevelModel = new PropertyModel<Integer>(item.getModel(), "maxCachedLevel");
@SuppressWarnings({ "rawtypes", "unchecked" })
final IModel<List<Integer>> allLevels = new Model(zoomLevels);
zoomStart = new ZoomLevelDropDownChoice("zoomStart", zoomStartModel, allLevels);
zoomStart.setEnabled(gridsetExists);
item.add(zoomStart);
zoomStop = new ZoomLevelDropDownChoice("zoomStop", zoomStopModel, allLevels);
zoomStop.setEnabled(gridsetExists);
item.add(zoomStop);
minCachedLevel = new ZoomLevelDropDownChoice("minCachedLevel", minCachedLevelModel,
allLevels);
minCachedLevel.setEnabled(gridsetExists);
item.add(minCachedLevel);
maxCachedLevel = new ZoomLevelDropDownChoice("maxCachedLevel", maxCachedLevelModel,
allLevels);
maxCachedLevel.setEnabled(gridsetExists);
item.add(maxCachedLevel);
for (ZoomLevelDropDownChoice dropDown : Arrays.asList(zoomStart, zoomStop,
minCachedLevel, maxCachedLevel)) {
dropDown.add(new OnChangeAjaxBehavior() {
private static final long serialVersionUID = 1L;
// cascades to zoomStop, min and max cached levels
@Override
protected void onUpdate(AjaxRequestTarget target) {
updateValidZoomRanges(zoomStart, zoomStop, minCachedLevel,
maxCachedLevel, target);
}
});
}
updateValidZoomRanges(zoomStart, zoomStop, minCachedLevel, maxCachedLevel, null);
// gridSetBounds = new EnvelopePanel("bounds");
// gridSetBounds.setCRSFieldVisible(false);
// gridSetBounds.setCrsRequired(false);
// gridSetBounds.setLabelsVisibility(false);
// gridSetBounds.setModel(new Model<ReferencedEnvelope>(new ReferencedEnvelope()));
gridSetBounds = new Label("bounds", new ResourceModel(
"GridSubsetsEditor.bounds.dynamic"));
item.add(gridSetBounds);
removeLink = new ImageAjaxLink<Object>("removeLink", GWCIconFactory.DELETE_ICON) {
private static final long serialVersionUID = -5072597940769821889L;
@Override
protected void onClick(AjaxRequestTarget target) {
List<XMLGridSubset> list;
list = new ArrayList<XMLGridSubset>(grids.getModelObject());
final XMLGridSubset subset = (XMLGridSubset) getDefaultModelObject();
list.remove(subset);
grids.setModelObject(list);
List<String> choices = new ArrayList<String>(availableGridSets.getChoices());
choices.add(subset.getGridSetName());
Collections.sort(choices);
availableGridSets.setChoices(choices);
target.add(container);
target.add(availableGridSets);
}
};
removeLink.setDefaultModel(item.getModel());
removeLink.add(new AttributeModifier("title", new ResourceModel(
"GridSubsetsEditor.removeLink")));
item.add(removeLink);
}
};
grids.setOutputMarkupId(true);
// this is necessary to avoid loosing item contents on edit/validation checks
grids.setReuseItems(true);
table.add(grids);
List<String> gridSetNames = new ArrayList<String>(GWC.get().getGridSetBroker().getNames());
for (XMLGridSubset gs : model.getObject()) {
gridSetNames.remove(gs.getGridSetName());
}
Collections.sort(gridSetNames);
GeoServerAjaxFormLink addGridsubsetLink = new GeoServerAjaxFormLink("addGridSubset") {
private static final long serialVersionUID = 1L;
@Override
protected void onClick(AjaxRequestTarget target, Form<?> form) {
availableGridSets.processInput();
final String selectedGridset = availableGridSets.getModelObject();
if (null == selectedGridset) {
return;
}
List<String> choices = new ArrayList<String>(availableGridSets.getChoices());
choices.remove(selectedGridset);
availableGridSets.setChoices(choices);
availableGridSets.setEnabled(!choices.isEmpty());
XMLGridSubset newSubset = new XMLGridSubset();
newSubset.setGridSetName(selectedGridset);
grids.getModelObject().add(newSubset);
target.add(table);
target.add(availableGridSets);
}
};
addGridsubsetLink.add(new Icon("addIcon", GWCIconFactory.ADD_ICON));
add(addGridsubsetLink);
availableGridSets = new DropDownChoice<String>("availableGridsets", new Model<String>(),
gridSetNames);
availableGridSets.setOutputMarkupId(true);
add(availableGridSets);
}
@Override
public void convertInput() {
grids.visitChildren((component, visit) -> {
if (component instanceof FormComponent) {
FormComponent<?> formComponent = (FormComponent<?>) component;
formComponent.processInput();
}
});
List<XMLGridSubset> info = grids.getModelObject();
HashSet<XMLGridSubset> convertedInput = new HashSet<XMLGridSubset>(info);
setConvertedInput(convertedInput);
}
/**
*/
@Override
protected void onBeforeRender() {
super.onBeforeRender();
}
public void setValidating(final boolean validate) {
validator.setEnabled(validate);
}
private static class ZoomLevelDropDownChoice extends DropDownChoice<Integer> {
private static final long serialVersionUID = -1312406093015271637L;
public ZoomLevelDropDownChoice(final String id, IModel<Integer> model,
IModel<List<Integer>> allChoices) {
super(id, model, allChoices);
setNullValid(true);// show null option even if model value isn't null
setOutputMarkupId(true);
}
@Override
protected String getNullValidKey() {
String nullValidKey = "GridSubsetsEditor." + getId() + ".nullValid";
return nullValidKey;
}
public void setAllowedMin(int newMin) {
List<? extends Integer> choices = getChoices();
final int max = choices.get(choices.size() - 1).intValue();
setChoices(newMin, Math.max(newMin, max));
}
public void setAllowedMax(int max) {
List<? extends Integer> choices = getChoices();
final int min = choices.get(0).intValue();
setChoices(Math.min(min, max), max);
}
private void setChoices(int min, int max) {
List<Integer> choices = new ArrayList<Integer>();
for (int i = min; i <= max; i++) {
choices.add(Integer.valueOf(i));
}
Integer modelObject = getModelObject();
setChoices(choices);
if (modelObject != null && modelObject >= min && modelObject <= max) {
setModelObject(modelObject);
} else {
setModelObject(null);
}
}
}
private void updateValidZoomRanges(final ZoomLevelDropDownChoice zoomStart,
final ZoomLevelDropDownChoice zoomStop, final ZoomLevelDropDownChoice minCachedLevel,
final ZoomLevelDropDownChoice maxCachedLevel, AjaxRequestTarget target) {
// zoomStart.processInput();
Integer min = zoomStart.getModelObject();
if (min != null) {
zoomStop.setAllowedMin(min.intValue());
minCachedLevel.setAllowedMin(min.intValue());
maxCachedLevel.setAllowedMin(min.intValue());
}
Integer max = zoomStop.getModelObject();
if (max != null) {
minCachedLevel.setAllowedMax(max.intValue());
maxCachedLevel.setAllowedMax(max.intValue());
}
Integer minCached = minCachedLevel.getModelObject();
if (minCached != null) {
maxCachedLevel.setAllowedMin(minCached.intValue());
}
if (null != target) {
target.add(zoomStop);
target.add(minCachedLevel);
target.add(maxCachedLevel);
}
}
}