/* (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.publish;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.logging.Level;
import org.apache.wicket.Component;
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.PublishedInfo;
import org.geoserver.web.ComponentAuthorizer;
import org.geoserver.web.GeoServerSecuredPage;
import org.geoserver.web.data.resource.ResourceConfigurationPanel;
/**
* Page allowing to configure a layer(group) (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.
*
* @author Niels Charlier
*/
public abstract class PublishedConfigurationPage<T extends PublishedInfo> extends GeoServerSecuredPage {
private static final long serialVersionUID = 7870938096047218989L;
public static final String NAME = "name";
public static final String WORKSPACE = "wsName";
protected IModel<T> myModel;
protected boolean isNew;
protected TabbedPanel tabbedPanel;
/**
* {@link PublishedEditTabPanel} contributions may need to edit something different than the
* LayerInfo and ResourceInfo this page holds models to. In such cases
* {@link PublishedEditTabPanelInfo#createOwnModel} will return a non null model and well pass it
* back to the concrete LayerEditTabPanel constructor. This is so because LayerEditTabPanel are
* re-created everytime the user switches tabs.
*/
private LinkedHashMap<Class<? extends PublishedEditTabPanel<T>>, IModel<?>> tabPanelCustomModels;
private boolean inputEnabled = true;
protected PublishedConfigurationPage(boolean isNew) {
this.isNew = isNew;
}
protected PublishedConfigurationPage(T info, boolean isNew) {
this.isNew = isNew;
setupPublished(info);
}
protected void setupPublished(T info) {
setupPublished(new Model<T>(info));
}
protected void setupPublished(IModel<T> infoModel) {
myModel = new CompoundPropertyModel<T>(infoModel);
initComponents();
}
/**
*
*/
@SuppressWarnings("rawtypes")
private void initComponents() {
this.tabPanelCustomModels = new LinkedHashMap<Class<? extends PublishedEditTabPanel<T>>, IModel<?>>();
add(new Label("publishedinfoname", getPublishedInfo().prefixedName()));
Form<T> theForm = new Form<T>("publishedinfo", myModel);
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")) {
private static final long serialVersionUID = 1L;
public Panel getPanel(String panelID) {
return createMainTab(panelID).setInputEnabled(inputEnabled);
}
});
tabs.add(new AbstractTab(new org.apache.wicket.model.ResourceModel(
"ResourceConfigurationPage.Publishing")) {
private static final long serialVersionUID = 1L;
public Panel getPanel(String panelID) {
return new PublishingEditTabPanel(panelID).setInputEnabled(inputEnabled);
}
});
// add the tabs contributed via extension point
List<PublishedEditTabPanelInfo> tabPanels = getGeoServerApplication().getBeansOfType(PublishedEditTabPanelInfo.class);
// sort the tabs based on order
Collections.sort(tabPanels, new Comparator<PublishedEditTabPanelInfo>() {
public int compare(PublishedEditTabPanelInfo o1, PublishedEditTabPanelInfo 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 (PublishedEditTabPanelInfo ttabPanelInfo : tabPanels) {
if (ttabPanelInfo.supports(getPublishedInfo())) {
@SuppressWarnings("unchecked")
PublishedEditTabPanelInfo<T> tabPanelInfo = (PublishedEditTabPanelInfo<T>) ttabPanelInfo;
String titleKey = tabPanelInfo.getTitleKey();
IModel<String> titleModel = null;
if (titleKey != null) {
titleModel = new org.apache.wicket.model.ResourceModel(titleKey);
} else {
titleModel = new Model<String>(tabPanelInfo.getComponentClass().getSimpleName());
}
final Class<PublishedEditTabPanel<T>> panelClass = tabPanelInfo.getComponentClass();
IModel<?> panelCustomModel = tabPanelInfo.createOwnModel(myModel, isNew);
tabPanelCustomModels.put(panelClass, panelCustomModel);
tabs.add(new AbstractTab(titleModel) {
private static final long serialVersionUID = -6637277497986497791L;
private final Class<PublishedEditTabPanel<T>> panelType = panelClass;
@Override
public Panel getPanel(String panelId) {
PublishedEditTabPanel<?> tabPanel;
final IModel<?> panelCustomModel = tabPanelCustomModels.get(panelType);
try {
// if this tab needs a custom model instead of just our layer model, then
// let it create it once
if (panelCustomModel == null) {
tabPanel = panelClass.getConstructor(String.class, IModel.class)
.newInstance(panelId, myModel);
} else {
tabPanel = panelClass.getConstructor(String.class, IModel.class,
IModel.class).newInstance(panelId, myModel,
panelCustomModel);
}
} catch (Exception e) {
throw new WicketRuntimeException(e);
// LOGGER.log(Level.WARNING, "Error creating resource panel", e);
}
return tabPanel;
}
});
}
}
// we need to override with submit links so that the various form
// element
// will validate and write down into their
tabbedPanel = new TabbedPanel("tabs", tabs) {
private static final long serialVersionUID = 1L;
@Override
protected WebMarkupContainer newLink(String linkId, final int index) {
return new SubmitLink(linkId) {
private static final long serialVersionUID = 1L;
@Override
public void onSubmit() {
setSelectedTab(index);
}
};
}
};
theForm.add(tabbedPanel);
theForm.add(saveLink());
theForm.add(cancelLink());
}
protected abstract PublishedEditTabPanel<T> createMainTab(String panelID);
protected abstract void doSaveInternal() throws IOException;
public void setSelectedTab(Class<? extends PublishedEditTabPanel<?>> selectedTabClass) {
int selectedTabIndex;
//relying on LinkedHashMap here
selectedTabIndex = new ArrayList<Class<? extends PublishedEditTabPanel<T>>>(
tabPanelCustomModels.keySet()).indexOf(selectedTabClass);
if (selectedTabIndex > -1) {
tabbedPanel.setSelectedTab(selectedTabIndex);
}
}
public void selectDataTab() {
tabbedPanel.setSelectedTab(0);
}
public void selectPublishingTab() {
tabbedPanel.setSelectedTab(1);
}
protected void disableForm() {
get("publishedinfo").setEnabled(false);
}
private SubmitLink saveLink() {
return new SubmitLink("save") {
private static final long serialVersionUID = 1839992481355433705L;
@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 {
doSaveInternal();
if (hasErrorMessage()) {
return;
}
for(Entry<Class<? extends PublishedEditTabPanel<T>>, IModel<?>> e : tabPanelCustomModels.entrySet()){
Class<? extends PublishedEditTabPanel<T>> panelClass = e.getKey();
IModel<?> customModel = e.getValue();
if(customModel == null){
continue;
}
PublishedEditTabPanel<?> tabPanel = panelClass.getConstructor(String.class, IModel.class,
IModel.class).newInstance("temp", myModel, customModel);
tabPanel.save();
}
onSuccessfulSave();
} catch (Exception e) {
LOGGER.log(Level.INFO, "Error saving layer", e);
error(e.getMessage() == null ? e.toString() : e.getMessage());
}
}
@SuppressWarnings("rawtypes")
private Link<?> cancelLink() {
return new Link("cancel") {
private static final long serialVersionUID = -9007727127569731882L;
@Override
public void onClick() {
onCancel();
}
};
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private List<PublishedConfigurationPanelInfo<T>> filterPublishedPanels(
List<PublishedConfigurationPanelInfo> list) {
List<PublishedConfigurationPanelInfo<T>> result = new ArrayList<PublishedConfigurationPanelInfo<T>>();
for (PublishedConfigurationPanelInfo info : list) {
if (info.canHandle(getPublishedInfo())) {
result.add((PublishedConfigurationPanelInfo<T>) info);
}
}
return result;
}
/**
* Returns the {@link PublishedInfo} contained in this page
*
*
*/
public T getPublishedInfo() {
return (T) myModel.getObject();
}
/**
* By default brings back the user to LayerPage, subclasses can override this behavior
*/
protected void onSuccessfulSave() {
doReturn();
}
/**
* By default brings back the user to LayerPage, subclasses can override this behavior
*/
protected void onCancel() {
doReturn();
}
/**
* Allows collaborating pages to update the published info object
*
* @param info
*/
public void updatePublishedInfo(T info) {
myModel.setObject(info);
}
protected abstract class ListEditTabPanel extends PublishedEditTabPanel<T> {
private static final long serialVersionUID = -7279044666531992361L;
public ListEditTabPanel(String id) {
super(id, myModel);
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);
}
protected class PublishingEditTabPanel extends ListEditTabPanel {
private static final long serialVersionUID = -6575960326680386479L;
public PublishingEditTabPanel(String id) {
super(id);
}
@Override
public ListView<PublishedConfigurationPanelInfo<T>> createList(String id) {
List<PublishedConfigurationPanelInfo<T>> pubPanels
= filterPublishedPanels(getGeoServerApplication().getBeansOfType(PublishedConfigurationPanelInfo.class));
ListView<PublishedConfigurationPanelInfo<T>> pubPanelList
= new ListView<PublishedConfigurationPanelInfo<T>>(id, pubPanels) {
private static final long serialVersionUID = 1L;
@Override
protected void populateItem(ListItem<PublishedConfigurationPanelInfo<T>> item) {
PublishedConfigurationPanelInfo<T> panelInfo =
(PublishedConfigurationPanelInfo<T>) item.getModelObject();
try {
PublishedConfigurationPanel<T> panel = panelInfo.getComponentClass()
.getConstructor(String.class, IModel.class)
.newInstance("content", myModel);
item.add((Component) panel);
} catch (Exception e) {
throw new WicketRuntimeException(
"Failed to add pluggable layer configuration panels", e);
}
}
};
return pubPanelList;
}
}
@Override
protected ComponentAuthorizer getPageAuthorizer() {
return ComponentAuthorizer.WORKSPACE_ADMIN;
}
public void setInputEnabled(boolean inputEnabled) {
this.inputEnabled = inputEnabled;
get("publishedinfo:save").setVisible(inputEnabled);
}
}