/* (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.store;
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 org.apache.wicket.markup.html.form.Form;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.ResourcePool;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.web.CatalogIconFactory;
import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.data.resource.DataStorePanelInfo;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.data.DataAccessFactory;
/**
* Entry point to look up for StoreInfo related extension points
* <p>
* At least two {@link DataStorePanelInfo} should be provided by the application context in order to
* be used as the default ones when no specific panel info is contributed for a given store type,
* and they shall have the id property set to {@code "defaultVector"} and {@code "defaultRaster"}
* for {@link DataStoreInfo} and {@link CoverageStoreInfo} defaults respectively.
* </p>
*
* TODO: port the lookup of {@link DataStorePanelInfo} present in {@link CatalogIconFactory} here.
*
* @author Gabriel Roldan
*
*/
public class StoreExtensionPoints {
private StoreExtensionPoints() {
// do nothing
}
/**
* Finds out the {@link StoreEditPanel} that provides the edit form components for the given
* store.
*
* @param componentId
* the id for the returned panel
* @param editForm
* the form that's going to contain the components in the returned panel
* @param storeInfo
* the store being edited
* @param app
* the {@link GeoServerApplication} where to look for registered
* {@link DataStorePanelInfo}s
* @return a custom {@link StoreEditPanel} if there's one declared for the given store type, or
* a default one otherwise
*/
public static StoreEditPanel getStoreEditPanel(final String componentId, final Form editForm,
final StoreInfo storeInfo, final GeoServerApplication app) {
if (storeInfo == null) {
throw new NullPointerException("storeInfo param");
}
if (app == null) {
throw new NullPointerException("GeoServerApplication param");
}
DataStorePanelInfo panelInfo = findPanelInfo(storeInfo, app);
if (panelInfo == null || panelInfo.getComponentClass() == null) {
// there's either no panel info specific for this kind of store, or it provides no
// component class
panelInfo = getDefaultPanelInfo(storeInfo, app);
}
final Class<StoreEditPanel> componentClass = panelInfo.getComponentClass();
final Constructor<StoreEditPanel> constructor;
try {
constructor = componentClass.getConstructor(String.class, Form.class);
} catch (SecurityException e) {
throw e;
} catch (NoSuchMethodException e) {
throw new RuntimeException(componentClass.getName()
+ " does not provide the required constructor");
}
final StoreEditPanel storeEditPanel;
try {
storeEditPanel = constructor.newInstance(componentId, editForm);
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException("Cannot instantiate extension point contributor "
+ componentClass.getName(), e);
}
return storeEditPanel;
}
private static DataStorePanelInfo getDefaultPanelInfo(StoreInfo storeInfo,
GeoServerApplication app) {
final List<DataStorePanelInfo> providers = app.getBeansOfType(DataStorePanelInfo.class);
DataStorePanelInfo panelInfo = null;
for (DataStorePanelInfo provider : providers) {
if (storeInfo instanceof DataStoreInfo && "defaultVector".equals(provider.getId())) {
panelInfo = provider;
break;
} else if (storeInfo instanceof CoverageStoreInfo
&& "defaultRaster".equals(provider.getId())) {
panelInfo = provider;
break;
}
}
if (panelInfo == null) {
if (storeInfo instanceof DataStoreInfo) {
throw new IllegalStateException("Bean of type DataStorePanelInfo named "
+ "'defaultDataStorePanel' not provided by application context");
} else if (storeInfo instanceof CoverageStoreInfo) {
throw new IllegalStateException("Bean of type DataStorePanelInfo named "
+ "'defaultCoverageStorePanel' not provided by application context");
} else {
throw new IllegalArgumentException("Unknown store type: "
+ storeInfo.getClass().getName());
}
}
if (panelInfo.getComponentClass() == null) {
throw new IllegalStateException("Default DataStorePanelInfo '" + panelInfo.getId()
+ "' does not define a componentClass property");
}
if (panelInfo.getIconBase() == null || panelInfo.getIcon() == null) {
throw new IllegalStateException("Default DataStorePanelInfo '" + panelInfo.getId()
+ "' does not define default icon");
}
return panelInfo;
}
/**
*
* @param storeInfo
* @param app
* @return the extension point descriptor for the given storeInfo, or {@code null} if there's no
* contribution specific for the given storeInfo's type
*/
private static DataStorePanelInfo findPanelInfo(final StoreInfo storeInfo,
final GeoServerApplication app) {
final Catalog catalog = storeInfo.getCatalog();
final ResourcePool resourcePool = catalog.getResourcePool();
Class<?> factoryClass = null;
if (storeInfo instanceof DataStoreInfo) {
DataAccessFactory storeFactory;
try {
storeFactory = resourcePool.getDataStoreFactory((DataStoreInfo) storeInfo);
} catch (IOException e) {
throw new IllegalArgumentException("no factory found for StoreInfo " + storeInfo);
}
if (storeFactory != null) {
factoryClass = storeFactory.getClass();
}
} else if (storeInfo instanceof CoverageStoreInfo) {
AbstractGridFormat gridFormat;
gridFormat = resourcePool.getGridCoverageFormat((CoverageStoreInfo) storeInfo);
if (gridFormat != null) {
factoryClass = gridFormat.getClass();
}
} else {
throw new IllegalArgumentException("Unknown store type: "
+ storeInfo.getClass().getName());
}
if (factoryClass == null) {
throw new IllegalArgumentException("Can't locate the factory for the store");
}
final List<DataStorePanelInfo> providers = app.getBeansOfType(DataStorePanelInfo.class);
List<DataStorePanelInfo> fallbacks = new ArrayList<DataStorePanelInfo>();
for (DataStorePanelInfo provider : providers) {
Class<?> providerFactoryClass = provider.getFactoryClass();
if(providerFactoryClass == null) {
continue;
}
if (factoryClass.equals(providerFactoryClass)) {
return provider;
} else if(providerFactoryClass.isAssignableFrom(factoryClass)) {
fallbacks.add(provider);
}
}
if(fallbacks.size() == 1) {
return fallbacks.get(0);
} else if(fallbacks.size() > 1) {
// sort by class hierarchy, pick the closest match
Collections.sort(fallbacks,
new Comparator<DataStorePanelInfo>() {
public int compare(DataStorePanelInfo o1, DataStorePanelInfo o2) {
Class<?> c1 = o1.getFactoryClass();
Class<?> c2 = o2.getFactoryClass();
if (c1.equals(c2)) {
return 0;
}
if (c1.isAssignableFrom(c2)) {
return 1;
}
if (c2.isAssignableFrom(c1)) {
;
}
return -1;
}
});
//check first two and make sure bindings are not equal
DataStorePanelInfo f1 = fallbacks.get(0);
DataStorePanelInfo f2 = fallbacks.get(1);
if (f1.getFactoryClass().equals(f2.getFactoryClass())) {
String msg = "Multiple editor panels for : (" + f1.getFactoryClass() + "): " + f1 + ", " + f2;
throw new RuntimeException(msg);
}
return f1;
}
// ok, we don't have a specific one
return null;
}
}