/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2014 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.web.data.resource;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.OnChangeAjaxBehavior;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.FormComponentPanel;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.ReuseIfModelsEqualStrategy;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.geoserver.catalog.DimensionInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.web.publish.PublishedEditTabPanel;
import org.geoserver.web.util.MetadataMapModel;
import org.geoserver.web.wicket.EnumChoiceRenderer;
import org.geoserver.web.wicket.GeoServerDataProvider.BeanProperty;
import org.geoserver.web.wicket.GeoServerDataProvider.Property;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geoserver.web.wicket.ReorderableTablePanel;
import org.geoserver.wms.dimension.DefaultValueConfiguration;
import org.geoserver.wms.dimension.DefaultValueConfiguration.DefaultValuePolicy;
import org.geoserver.wms.dimension.DefaultValueConfigurations;
/**
* Adds a tab to the layer editor to allow editing the dynamic dimension default values
*
* @author Andrea Aime - GeoSolutions
*/
public class DynamicDimensionsTabPanel extends PublishedEditTabPanel<LayerInfo> {
private static final long serialVersionUID = -5736472115970942723L;
Property<DefaultValueConfiguration> DIMENSION = new BeanProperty<DefaultValueConfiguration>(
"dimension", "dimension");
Property<DefaultValueConfiguration> POLICY = new BeanProperty<DefaultValueConfiguration>(
"policy", "policy");
Property<DefaultValueConfiguration> DEFAULT_VALUE_EXPRESSION = new BeanProperty<DefaultValueConfiguration>(
"defaultValueExpression", "defaultValueExpression");
ReorderableTablePanel<DefaultValueConfiguration> table;
List<DefaultValueConfiguration> configurations;
IModel<LayerInfo> model;
public DynamicDimensionsTabPanel(String id, IModel<LayerInfo> model) {
super(id, model);
MetadataMapModel configsModel = new MetadataMapModel(new PropertyModel<MetadataMap>(model,
"resource.metadata"), DefaultValueConfigurations.KEY,
DefaultValueConfigurations.class);
final LayerInfo layer = model.getObject();
configurations = getConfigurations(model, configsModel);
Editor editor = new Editor("editor", getEnabledDimensionNames(layer), configsModel);
add(editor);
}
private List<DefaultValueConfiguration> getConfigurations(IModel<LayerInfo> layerModel,
IModel<DefaultValueConfigurations> configModel) {
ArrayList<DefaultValueConfiguration> result = new ArrayList<DefaultValueConfiguration>();
// see if we have configs already
DefaultValueConfigurations configs = configModel.getObject();
if (configs != null) {
result.addAll(configs.getConfigurations());
} else {
configs = new DefaultValueConfigurations(new ArrayList<DefaultValueConfiguration>());
}
// add missing dimension configs
Set<String> dimensionNames = getEnabledDimensionNames(layerModel.getObject());
for (String dimensionName : dimensionNames) {
addIfMissing(dimensionName, result);
}
// remove unknown ones
for (Iterator<DefaultValueConfiguration> it = result.iterator(); it.hasNext();) {
DefaultValueConfiguration config = it.next();
if (!dimensionNames.contains(config.getDimension())) {
it.remove();
}
}
configs.getConfigurations().clear();
configs.getConfigurations().addAll(result);
return result;
}
Set<String> getEnabledDimensionNames(LayerInfo layer) {
Set<String> dimensionNames = new HashSet<String>();
for (Map.Entry<String, Serializable> entry : layer.getResource().getMetadata().entrySet()) {
String key = entry.getKey();
Serializable md = entry.getValue();
if (md instanceof DimensionInfo) {
// skip disabled dimensions
DimensionInfo di = (DimensionInfo) md;
if (!di.isEnabled()) {
continue;
}
// get the dimension name
String dimensionName;
if (key.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) {
dimensionName = key.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length());
} else {
dimensionName = key;
}
dimensionNames.add(dimensionName);
}
}
return dimensionNames;
}
private void addIfMissing(String dimension, ArrayList<DefaultValueConfiguration> configs) {
for (DefaultValueConfiguration config : configs) {
if (dimension.equals(config.getDimension())) {
return;
}
}
configs.add(new DefaultValueConfiguration(dimension, DefaultValuePolicy.STANDARD));
}
class Editor extends FormComponentPanel<DefaultValueConfigurations> {
private static final long serialVersionUID = -1339941470144600565L;
public Editor(String id, final Collection<String> enabledDimensionNames,
IModel<DefaultValueConfigurations> model) {
super(id, model);
List<Property<DefaultValueConfiguration>> properties = Arrays.asList(DIMENSION, POLICY,
DEFAULT_VALUE_EXPRESSION);
table = new ReorderableTablePanel<DefaultValueConfiguration>("defaultConfigs",
configurations, properties) {
private static final long serialVersionUID = 8562909315041926320L;
@Override
protected Component getComponentForProperty(String id, final IModel<DefaultValueConfiguration> itemModel,
Property<DefaultValueConfiguration> property) {
if (DEFAULT_VALUE_EXPRESSION.equals(property)) {
Fragment f = new Fragment(id, "ecqlEditor", DynamicDimensionsTabPanel.this);
TextArea<String> ta = new TextArea<String>("editor");
// a dimension expression cannot refer to itself
List<String> otherDimensions = new ArrayList<String>(enabledDimensionNames);
String currentDimension = ((DefaultValueConfiguration) itemModel
.getObject()).getDimension();
otherDimensions.remove(currentDimension);
ta.add(new ECQLValidator().setValidAttributes(otherDimensions));
ta.setModel((IModel<String>) property.getModel(itemModel));
ta.setOutputMarkupId(true);
Object currentPolicy = POLICY.getModel(itemModel).getObject();
ta.setVisible(DefaultValuePolicy.EXPRESSION.equals(currentPolicy));
f.add(ta);
return f;
} else if (POLICY.equals(property)) {
Fragment f = new Fragment(id, "policyChoice",
DynamicDimensionsTabPanel.this);
final DropDownChoice<DefaultValuePolicy> dd = new DropDownChoice<DefaultValueConfiguration.DefaultValuePolicy>(
"choice", Arrays.asList(DefaultValuePolicy.values()));
dd.setChoiceRenderer(new EnumChoiceRenderer(DynamicDimensionsTabPanel.this));
dd.setModel((IModel<DefaultValuePolicy>) property.getModel(itemModel));
f.add(dd);
return f;
}
return null;
}
@Override
protected void onPopulateItem(final Property<DefaultValueConfiguration> property,
final ListItem<Property<DefaultValueConfiguration>> item) {
super.onPopulateItem(property, item);
// assuming that if we got here, everything before it has been populated
if (property == DEFAULT_VALUE_EXPRESSION) {
MarkupContainer parent = item.getParent();
// drill down into the containers to get the actual form components we want
final DropDownChoice<?> dd = (DropDownChoice<?>) ((Fragment) ((ListItem<?>) parent
.get(3))
.get(0)).get(0);
final TextArea<?> ta = (TextArea<?>)((Fragment) ((ListItem<?>) parent.get(4)).get(0)).get(0);
dd.add(new OnChangeAjaxBehavior() {
private static final long serialVersionUID = -6159626785706793328L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
DefaultValuePolicy currentPolicy = (DefaultValuePolicy) dd
.getConvertedInput();
ta.setVisible(DefaultValuePolicy.EXPRESSION.equals(currentPolicy));
target.add(ta);
target.add(table);
}
});
}
item.add(new Behavior() {
private static final long serialVersionUID = 685833036040462732L;
public void onComponentTag(Component component, ComponentTag tag) {
if (property == DEFAULT_VALUE_EXPRESSION) {
tag.put("style", "width:99%");
} else {
tag.put("style", "width:1%");
}
}
});
}
};
table.setFilterable(false);
table.setSortable(false);
table.setPageable(false);
table.setItemReuseStrategy(ReuseIfModelsEqualStrategy.getInstance());
table.setOutputMarkupId(true);
add(table);
add(new IValidator<DefaultValueConfigurations>() {
private static final long serialVersionUID = 530635923236054192L;
@Override
public void validate(IValidatable<DefaultValueConfigurations> validatable) {
DefaultValueConfigurations configurations = validatable.getValue();
for (DefaultValueConfiguration config : configurations.getConfigurations()) {
if (config.getPolicy() == DefaultValuePolicy.EXPRESSION
&& config.getDefaultValueExpression() == null) {
error(new ParamResourceModel("expressionRequired",
DynamicDimensionsTabPanel.this).getString());
}
}
}
});
}
@Override
public void convertInput() {
visitChildren(TextArea.class, (component, visit) -> {
if (component instanceof TextArea) {
TextArea textArea = (TextArea) component;
textArea.updateModel();
}
});
setConvertedInput(new DefaultValueConfigurations(configurations));
}
};
}