/* (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.web.data.resource;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.beanutils.BeanToPropertyValueTransformer;
import org.apache.commons.collections.CollectionUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextArea;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.link.Link;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
import org.geoserver.catalog.AttributeTypeInfo;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.ResourcePool;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.data.layer.CascadedWFSStoredQueryEditPage;
import org.geoserver.web.data.layer.SQLViewEditPage;
import org.geoserver.web.wicket.GeoServerAjaxFormLink;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geotools.data.wfs.internal.v2_0.storedquery.StoredQueryConfiguration;
import org.geotools.filter.FilterAttributeExtractor;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.jdbc.VirtualTable;
import org.geotools.measure.Measure;
import org.geotools.util.logging.Logging;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter;
@SuppressWarnings("serial")
public class FeatureResourceConfigurationPanel extends ResourceConfigurationPanel {
static final Logger LOGGER = Logging.getLogger(FeatureResourceConfigurationPanel.class);
ModalWindow reloadWarningDialog;
ListView attributes;
private Fragment attributePanel;
public FeatureResourceConfigurationPanel(String id, final IModel model) {
super(id, model);
CheckBox circularArcs = new CheckBox("circularArcPresent");
add(circularArcs);
TextField<Measure> tolerance = new TextField<Measure>("linearizationTolerance", Measure.class);
add(tolerance);
attributePanel = new Fragment("attributePanel", "attributePanelFragment", this);
attributePanel.setOutputMarkupId(true);
add(attributePanel);
// We need to use the resourcePool directly because we're playing with an edited
// FeatureTypeInfo and the info.getFeatureType() and info.getAttributes() will hit
// the resource pool without the modified properties (since it passes "this" into calls
// to the ResourcePoool
// just use the direct attributes, this is not editable atm
attributes = new ListView("attributes", new AttributeListModel()) {
@Override
protected void populateItem(ListItem item) {
// odd/even style
item.add(AttributeModifier.replace("class",
item.getIndex() % 2 == 0 ? "even" : "odd"));
// dump the attribute information we have
AttributeTypeInfo attribute = (AttributeTypeInfo) item.getModelObject();
item.add(new Label("name", attribute.getName()));
item.add(new Label("minmax", attribute.getMinOccurs() + "/" + attribute.getMaxOccurs()));
try {
// working around a serialization issue
FeatureTypeInfo typeInfo = (FeatureTypeInfo) model.getObject();
final ResourcePool resourcePool = GeoServerApplication.get().getCatalog().getResourcePool();
final FeatureType featureType = resourcePool.getFeatureType(typeInfo);
org.opengis.feature.type.PropertyDescriptor pd = featureType.getDescriptor(attribute.getName());
String typeName = "?";
String nillable = "?";
try {
typeName = pd.getType().getBinding().getSimpleName();
nillable = String.valueOf(pd.isNillable());
} catch(Exception e) {
LOGGER.log(Level.INFO, "Could not find attribute " + attribute.getName() + " in feature type " + featureType, e);
}
item.add(new Label("type", typeName));
item.add(new Label("nillable", nillable));
} catch(IOException e) {
item.add(new Label("type", "?"));
item.add(new Label("nillable", "?"));
}
}
};
attributePanel.add(attributes);
TextArea<String> cqlFilter = new TextArea<String>("cqlFilter");
cqlFilter.add(new CqlFilterValidator(model));
add(cqlFilter);
// reload links
WebMarkupContainer reloadContainer = new WebMarkupContainer("reloadContainer");
attributePanel.add(reloadContainer);
GeoServerAjaxFormLink reload = new GeoServerAjaxFormLink("reload") {
@Override
protected void onClick(AjaxRequestTarget target, Form form) {
GeoServerApplication app = (GeoServerApplication) getApplication();
FeatureTypeInfo ft = (FeatureTypeInfo)getResourceInfo();
app.getCatalog().getResourcePool().clear(ft);
app.getCatalog().getResourcePool().clear(ft.getStore());
target.add(attributePanel);
}
};
reloadContainer.add(reload);
GeoServerAjaxFormLink warning = new GeoServerAjaxFormLink("reloadWarning") {
@Override
protected void onClick(AjaxRequestTarget target, Form form) {
reloadWarningDialog.show(target);
}
};
reloadContainer.add(warning);
add(reloadWarningDialog = new ModalWindow("reloadWarningDialog"));
reloadWarningDialog.setPageCreator(new ModalWindow.PageCreator() {
public Page createPage() {
return new ReloadWarningDialog(
new StringResourceModel("featureTypeReloadWarning", FeatureResourceConfigurationPanel.this, null));
}
});
reloadWarningDialog.setTitle(new StringResourceModel("warning", (Component) null, null));
reloadWarningDialog.setInitialHeight(100);
reloadWarningDialog.setInitialHeight(200);
// sql view handling
WebMarkupContainer sqlViewContainer = new WebMarkupContainer("editSqlContainer");
attributePanel.add(sqlViewContainer);
sqlViewContainer.add(new Link("editSql") {
@Override
public void onClick() {
FeatureTypeInfo typeInfo = (FeatureTypeInfo) model.getObject();
try {
setResponsePage(new SQLViewEditPage(typeInfo, ((ResourceConfigurationPage) this.getPage())));
} catch(Exception e) {
LOGGER.log(Level.SEVERE, "Failure opening the sql view edit page", e);
error(e.toString());
}
}
});
// which one do we show, reload or edit?
FeatureTypeInfo typeInfo = (FeatureTypeInfo) model.getObject();
reloadContainer.setVisible(typeInfo.getMetadata().get(FeatureTypeInfo.JDBC_VIRTUAL_TABLE, VirtualTable.class) == null);
sqlViewContainer.setVisible(!reloadContainer.isVisible());
// Cascaded Stored Query
WebMarkupContainer cascadedStoredQueryContainer = new WebMarkupContainer("editCascadedStoredQueryContainer");
attributePanel.add(cascadedStoredQueryContainer);
cascadedStoredQueryContainer.add(new Link("editCascadedStoredQuery") {
@Override
public void onClick() {
FeatureTypeInfo typeInfo = (FeatureTypeInfo) model.getObject();
try {
setResponsePage(new CascadedWFSStoredQueryEditPage(typeInfo, ((ResourceConfigurationPage) this.getPage())));
} catch(Exception e) {
LOGGER.log(Level.SEVERE, "Failure opening the sql view edit page", e);
error(e.toString());
}
}
});
cascadedStoredQueryContainer.setVisible(typeInfo.getMetadata().get(FeatureTypeInfo.STORED_QUERY_CONFIGURATION, StoredQueryConfiguration.class) != null);
}
@Override
public void resourceUpdated(AjaxRequestTarget target) {
if (target != null) {
// force it to reload the attribute list
attributes.getModel().detach();
target.add(attributePanel);
}
}
static class ReloadWarningDialog extends WebPage {
public ReloadWarningDialog(StringResourceModel message) {
add(new Label("message", message));
}
}
/*
* Wicket validator to check CQL filter string
*/
private class CqlFilterValidator implements IValidator<String> {
private FeatureTypeInfo typeInfo;
public CqlFilterValidator(IModel model) {
this.typeInfo = (FeatureTypeInfo) model.getObject();
}
@Override
public void validate(IValidatable<String> validatable) {
try {
validateCqlFilter(typeInfo, validatable.getValue());
} catch (Exception e) {
ValidationError error = new ValidationError();
error.setMessage(e.getMessage());
validatable.error(error);
}
}
}
/*
* Validate that CQL filter syntax is valid, and attribute names used in the CQL filter are actually part of the layer
*/
private void validateCqlFilter(FeatureTypeInfo typeInfo,
String cqlFilterString) throws Exception {
Filter cqlFilter = null;
if (cqlFilterString != null && !cqlFilterString.isEmpty()) {
cqlFilter = ECQL.toFilter(cqlFilterString);
FeatureType ft = typeInfo.getFeatureType();
if (ft instanceof SimpleFeatureType) {
SimpleFeatureType sft = (SimpleFeatureType) ft;
BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer(
"localName");
Collection<String> featureAttributesNames = CollectionUtils.collect(
sft.getAttributeDescriptors(), transformer);
FilterAttributeExtractor filterAttriubtes = new FilterAttributeExtractor(null);
cqlFilter.accept(filterAttriubtes, null);
Set<String> filterAttributesNames = filterAttriubtes.getAttributeNameSet();
for (String filterAttributeName : filterAttributesNames) {
if (!featureAttributesNames.contains(filterAttributeName)) {
throw new ResourceConfigurationException(
ResourceConfigurationException.CQL_ATTRIBUTE_NAME_NOT_FOUND_$1,
new Object[] { filterAttributeName });
}
}
}
}
}
class AttributeListModel extends LoadableDetachableModel<List<AttributeTypeInfo>> {
@Override
protected List<AttributeTypeInfo> load() {
try {
FeatureTypeInfo typeInfo = (FeatureTypeInfo) getDefaultModelObject();
Catalog catalog = GeoServerApplication.get().getCatalog();
final ResourcePool resourcePool = catalog.getResourcePool();
// using loadAttributes to dodge the ResourcePool caches, the
// feature type structure might have been modified (e.g., SQL view editing)
return resourcePool.loadAttributes(typeInfo);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Grabbing the attribute list failed", e);
String error = new ParamResourceModel("attributeListingFailed", FeatureResourceConfigurationPanel.this, e.getMessage()).getString();
FeatureResourceConfigurationPanel.this.getPage().error(error);
return Collections.emptyList();
}
}
}
}