/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
* 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.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
import org.apache.wicket.extensions.markup.html.tabs.ITab;
import org.apache.wicket.extensions.markup.html.tabs.TabbedPanel;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.SubmitLink;
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.Panel;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ProjectionPolicy;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.GeoServerSecuredPage;
import org.geoserver.web.data.layer.LayerPage;
import org.geoserver.web.publish.LayerConfigurationPanel;
import org.geoserver.web.publish.LayerConfigurationPanelInfo;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
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 LayerConfigurationPanel}.
* <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.
*/
@SuppressWarnings("serial")
public class ResourceConfigurationPage extends GeoServerSecuredPage {
public static final String NAME = "name";
public static final String WORKSPACE = "wsName";
private IModel myResourceModel;
private IModel myLayerModel;
private boolean isNew;
public ResourceConfigurationPage(PageParameters parameters) {
this(parameters.getString(WORKSPACE), parameters.getString(NAME));
}
public ResourceConfigurationPage(String workspaceName, String layerName) {
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(LayerPage.class);
return;
}
setup(getCatalog().getResource(layer.getResource().getId(), ResourceInfo.class) , layer);
this.isNew = false;
initComponents();
}
public ResourceConfigurationPage(ResourceInfo info, boolean isNew) {
setup(info, getCatalog().getLayers(info).get(0));
this.isNew = isNew;
initComponents();
}
public ResourceConfigurationPage(LayerInfo info, boolean isNew) {
setup(info.getResource(), info);
this.isNew = isNew;
initComponents();
}
private void setup(ResourceInfo resource, LayerInfo layer) {
layer.setResource(resource);
myResourceModel = new CompoundPropertyModel(new ResourceModel(resource));
myLayerModel = new CompoundPropertyModel(new LayerModel(layer));
}
private void initComponents() {
add(new Label("resourcename", getResourceInfo().getPrefixedName()));
Form theForm = new Form("resource", myResourceModel);
add(theForm);
List<ITab> tabs = new ArrayList<ITab>();
//add the "well known" tabs
tabs.add(new AbstractTab(
new org.apache.wicket.model.ResourceModel("ResourceConfigurationPage.Data")) {
public Panel getPanel(String panelID) {
return new DataLayerEditTabPanel(panelID, myLayerModel);
}
});
tabs.add(new AbstractTab(
new org.apache.wicket.model.ResourceModel("ResourceConfigurationPage.Publishing")) {
public Panel getPanel(String panelID) {
return new PublishingLayerEditTabPanel(panelID, myLayerModel);
}
});
//add the tabs contributed via extension point
List<LayerEditTabPanelInfo> tabPanels =
getGeoServerApplication().getBeansOfType(LayerEditTabPanelInfo.class);
//sort the tabs based on order
Collections.sort(tabPanels, new Comparator<LayerEditTabPanelInfo>() {
public int compare(LayerEditTabPanelInfo o1, LayerEditTabPanelInfo o2) {
Integer order1 = o1.getOrder() >= 0 ? o1.getOrder() : Integer.MAX_VALUE;
Integer order2 = o2.getOrder() >= 0 ? o2.getOrder() : Integer.MAX_VALUE;
return order1.compareTo( order2 );
}
});
for (LayerEditTabPanelInfo tabPanel : tabPanels) {
String titleKey = tabPanel.getTitleKey();
IModel titleModel = null;
if (titleKey != null) {
titleModel = new org.apache.wicket.model.ResourceModel(titleKey);
}
else {
titleModel = new Model(tabPanel.getComponentClass().getSimpleName());
}
final Class<LayerEditTabPanel> panelClass = tabPanel.getComponentClass();
tabs.add(new AbstractTab(titleModel) {
@Override
public Panel getPanel(String panelId) {
try {
return panelClass.getConstructor(String.class, IModel.class)
.newInstance(panelId, myLayerModel);
}
catch (Exception e) {
throw new WicketRuntimeException(e);
//LOGGER.log(Level.WARNING, "Error creating resource panel", e);
}
}
});
}
// we need to override with submit links so that the various form
// element
// will validate and write down into their
theForm.add(new TabbedPanel("tabs", tabs) {
@Override
protected WebMarkupContainer newLink(String linkId, final int index) {
return new SubmitLink(linkId) {
@Override
public void onSubmit() {
setSelectedTab(index);
}
};
}
});
theForm.add(saveLink());
theForm.add(cancelLink());
}
private SubmitLink saveLink() {
return new SubmitLink("save") {
@Override
public void onSubmit() {
doSave();
}
};
}
/**
* Performs the necessary operation on save.
* <p>
* This implementation adds the necessary objects to the catalog, respecting the isNew flag,
* and calls {@link #onSuccessfulSave()} upon success.
* </p>
*/
protected void doSave() {
try {
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;
AbstractGridCoverage2DReader reader = (AbstractGridCoverage2DReader) 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.add(resourceInfo);
try {
catalog.add(getLayerInfo());
} catch (IllegalArgumentException e) {
catalog.remove(resourceInfo);
throw e;
}
} else {
ResourceInfo oldState = catalog.getResource(resourceInfo.getId(), ResourceInfo.class);
catalog.save(resourceInfo);
try {
LayerInfo layer = getLayerInfo();
layer.setResource(resourceInfo);
catalog.save(layer);
} catch (IllegalArgumentException e) {
catalog.save(oldState);
throw e;
}
}
onSuccessfulSave();
} catch (Exception e) {
LOGGER.log(Level.INFO, "Error saving layer", e);
error(e.getMessage());
}
}
private Link cancelLink() {
return new Link("cancel") {
@Override
public void onClick() {
onCancel();
}
};
}
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;
}
private List<LayerConfigurationPanelInfo> filterLayerPanels(
List<LayerConfigurationPanelInfo> list) {
for (int i = 0; i < list.size(); i++) {
if (!list.get(i).canHandle(getLayerInfo())) {
list.remove(i);
i--;
}
}
return list;
}
private abstract class ListLayerEditTabPanel extends LayerEditTabPanel {
public ListLayerEditTabPanel(String id, IModel model) {
super(id, model);
ListView list = createList("theList");
// do this or die on validation (the form element contents will
// reset, the edit will be lost)
list.setReuseItems(true);
add(list);
}
protected abstract ListView createList(String id);
}
private class DataLayerEditTabPanel extends ListLayerEditTabPanel {
public DataLayerEditTabPanel(String id, IModel model) {
super(id, model);
}
protected ListView createList(String id) {
List dataPanels = filterResourcePanels(((GeoServerApplication) getGeoServerApplication())
.getBeansOfType(ResourceConfigurationPanelInfo.class));
ListView dataPanelList = new ListView(id, dataPanels) {
@Override
protected void populateItem(ListItem 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;
}
}
private class PublishingLayerEditTabPanel extends ListLayerEditTabPanel {
private static final long serialVersionUID = -6575960326680386479L;
public PublishingLayerEditTabPanel(String id, IModel model) {
super(id, model);
}
@Override
public ListView createList(String id) {
List pubPanels = filterLayerPanels(((GeoServerApplication) getGeoServerApplication())
.getBeansOfType(LayerConfigurationPanelInfo.class));
ListView pubPanelList = new ListView(id, pubPanels) {
@Override
protected void populateItem(ListItem item) {
LayerConfigurationPanelInfo panelInfo = (LayerConfigurationPanelInfo) item
.getModelObject();
try {
LayerConfigurationPanel panel = panelInfo.getComponentClass().getConstructor(
String.class, IModel.class).newInstance("content", myLayerModel);
item.add((Component) panel);
} catch (Exception e) {
throw new WicketRuntimeException(
"Failed to add pluggable layer configuration panels", e);
}
}
};
return pubPanelList;
}
}
/**
* Returns the {@link ResourceInfo} contained in this page
*
* @return
*/
public ResourceInfo getResourceInfo() {
return (ResourceInfo) myResourceModel.getObject();
}
/**
* Returns the {@link LayerInfo} contained in this page
*
* @return
*/
public LayerInfo getLayerInfo() {
return (LayerInfo) myLayerModel.getObject();
}
/**
* By default brings back the user to LayerPage, subclasses can override this behavior
*/
protected void onSuccessfulSave() {
setResponsePage(LayerPage.class);
}
/**
* By default brings back the user to LayerPage, subclasses can override this behavior
*/
protected void onCancel() {
setResponsePage(LayerPage.class);
}
/**
* Allows collaborating pages to update the resource info object
* @param info
*/
public void updateResource(ResourceInfo info) {
myResourceModel.setObject(info);
}
/**
* Allows collaborating pages to update the layer info object
* @param info
*/
public void updateLayer(LayerInfo info) {
myLayerModel.setObject(info);
}
}