/* (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.lang.reflect.Constructor; import java.util.List; import org.apache.wicket.Component; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.model.CompoundPropertyModel; import org.apache.wicket.model.IModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.ProjectionPolicy; import org.geoserver.catalog.ResourceInfo; import org.geoserver.web.data.layer.LayerPage; import org.geoserver.web.publish.PublishedConfigurationPage; import org.geoserver.web.publish.PublishedConfigurationPanel; import org.geoserver.web.publish.PublishedEditTabPanel; import org.geoserver.web.wicket.ParamResourceModel; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.factory.GeoTools; import org.geotools.feature.NameImpl; import org.geotools.geometry.jts.ReferencedEnvelope; import org.opengis.coverage.grid.GridGeometry; /** * Page allowing to configure a layer and its resource. * <p> * The page is completely pluggable, the UI will be made up by scanning the Spring context for * implementations of {@link ResourceConfigurationPanel} and {@link PublishedConfigurationPanel}. * <p> * WARNING: one crucial aspect of this page is its ability to not loose edits when one switches from * one tab to the other. I did not find any effective way to unit test this, so _please_, if you do * modify anything in this class (especially the models), manually retest that the edits are not * lost on tab switch. */ public class ResourceConfigurationPage extends PublishedConfigurationPage<LayerInfo> { private static final long serialVersionUID = 7870938096047218989L; IModel<ResourceInfo> myResourceModel; public ResourceConfigurationPage(PageParameters parameters) { this(parameters.get(WORKSPACE).toOptionalString(), parameters.get(NAME).toString()); } public ResourceConfigurationPage(String workspaceName, String layerName) { super(false); this.returnPageClass = LayerPage.class; LayerInfo layer; if (workspaceName != null) { NamespaceInfo ns = getCatalog().getNamespaceByPrefix(workspaceName); if (ns == null) { // unlikely to happen, requires someone making modifications on the workspaces // with a layer page open in another tab/window throw new RuntimeException("Could not find workspace " + workspaceName); } String nsURI = ns.getURI(); layer = getCatalog().getLayerByName(new NameImpl(nsURI, layerName)); } else { layer = getCatalog().getLayerByName(layerName); } if (layer == null) { error(new ParamResourceModel("ResourceConfigurationPage.notFound", this, layerName) .getString()); setResponsePage(returnPage); return; } setupPublished(layer); setupResource(layer.getResource()); } public ResourceConfigurationPage(ResourceInfo info, boolean isNew) { super(isNew); this.returnPageClass = LayerPage.class; setupPublished(getCatalog().getLayers(info).get(0)); setupResource(info); } public ResourceConfigurationPage(LayerInfo info, boolean isNew) { super(info, isNew); this.returnPageClass = LayerPage.class; setupResource(isNew ? info.getResource() : getCatalog().getResource(info.getResource().getId(), ResourceInfo.class)); } private void setupResource(ResourceInfo resource) { getPublishedInfo().setResource(resource); myResourceModel = new CompoundPropertyModel<ResourceInfo>(new ResourceModel(resource)); } private List<ResourceConfigurationPanelInfo> filterResourcePanels( List<ResourceConfigurationPanelInfo> list) { for (int i = 0; i < list.size(); i++) { if (!list.get(i).canHandle(getResourceInfo())) { list.remove(i); i--; } } return list; } protected class DataLayerEditTabPanel extends ListEditTabPanel { private static final long serialVersionUID = -3442310698941800127L; public DataLayerEditTabPanel(String id) { super(id); } protected ListView<ResourceConfigurationPanelInfo> createList(String id) { List<ResourceConfigurationPanelInfo> dataPanels = filterResourcePanels(getGeoServerApplication() .getBeansOfType(ResourceConfigurationPanelInfo.class)); ListView<ResourceConfigurationPanelInfo> dataPanelList = new ListView<ResourceConfigurationPanelInfo>(id, dataPanels) { private static final long serialVersionUID = -845785165778837024L; @Override protected void populateItem(ListItem<ResourceConfigurationPanelInfo> item) { ResourceConfigurationPanelInfo panelInfo = (ResourceConfigurationPanelInfo) item .getModelObject(); try { final Class<ResourceConfigurationPanel> componentClass = panelInfo .getComponentClass(); final Constructor<ResourceConfigurationPanel> constructor; constructor = componentClass.getConstructor(String.class, IModel.class); ResourceConfigurationPanel panel = constructor.newInstance("content", myResourceModel); item.add((Component) panel); } catch (Exception e) { throw new WicketRuntimeException( "Failed to add pluggable resource configuration panels", e); } } }; return dataPanelList; } } /** * Returns the {@link ResourceInfo} contained in this page * * */ public ResourceInfo getResourceInfo() { return (ResourceInfo) myResourceModel.getObject(); } /** * Allows collaborating pages to update the resource info object * * @param info * @param target */ public void updateResource(ResourceInfo info) { updateResource(info, null); } /** * Allows collaborating pages to update the resource info object * * @param info the resource info to update * @param target */ public void updateResource(ResourceInfo info, final AjaxRequestTarget target) { myResourceModel.setObject(info); visitChildren((component, visit) -> { if (component instanceof ResourceConfigurationPanel) { ResourceConfigurationPanel rcp = (ResourceConfigurationPanel) component; rcp.resourceUpdated(target); visit.dontGoDeeper(); } }); } @Override protected PublishedEditTabPanel<LayerInfo> createMainTab(String panelID) { return new DataLayerEditTabPanel(panelID); } @Override protected void doSaveInternal() throws IOException { Catalog catalog = getCatalog(); ResourceInfo resourceInfo = getResourceInfo(); if (isNew) { // updating grid if is a coverage if (resourceInfo instanceof CoverageInfo) { // the coverage bounds computation path is a bit more linear, the // readers always return the bounds and in the proper CRS (afaik) CoverageInfo cinfo = (CoverageInfo) resourceInfo; GridCoverage2DReader reader = (GridCoverage2DReader) cinfo .getGridCoverageReader(null, GeoTools.getDefaultHints()); // get bounds final ReferencedEnvelope bounds = new ReferencedEnvelope( reader.getOriginalEnvelope()); // apply the bounds, taking into account the reprojection policy if need be final ProjectionPolicy projectionPolicy = resourceInfo.getProjectionPolicy(); if (projectionPolicy != ProjectionPolicy.NONE && bounds != null) { // we need to fix the registered grid for this coverage final GridGeometry grid = cinfo.getGrid(); cinfo.setGrid(new GridGeometry2D(grid.getGridRange(), grid.getGridToCRS(), resourceInfo.getCRS())); } } catalog.validate(resourceInfo, true).throwIfInvalid(); catalog.add(resourceInfo); try { catalog.add(getPublishedInfo()); } catch (IllegalArgumentException e) { catalog.remove(resourceInfo); throw e; } } else { ResourceInfo oldState = catalog.getResource(resourceInfo.getId(), ResourceInfo.class); catalog.validate(resourceInfo, true).throwIfInvalid(); catalog.save(resourceInfo); try { LayerInfo layer = getPublishedInfo(); layer.setResource(resourceInfo); catalog.save(layer); } catch (IllegalArgumentException e) { catalog.save(oldState); throw e; } } } }