/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wms.eo.web; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; 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.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.SubmitLink; 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.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.DimensionPresentation; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.PublishedType; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.SingleGridCoverage2DReader; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.impl.DimensionInfoImpl; import org.geoserver.catalog.util.ReaderDimensionsAccessor; import org.geoserver.web.GeoServerSecuredPage; import org.geoserver.web.data.store.StoreChoiceRenderer; import org.geoserver.web.data.store.StoreNameComparator; import org.geoserver.web.wicket.GeoServerDataProvider; import org.geoserver.web.wicket.GeoServerDataProvider.Property; import org.geoserver.web.wicket.GeoServerTablePanel; import org.geoserver.web.wicket.ParamResourceModel; import org.geoserver.wms.eo.EoLayerType; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.util.logging.Logging; import org.opengis.coverage.grid.GridCoverageReader; public class EoCoverageSelectorPage extends GeoServerSecuredPage { static final Logger LOGGER = Logging.getLogger(EoCoverageSelectorPage.class); List<EoCoverageSelection> selections = new ArrayList<EoCoverageSelectorPage.EoCoverageSelection>(); CoverageStoreInfo selectedStore; private DropDownChoice<CoverageStoreInfo> stores; private List<EoLayerGroupEntry> layerGroupEntries; private String groupName; public EoCoverageSelectorPage(EoLayerGroupAbstractPage owner, String layerGroupName, String coverageStoreId) { this(owner, layerGroupName); this.groupName = layerGroupName; CoverageStoreInfo store = getCatalog().getStore(coverageStoreId, CoverageStoreInfo.class); stores.setDefaultModelObject(store); stores.setVisible(false); updateCoveragesList(true); } public EoCoverageSelectorPage(EoLayerGroupAbstractPage returnPage, String groupName) { setReturnPage(returnPage); this.groupName = groupName; layerGroupEntries = returnPage.lgEntryPanel.entryProvider.getItems(); Form form = new Form("form"); add(form); List<CoverageStoreInfo> coverageStores = new ArrayList<CoverageStoreInfo>(getCatalog() .getCoverageStores()); Collections.sort(coverageStores, new StoreNameComparator()); stores = new DropDownChoice<CoverageStoreInfo>( "store", coverageStores, new StoreChoiceRenderer()); stores.setModel(new PropertyModel(this, "selectedStore")); stores.setRequired(true); form.add(stores); final WebMarkupContainer coveragesContainer = new WebMarkupContainer("coveragesContainer"); coveragesContainer.setOutputMarkupId(true); form.add(coveragesContainer); GeoServerTablePanel<EoCoverageSelection> coverages = new GeoServerTablePanel<EoCoverageSelection>( "coverages", new EoCoverageSelectionProvider(selections)) { @Override protected Component getComponentForProperty(String id, IModel<EoCoverageSelection> itemModel, Property<EoCoverageSelection> property) { if ("type".equals(property.getName())) { DropDownChoice<EoLayerType> layerTypes = new DropDownChoice<EoLayerType>( "type", (IModel<EoLayerType>) property.getModel(itemModel), EoLayerType.getRasterTypes(true), new EoLayerTypeRenderer()); Fragment fragment = new Fragment(id, "typeFragment", EoCoverageSelectorPage.this); fragment.add(layerTypes); return fragment; } return null; } }; coverages.setItemReuseStrategy(ReuseIfModelsEqualStrategy.getInstance()); coveragesContainer.add(coverages); // link the store dropdown to the coverages table stores.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { updateCoveragesList(true); target.add(coveragesContainer); target.add(feedbackPanel); } }); form.add(addToLayerGroupLink()); form.add(cancelLink()); } protected void updateCoveragesList(boolean reportSkippedLayers) { selections.clear(); if (stores.getModelObject() != null) { try { CoverageStoreInfo store = (CoverageStoreInfo) stores.getModelObject(); GridCoverageReader reader = store.getGridCoverageReader(null, null); String[] names = reader.getGridCoverageNames(); Arrays.sort(names); for (String name : names) { LayerInfo li = getPreExistingLayer(name, store); if(li == null) { selections.add(new EoCoverageSelection(name, EoLayerType.IGNORE)); } else if(reportSkippedLayers) { info("Skipping coverage " + name + " as it's already part of the group as " + li.getName()); } } } catch (IOException e) { LOGGER.log(Level.SEVERE, "Failed to list coverages in the specified store", e); error("Failed to list coverages in the specified store: " + e.getMessage()); } } } private LayerInfo getPreExistingLayer(String coverageName,StoreInfo si) { for (EoLayerGroupEntry entry : layerGroupEntries) { if(entry.getLayer() instanceof LayerInfo) { LayerInfo li = (LayerInfo) entry.getLayer(); if(li.getResource() instanceof CoverageInfo) { CoverageInfo ci = (CoverageInfo) li.getResource(); // if same store, check the native name if(ci.getStore().getId().equals(si.getId())) { if(ci.getNativeName().equals(coverageName)) { return li; } } } } } return null; } private Component cancelLink() { return new AjaxLink<String>("cancel") { @Override public void onClick(AjaxRequestTarget target) { doReturn(); } }; } private SubmitLink addToLayerGroupLink() { return new SubmitLink("save") { @Override public void onSubmit() { CatalogBuilder builder = new CatalogBuilder(getCatalog()); builder.setStore(selectedStore); EoLayerGroupAbstractPage layerGroupPage = (EoLayerGroupAbstractPage) returnPage; List<EoLayerGroupEntry> items = layerGroupPage.lgEntryPanel.entryProvider.getItems(); boolean error = false; for (EoCoverageSelection item : selections) { if (item.getType() != EoLayerType.IGNORE) { try { LayerInfo layer = createLayer(groupName, item, builder); if(layer == null) { error = true; } else { items.add(new EoLayerGroupEntry(layer, layer.getDefaultStyle(), layerGroupPage.lgModel.getObject().getName())); info("Layer " + layer.getName() + " successfully created and added to the EO layer group"); } } catch(Exception e) { error = true; error("Failed to create layer from covearge " + item.getCoverageName() + ": " + e.getMessage()); } } } if(!error) { setResponsePage(returnPage); } else { updateCoveragesList(true); } } }; } LayerInfo createLayer(String groupName, EoCoverageSelection selection, CatalogBuilder builder) { String coverageName = selection.getCoverageName(); EoLayerType layerType = selection.getType(); String name = coverageName; if(groupName != null) { name = groupName + "_" + name; } try { // build the coverage and enable its dimensions CoverageInfo resource = builder.buildCoverage(coverageName); boolean dimensionsPresent = enableDimensions(resource, layerType); if (!dimensionsPresent) { if(layerType == EoLayerType.BAND_COVERAGE) { error(new ParamResourceModel("EoLayerGroupError.invalidBandCoverage", null, coverageName).getString()); } else { error(new ParamResourceModel("EoLayerGroupError.invalidLayer", null, coverageName).getString()); } return null; } // update the name and save the coverage resource.setName(name); resource.setTitle(name); getCatalog().add(resource); // save the layer too LayerInfo layer = builder.buildLayer(resource); layer.setName(name); layer.setTitle(name); layer.setEnabled(true); layer.setQueryable(true); layer.setType(PublishedType.RASTER); layer.getMetadata().put(EoLayerType.KEY, layerType.name()); if(layerType == EoLayerType.BITMASK) { StyleInfo red = getCatalog().getStyleByName("red"); if(red != null) { layer.setDefaultStyle(red); } else { warn("Default style for bitmask layers 'red' was not found, the default 'raster' layer got assigned instead. Please fix"); } } getCatalog().add(layer); return layer; } catch (Exception e) { throw new IllegalArgumentException("The layer '" + name + "' could not be created. Failure message: " + e.getMessage(), e); } } /** * Check presence of TIME dimension and, if the layer is of band type, a custom dimension Enable * all dimensions found. */ private boolean enableDimensions(CoverageInfo ci, EoLayerType type) { boolean timeDimension = false; boolean customDimension = false; GridCoverage2DReader reader = null; try { // acquire a reader reader = (GridCoverage2DReader) ci.getGridCoverageReader(null, null); if (reader == null) { throw new RuntimeException("Unable to acquire reader for this coverageinfo: " + ci.getName()); } if(ci.getNativeCoverageName() != null) { reader = SingleGridCoverage2DReader.wrap(reader, ci.getNativeCoverageName()); } // inspect dimensions final ReaderDimensionsAccessor ra = new ReaderDimensionsAccessor(reader); for (String domain : ra.getCustomDomains()) { if (LOGGER.isLoggable(Level.FINE)) { boolean hasRange = ra.hasRange(domain); boolean hasResolution = ra.hasResolution(domain); LOGGER.fine(ci.getName() + ": found " + domain + " dimension (hasRange: " + hasRange + ", hasResolution: " + hasResolution + ")"); } DimensionInfo dimension = new DimensionInfoImpl(); dimension.setEnabled(true); dimension.setPresentation(DimensionPresentation.LIST); ci.getMetadata().put(ResourceInfo.CUSTOM_DIMENSION_PREFIX + domain, dimension); customDimension = true; } String elev = reader.getMetadataValue(GridCoverage2DReader.HAS_ELEVATION_DOMAIN); if (Boolean.parseBoolean(elev)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(ci.getName() + ": found ELEVATION dimension"); } DimensionInfo dimension = new DimensionInfoImpl(); dimension.setEnabled(true); dimension.setPresentation(DimensionPresentation.LIST); ci.getMetadata().put(ResourceInfo.ELEVATION, dimension); } String time = reader.getMetadataValue(GridCoverage2DReader.HAS_TIME_DOMAIN); if (Boolean.parseBoolean(time)) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine(ci.getName() + ": found TIME dimension"); } DimensionInfo dimension = new DimensionInfoImpl(); dimension.setEnabled(true); dimension.setPresentation(DimensionPresentation.LIST); ci.getMetadata().put(ResourceInfo.TIME, dimension); timeDimension = true; } } catch (IOException e) { if (LOGGER.isLoggable(Level.SEVERE)) { LOGGER.log(Level.SEVERE, "Failed to access coverage reader custom dimensions", e); } } if(type == EoLayerType.BAND_COVERAGE) { return timeDimension && customDimension; } else { return timeDimension; } } static class EoCoverageSelection implements Serializable { String coverageName; EoLayerType type; public EoCoverageSelection(String coverageName, EoLayerType type) { super(); this.coverageName = coverageName; this.type = type; } public String getCoverageName() { return coverageName; } public EoLayerType getType() { return type; } @Override public String toString() { return "EoCoverageSelection [coverageName=" + coverageName + ", type=" + type + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((coverageName == null) ? 0 : coverageName.hashCode()); result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; EoCoverageSelection other = (EoCoverageSelection) obj; if (coverageName == null) { if (other.coverageName != null) return false; } else if (!coverageName.equals(other.coverageName)) return false; if (type != other.type) return false; return true; } } static class EoCoverageSelectionProvider extends GeoServerDataProvider<EoCoverageSelection> { List<EoCoverageSelection> selections; public EoCoverageSelectionProvider(List<EoCoverageSelection> selections) { super(); this.selections = selections; } @Override protected List<org.geoserver.web.wicket.GeoServerDataProvider.Property<EoCoverageSelection>> getProperties() { List<GeoServerDataProvider.Property<EoCoverageSelection>> result = new ArrayList<GeoServerDataProvider.Property<EoCoverageSelection>>(); result.add(new BeanProperty("coverageName", "coverageName")); result.add(new BeanProperty("type", "type")); return result; } @Override protected List<EoCoverageSelection> getItems() { return selections; } } }