/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package mil.nga.giat.elasticsearch; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import mil.nga.giat.data.elasticsearch.ElasticAttribute; import mil.nga.giat.data.elasticsearch.ElasticDataStore; import mil.nga.giat.data.elasticsearch.ElasticLayerConfiguration; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.form.AjaxButton; import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.CheckBox; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.PropertyModel; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.web.GeoServerApplication; import org.geoserver.web.wicket.GeoServerDataProvider.Property; import org.geoserver.web.wicket.GeoServerTablePanel; import org.geoserver.web.wicket.ParamResourceModel; import org.geotools.feature.NameImpl; import org.geotools.util.NullProgressListener; import org.geotools.util.logging.Logging; import org.opengis.feature.type.Name; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Class to render and manage the Elasticsearch modal dialog This dialog allow * the user to choice which Elasticsearch attributes include in layers, selects * attribute to use as GEOMETRY. */ public abstract class ElasticConfigurationPage extends Panel { private static final long serialVersionUID = 5615867383881988931L; private static final Logger LOGGER = Logging.getLogger(ElasticConfigurationPage.class); private FeedbackPanel feedbackPanel; private final String useAllMarkupId; private static final List<Class<? extends Geometry>> GEOMETRY_TYPES = Arrays.asList(Geometry.class, GeometryCollection.class, Point.class, MultiPoint.class, LineString.class, MultiLineString.class, Polygon.class, MultiPolygon.class); /** * Constructs the dialog to set Elasticsearch attributes and configuration * options. * * @see {@link ElasticAttributeProvider} * @see {@link ElasticAttribute} * */ public ElasticConfigurationPage(String panelId, final IModel model) { super(panelId, model); ResourceInfo ri = (ResourceInfo) model.getObject(); final Form elastic_form = new Form("es_form", new CompoundPropertyModel(this)); add(elastic_form); List<ElasticAttribute> attributes; attributes = fillElasticAttributes(ri).getAttributes(); final ElasticAttributeProvider attProvider = new ElasticAttributeProvider(attributes); final GeoServerTablePanel<ElasticAttribute> elasticAttributePanel; elasticAttributePanel = getElasticAttributePanel(attProvider); elastic_form.add(elasticAttributePanel); // select all check box boolean selectAll = true; for (final ElasticAttribute attribute : attributes) { if (attribute.isUse() == null || !attribute.isUse()) { selectAll = false; } } AjaxCheckBox useAllCheckBox = new AjaxCheckBox("useAll", Model.of(selectAll)) { @Override protected void onUpdate(AjaxRequestTarget target) { final boolean use = (Boolean) this.getDefaultModelObject(); for (final ElasticAttribute attribute : attProvider.getItems()) { attribute.setUse(use); } target.add(elasticAttributePanel); } }; useAllCheckBox.setOutputMarkupId(true); elastic_form.add(useAllCheckBox); useAllMarkupId = useAllCheckBox.getMarkupId(); // use short name check box final Boolean useShortName; if (!attributes.isEmpty() && attributes.get(0).getUseShortName() != null) { useShortName = attributes.get(0).getUseShortName(); } else { useShortName = false; } AjaxCheckBox checkBox = new AjaxCheckBox("useShortName", Model.of(useShortName)) { @Override protected void onUpdate(AjaxRequestTarget target) { final boolean useShortName = (Boolean) this.getDefaultModelObject(); for (final ElasticAttribute attribute : attProvider.getItems()) { attribute.setUseShortName(useShortName); } target.add(elasticAttributePanel); } }; checkBox.setOutputMarkupId(true); elastic_form.add(checkBox); elastic_form.add(new AjaxButton("es_save") { protected void onSubmit(AjaxRequestTarget target, Form form) { onSave(target); } }); feedbackPanel = new FeedbackPanel("es_feedback"); feedbackPanel.setOutputMarkupId(true); elastic_form.add(feedbackPanel); } /** * Do nothing */ protected void onCancel(AjaxRequestTarget target) { done(target, null, null); } /** * Validates Elasticsearch attributes configuration and stores the * Elasticsearch layer configuration into feature type metadata as * {@link ElasticLayerConfiguration#KEY} <br> * Validation include the follow rules <li>One attribute must be a GEOMETRY. * * @see {@link ElasticLayerConfiguration} * @see {@link FeatureTypeInfo#getMetadata} */ protected void onSave(AjaxRequestTarget target) { try { ResourceInfo ri = (ResourceInfo) getDefaultModel().getObject(); ElasticLayerConfiguration layerConfig = fillElasticAttributes(ri); Boolean geomSet = false; // Validate configuration for (ElasticAttribute att : layerConfig.getAttributes()) { if (Geometry.class.isAssignableFrom(att.getType()) && att.isUse()) { geomSet = true; } } if (!geomSet) { error(new ParamResourceModel("geomEmptyFailure", ElasticConfigurationPage.this) .getString()); } Catalog catalog = ((GeoServerApplication) this.getPage().getApplication()).getCatalog(); FeatureTypeInfo typeInfo; DataStoreInfo dsInfo = catalog.getStore(ri.getStore().getId(), DataStoreInfo.class); ElasticDataStore ds = (ElasticDataStore) dsInfo.getDataStore(null); CatalogBuilder builder = new CatalogBuilder(catalog); builder.setStore(dsInfo); typeInfo = builder.buildFeatureType(ds.getFeatureSource(ri.getQualifiedName())); typeInfo.setName(ri.getName()); typeInfo.getMetadata().put(ElasticLayerConfiguration.KEY, layerConfig); LayerInfo layerInfo = builder.buildLayer(typeInfo); layerInfo.setName(ri.getName()); done(target, layerInfo, layerConfig); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); error(new ParamResourceModel("creationFailure", this, e).getString()); } } /* * Load ElasticLayerConfiguration configuration before shows on table Reloads * Elasticsearch attributes from datastore and merge it with user attributes * configurations */ private ElasticLayerConfiguration fillElasticAttributes(ResourceInfo ri) { ElasticLayerConfiguration layerConfig = (ElasticLayerConfiguration) ri.getMetadata() .get(ElasticLayerConfiguration.KEY); if (layerConfig == null) { layerConfig = new ElasticLayerConfiguration(ri.getName()); ri.getMetadata().put(ElasticLayerConfiguration.KEY, layerConfig); } try { ElasticDataStore dataStore = (ElasticDataStore) ((DataStoreInfo) ri.getStore()) .getDataStore(new NullProgressListener()); ArrayList<ElasticAttribute> result = new ArrayList<ElasticAttribute>(); Map<String, ElasticAttribute> tempMap = new HashMap<String, ElasticAttribute>(); final List<ElasticAttribute> attributes; if (layerConfig.getAttributes() != null) { attributes = layerConfig.getAttributes(); for (ElasticAttribute att : attributes) { tempMap.put(att.getName(), att); } } else { attributes = new ArrayList<>(); layerConfig.getAttributes().addAll(attributes); } final String docType = layerConfig.getDocType(); final Name layerName = new NameImpl(layerConfig.getLayerName()); dataStore.getDocTypes().put(layerName, docType); for (ElasticAttribute at : dataStore.getElasticAttributes(layerName)) { if (tempMap.containsKey(at.getName())) { ElasticAttribute prev = tempMap.get(at.getName()); at = prev; } result.add(at); } layerConfig.getAttributes().clear(); layerConfig.getAttributes().addAll(result); } catch (Exception e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); } return layerConfig; } /* * Builds attribute table */ private GeoServerTablePanel<ElasticAttribute> getElasticAttributePanel( ElasticAttributeProvider attProvider) { GeoServerTablePanel<ElasticAttribute> atts = new GeoServerTablePanel<ElasticAttribute>( "esAttributes", attProvider) { @Override protected Component getComponentForProperty(String id, IModel<ElasticAttribute> itemModel, Property<ElasticAttribute> property) { ElasticAttribute att = (ElasticAttribute) itemModel.getObject(); boolean isGeometry = att.getType() != null && Geometry.class.isAssignableFrom(att.getType()); if (property == ElasticAttributeProvider.NAME && isGeometry) { Fragment f = new Fragment(id, "label", ElasticConfigurationPage.this); f.add(new Label("label", att.getDisplayName() + "*")); return f; } else if (property == ElasticAttributeProvider.TYPE && isGeometry) { Fragment f = new Fragment(id, "geometry", ElasticConfigurationPage.this); f.add(new DropDownChoice("geometry", new PropertyModel(itemModel, "type"), GEOMETRY_TYPES, new GeometryTypeRenderer())); return f; } else if (property == ElasticAttributeProvider.USE) { CheckBox checkBox = new CheckBox("use", new PropertyModel<Boolean>(itemModel, "use")); final String onclick = "document.getElementById(\"" + useAllMarkupId + "\").checked = false;"; checkBox.add(new AttributeAppender("onclick", new Model<String>(onclick), ";")); Fragment f = new Fragment(id, "checkboxUse", ElasticConfigurationPage.this); f.add(checkBox); return f; } else if (property == ElasticAttributeProvider.DEFAULT_GEOMETRY) { if (isGeometry) { Fragment f = new Fragment(id, "checkboxDefaultGeometry", ElasticConfigurationPage.this); f.add(new CheckBox("defaultGeometry", new PropertyModel<Boolean>(itemModel, "defaultGeometry"))); return f; } else { Fragment f = new Fragment(id, "empty", ElasticConfigurationPage.this); return f; } } else if (property == ElasticAttributeProvider.SRID) { if (isGeometry) { Fragment f = new Fragment(id, "label", ElasticConfigurationPage.this); f.add(new Label("label", String.valueOf(att.getSrid()))); return f; } else { Fragment f = new Fragment(id, "empty", ElasticConfigurationPage.this); return f; } } else if (property == ElasticAttributeProvider.DATE_FORMAT) { if (att.getDateFormat() != null) { Fragment f = new Fragment(id, "label", ElasticConfigurationPage.this); f.add(new Label("label", String.valueOf(att.getDateFormat()))); return f; } else { Fragment f = new Fragment(id, "empty", ElasticConfigurationPage.this); return f; } } else if (property == ElasticAttributeProvider.ANALYZED) { if (att.getAnalyzed() != null && att.getAnalyzed()) { Fragment f = new Fragment(id, "label", ElasticConfigurationPage.this); f.add(new Label("label", "x")); return f; } else { Fragment f = new Fragment(id, "empty", ElasticConfigurationPage.this); return f; } } else if (property == ElasticAttributeProvider.STORED) { if (att.isStored()) { Fragment f = new Fragment(id, "label", ElasticConfigurationPage.this); f.add(new Label("label", "x")); return f; } else { Fragment f = new Fragment(id, "empty", ElasticConfigurationPage.this); return f; } } return null; } @Override protected void onPopulateItem(Property<ElasticAttribute> property, ListItem<Property<ElasticAttribute>> item) { if (property == ElasticAttributeProvider.STORED) { item.add(new AttributeModifier("style",Model.of("text-align:center"))); } else if (property == ElasticAttributeProvider.ANALYZED) { item.add(new AttributeModifier("style",Model.of("text-align:center"))); } } }; atts.setOutputMarkupId(true); atts.setFilterVisible(false); atts.setSortable(false); atts.setPageable(false); atts.setOutputMarkupId(true); return atts; } /* * Render geometry type select */ private static class GeometryTypeRenderer implements IChoiceRenderer { public Object getDisplayValue(Object object) { return ((Class) object).getSimpleName(); } public String getIdValue(Object object, int index) { return (String) getDisplayValue(object); } @Override public Object getObject(String id, IModel choices) { for (Class<? extends Geometry> c : GEOMETRY_TYPES) { if (id.equals(getDisplayValue(c))) { return c; } } return null; } } /** * Abstract method to implements in panel that opens the dialog to close the dialog itself <br> * This method is called after modal executes its operation * * @param target ajax response target * @param layerInfo GeoServer layer configuration * @param layerConfig Elasticsearch layer configuration * * @see {@link #onSave} * @see {@link #onCancel} * */ abstract void done(AjaxRequestTarget target, LayerInfo layerInfo, ElasticLayerConfiguration layerConfig); }