/* Copyright (c) 2001 - 2008 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.config.util;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.collections.MultiHashMap;
import org.geoserver.catalog.AttributeTypeInfo;
import org.geoserver.catalog.AttributionInfo;
import org.geoserver.catalog.AuthorityURLInfo;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CoverageDimensionInfo;
import org.geoserver.catalog.CoverageInfo;
import org.geoserver.catalog.CoverageStoreInfo;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.Keyword;
import org.geoserver.catalog.KeywordInfo;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerIdentifierInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.MetadataLinkInfo;
import org.geoserver.catalog.MetadataMap;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.catalog.StoreInfo;
import org.geoserver.catalog.StyleInfo;
import org.geoserver.catalog.WMSLayerInfo;
import org.geoserver.catalog.WMSStoreInfo;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.catalog.impl.AttributeTypeInfoImpl;
import org.geoserver.catalog.impl.AttributionInfoImpl;
import org.geoserver.catalog.impl.AuthorityURL;
import org.geoserver.catalog.impl.CatalogImpl;
import org.geoserver.catalog.impl.CoverageDimensionImpl;
import org.geoserver.catalog.impl.CoverageInfoImpl;
import org.geoserver.catalog.impl.CoverageStoreInfoImpl;
import org.geoserver.catalog.impl.DataStoreInfoImpl;
import org.geoserver.catalog.impl.DefaultCatalogFacade;
import org.geoserver.catalog.impl.DimensionInfoImpl;
import org.geoserver.catalog.impl.FeatureTypeInfoImpl;
import org.geoserver.catalog.impl.LayerGroupInfoImpl;
import org.geoserver.catalog.impl.LayerIdentifier;
import org.geoserver.catalog.impl.LayerInfoImpl;
import org.geoserver.catalog.impl.MetadataLinkInfoImpl;
import org.geoserver.catalog.impl.NamespaceInfoImpl;
import org.geoserver.catalog.impl.ResolvingProxy;
import org.geoserver.catalog.impl.ResourceInfoImpl;
import org.geoserver.catalog.impl.StoreInfoImpl;
import org.geoserver.catalog.impl.StyleInfoImpl;
import org.geoserver.catalog.impl.WMSLayerInfoImpl;
import org.geoserver.catalog.impl.WMSStoreInfoImpl;
import org.geoserver.catalog.impl.WorkspaceInfoImpl;
import org.geoserver.config.ContactInfo;
import org.geoserver.config.CoverageAccessInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerInfo;
import org.geoserver.config.JAIInfo;
import org.geoserver.config.LoggingInfo;
import org.geoserver.config.ServiceInfo;
import org.geoserver.config.impl.ContactInfoImpl;
import org.geoserver.config.impl.CoverageAccessInfoImpl;
import org.geoserver.config.impl.GeoServerImpl;
import org.geoserver.config.impl.GeoServerInfoImpl;
import org.geoserver.config.impl.JAIInfoImpl;
import org.geoserver.config.impl.LoggingInfoImpl;
import org.geoserver.config.impl.ServiceInfoImpl;
import org.geoserver.ows.util.OwsUtils;
import org.geoserver.security.SecureCatalogImpl;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.Geometries;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.jdbc.RegexpValidator;
import org.geotools.jdbc.VirtualTable;
import org.geotools.jdbc.VirtualTableParameter;
import org.geotools.jdbc.VirtualTableParameter.Validator;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.crs.DefaultProjectedCRS;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.util.Converters;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.SingleValueConverterWrapper;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
import com.thoughtworks.xstream.converters.collections.CollectionConverter;
import com.thoughtworks.xstream.converters.collections.MapConverter;
import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.converters.reflection.SortableFieldKeySorter;
import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.ClassAliasingMapper;
import com.thoughtworks.xstream.mapper.Mapper;
import com.vividsolutions.jts.geom.Geometry;
/**
* Utility class which loads and saves catalog and configuration objects to and
* from an xstream.
*
* @author Justin Deoliveira, The Open Planning Project
*
*/
public class XStreamPersister {
/**
* Callback interface or xstream persister.
*/
public static class Callback {
protected void postEncodeWorkspace( WorkspaceInfo ws, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeNamespace( NamespaceInfo ns, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeDataStore( DataStoreInfo ds, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeCoverageStore( CoverageStoreInfo ds, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeFeatureType( FeatureTypeInfo ds, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeWMSLayer( WMSLayerInfo ds, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeCoverage( CoverageInfo ds, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeLayer( LayerInfo ls, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeLayerGroup( LayerGroupInfo ls, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeReference( Object obj, String ref, HierarchicalStreamWriter writer, MarshallingContext context ) {
}
protected void postEncodeWMSStore(WMSStoreInfo store, HierarchicalStreamWriter writer, MarshallingContext context) {
}
}
/**
* logging instance
*/
static Logger LOGGER = Logging.getLogger( "org.geoserver" );
/**
* internal xstream instance
*/
XStream xs;
/**
* GeoServer reference used to resolve references to gloal from services
*/
GeoServer geoserver;
/**
* Catalog reference, used to resolve references to stores, workspaces
* + namespaces
*/
Catalog catalog;
/**
* Callback instance.
*/
Callback callback;
/**
* Flag controlling how references to objects are encoded.
*/
boolean referenceByName = false;
/**
* The type map used in {@link BreifMapConverter} to handle complex objects
*/
Map<String, Class<?>> forwardBreifMap = new HashMap<String, Class<?>>();
Map<Class<?>, String> backwardBreifMap = new HashMap<Class<?>, String>();
/**
* Constructs the persister and underlying xstream.
*/
protected XStreamPersister() {
this(null);
}
/**
* Constructs the persister and underlying xstream specifying the stream driver explicitly.
*/
protected XStreamPersister(HierarchicalStreamDriver streamDriver) {
//control the order in which fields are sorted
SortableFieldKeySorter sorter = new SortableFieldKeySorter();
//sorter.registerFieldOrder( DefaultCatalogDAO.class, new String[]{ "workspaces", "namespaces", "stores", "styles",
/* these we actually omit, but the sorter needs them specified */
// "layerGroups", "resources", "maps", "defaultStores", "listeners", "layers", "resourcePool", "resourceLoader", "LOGGER" } );
ReflectionProvider reflectionProvider = new CustomReflectionProvider( new FieldDictionary( sorter ) );
//new Sun14ReflectionProvider( new FieldDictionary( sorter ) );
if ( streamDriver != null ) {
xs = new XStream( reflectionProvider, streamDriver );
}
else {
xs = new XStream( reflectionProvider );
}
xs.setMode(XStream.NO_REFERENCES);
init(xs);
}
protected void init(XStream xs) {
// Default implementations
initImplementationDefaults(xs);
// Aliases
xs.alias("global", GeoServerInfo.class);
xs.alias("logging", LoggingInfo.class);
xs.alias("jai", JAIInfo.class);
xs.alias("coverageAccess", CoverageAccessInfo.class);
xs.alias("catalog", Catalog.class);
xs.alias("namespace", NamespaceInfo.class);
xs.alias("workspace", WorkspaceInfo.class);
xs.alias("dataStore", DataStoreInfo.class);
xs.alias("wmsStore", WMSStoreInfo.class);
xs.alias("coverageStore", CoverageStoreInfo.class);
xs.alias("style",StyleInfo.class);
xs.alias( "featureType", FeatureTypeInfo.class);
xs.alias( "coverage", CoverageInfo.class);
xs.alias( "wmsLayer", WMSLayerInfo.class);
xs.alias( "coverageDimension", CoverageDimensionInfo.class);
xs.alias( "metadataLink", MetadataLinkInfo.class);
xs.alias( "attribute", AttributeTypeInfo.class );
xs.alias( "layer", LayerInfo.class);
xs.alias( "layerGroup", LayerGroupInfo.class);
xs.alias( "gridGeometry", GridGeometry2D.class);
xs.alias( "projected", DefaultProjectedCRS.class);
xs.alias( "attribution", AttributionInfo.class );
xs.aliasField("abstract", ResourceInfoImpl.class, "_abstract" );
xs.alias("AuthorityURL", AuthorityURLInfo.class);
xs.alias("Identifier", LayerIdentifierInfo.class);
// GeoServerInfo
xs.omitField(impl(GeoServerInfo.class), "clientProperties");
xs.omitField(impl(GeoServerInfo.class), "geoServer");
xs.registerLocalConverter(impl(GeoServerInfo.class), "metadata", new MetadataMapConverter());
// ServiceInfo
xs.omitField(impl(ServiceInfo.class), "clientProperties");
xs.omitField(impl(ServiceInfo.class), "geoServer");
xs.registerLocalConverter(impl(ServiceInfo.class), "workspace", new ReferenceConverter(WorkspaceInfo.class));
xs.registerLocalConverter(impl(ServiceInfo.class), "metadata", new MetadataMapConverter());
xs.registerLocalConverter(impl(ServiceInfo.class), "keywords", new KeywordListConverter());
// Catalog
xs.omitField(impl(Catalog.class), "resourcePool");
xs.omitField(impl(Catalog.class), "resourceLoader");
xs.omitField(impl(Catalog.class), "listeners");
xs.omitField(impl(Catalog.class), "LOGGER");
xs.omitField(impl(DefaultCatalogFacade.class), "catalog");
xs.omitField(impl(DefaultCatalogFacade.class), "resources");
xs.omitField(impl(DefaultCatalogFacade.class), "layers");
xs.omitField(impl(DefaultCatalogFacade.class), "maps");
xs.omitField(impl(DefaultCatalogFacade.class), "layerGroups");
xs.registerLocalConverter(DefaultCatalogFacade.class, "stores",
new StoreMultiHashMapConverter());
xs.registerLocalConverter(DefaultCatalogFacade.class, "namespaces",
new SpaceMapConverter("namespace"));
xs.registerLocalConverter(DefaultCatalogFacade.class, "workspaces",
new SpaceMapConverter("workspace"));
//WorkspaceInfo
xs.omitField( impl(WorkspaceInfo.class), "_default");
xs.registerLocalConverter( impl(WorkspaceInfo.class), "metadata", new MetadataMapConverter() );
//NamespaceInfo
xs.omitField( impl(NamespaceInfo.class), "catalog");
xs.omitField( impl(NamespaceInfo.class), "_default");
xs.registerLocalConverter( impl(NamespaceInfo.class), "metadata", new MetadataMapConverter() );
// StoreInfo
xs.omitField(impl(StoreInfo.class), "catalog");
xs.omitField(impl(StoreInfo.class), "error");
//xs.omitField(StoreInfo.class), "workspace"); //handled by StoreInfoConverter
xs.registerLocalConverter(impl(StoreInfo.class), "workspace", new ReferenceConverter(WorkspaceInfo.class));
xs.registerLocalConverter(impl(StoreInfo.class), "connectionParameters", new BreifMapConverter() );
xs.registerLocalConverter(impl(StoreInfo.class), "metadata", new MetadataMapConverter());
// StyleInfo
xs.omitField(impl(StyleInfo.class), "catalog");
xs.registerLocalConverter(impl(StyleInfo.class), "metadata", new MetadataMapConverter() );
// ResourceInfo
xs.omitField( impl(ResourceInfo.class), "catalog");
xs.omitField( impl(ResourceInfo.class), "crs" );
xs.registerLocalConverter( impl(ResourceInfo.class), "nativeCRS", new CRSConverter());
xs.registerLocalConverter( impl(ResourceInfo.class), "store", new ReferenceConverter(StoreInfo.class));
xs.registerLocalConverter( impl(ResourceInfo.class), "namespace", new ReferenceConverter(NamespaceInfo.class));
xs.registerLocalConverter( impl(ResourceInfo.class), "metadata", new MetadataMapConverter() );
xs.registerLocalConverter( impl(ResourceInfo.class), "keywords", new KeywordListConverter());
// FeatureTypeInfo
// CoverageInfo
xs.registerLocalConverter( impl(CoverageInfo.class), "supportedFormats", new LaxCollectionConverter(xs.getMapper()));
xs.registerLocalConverter( impl(CoverageInfo.class), "requestSRS", new LaxCollectionConverter(xs.getMapper()));
xs.registerLocalConverter( impl(CoverageInfo.class), "responseSRS", new LaxCollectionConverter(xs.getMapper()));
xs.registerLocalConverter( impl(CoverageInfo.class), "interpolationMethods", new LaxCollectionConverter(xs.getMapper()));
xs.registerLocalConverter( impl(CoverageInfo.class), "dimensions", new LaxCollectionConverter(xs.getMapper()));
// CoverageDimensionInfo
xs.registerLocalConverter( impl(CoverageDimensionInfo.class), "range", new NumberRangeConverter());
// AttributeTypeInfo
xs.omitField( impl(AttributeTypeInfo.class), "featureType");
xs.omitField( impl(AttributeTypeInfo.class), "attribute");
// LayerInfo
//xs.omitField( LayerInfo.class), "resource");
xs.registerLocalConverter( impl(LayerInfo.class), "resource", new ReferenceConverter( ResourceInfo.class ) );
xs.registerLocalConverter( impl(LayerInfo.class), "defaultStyle", new ReferenceConverter( StyleInfo.class ) );
xs.registerLocalConverter( impl(LayerInfo.class), "styles", new ReferenceCollectionConverter( StyleInfo.class ) );
xs.registerLocalConverter( impl(LayerInfo.class), "metadata", new MetadataMapConverter() );
// LayerGroupInfo
xs.registerLocalConverter(impl(LayerGroupInfo.class), "layers", new ReferenceCollectionConverter( LayerInfo.class ));
xs.registerLocalConverter(impl(LayerGroupInfo.class), "styles", new ReferenceCollectionConverter( StyleInfo.class ));
xs.registerLocalConverter(impl(LayerGroupInfo.class), "metadata", new MetadataMapConverter() );
//ReferencedEnvelope
xs.registerLocalConverter( ReferencedEnvelope.class, "crs", new SRSConverter() );
xs.registerLocalConverter( GeneralEnvelope.class, "crs", new SRSConverter() );
// ServiceInfo
xs.omitField( impl(ServiceInfo.class), "geoServer" );
// Converters
xs.registerConverter(new SpaceInfoConverter());
xs.registerConverter(new StoreInfoConverter());
xs.registerConverter(new ResourceInfoConverter());
xs.registerConverter(new FeatureTypeInfoConverter());
xs.registerConverter(new CoverageInfoConverter());
xs.registerConverter(new LayerInfoConverter());
xs.registerConverter(new LayerGroupInfoConverter());
xs.registerConverter(new GridGeometry2DConverter());
xs.registerConverter(new ProxyCollectionConverter( xs.getMapper() ) );
xs.registerConverter(new VirtualTableConverter());
xs.registerConverter(new KeywordInfoConverter());
// register VirtulaTable handling
registerBreifMapComplexType("virtualTable", VirtualTable.class);
registerBreifMapComplexType("dimensionInfo", DimensionInfoImpl.class);
callback = new Callback();
}
/**
* Use this method to register complex types that cannot be simply represented as a string
* in a {@link BreifMapConverter}. The {@code typeId} will be used as a type discriminator
* in the brief map, as well as the element root for the complex object to be converted.
* @param typeId
* @param clazz
*/
public void registerBreifMapComplexType(String typeId, Class clazz) {
forwardBreifMap.put(typeId, clazz);
backwardBreifMap.put(clazz, typeId);
}
public XStream getXStream() {
return xs;
}
public ClassAliasingMapper getClassAliasingMapper() {
return (ClassAliasingMapper) xs.getMapper().lookupMapperOfType( ClassAliasingMapper.class );
}
public void setCatalog(Catalog catalog) {
this.catalog = catalog;
}
public void setGeoServer(GeoServer geoserver) {
this.geoserver = geoserver;
}
public void setCallback(Callback callback) {
this.callback = callback;
}
public void setReferenceByName(boolean referenceByName) {
this.referenceByName = referenceByName;
}
public void setExcludeIds() {
xs.omitField( WorkspaceInfoImpl.class, "id");
xs.omitField( NamespaceInfoImpl.class, "id");
xs.omitField(StoreInfoImpl.class, "id");
xs.omitField(StyleInfoImpl.class, "id");
xs.omitField( ResourceInfoImpl.class, "id");
xs.omitField( LayerInfoImpl.class, "id");
xs.omitField(LayerGroupInfoImpl.class, "id" );
xs.omitField(AttributeTypeInfoImpl.class, "id");
}
public void setHideFeatureTypeAttributes() {
xs.omitField(FeatureTypeInfoImpl.class, "attributes");
}
/**
* Saves an object to persistence.
*
* @param obj The object to save.
* @param out The stream to save the object to.
*
* @throws IOException
*/
public void save(Object obj, OutputStream out) throws IOException {
//unwrap dynamic proxies
obj = unwrapProxies( obj );
xs.toXML(obj, new OutputStreamWriter( out, "UTF-8" ));
}
/**
* Unwraps any proxies around the object.
* <p>
* If the object is not being proxied it is passed back.
* </p>
*/
public static Object unwrapProxies( Object obj ) {
obj = SecureCatalogImpl.unwrap( obj );
obj = GeoServerImpl.unwrap( obj );
obj = CatalogImpl.unwrap( obj );
return obj;
}
/**
* Loads an object from peristence.
*
* @param in The input stream to read the object from.
* @param clazz The class of the expected object.
*
* @throws IOException
*/
public <T> T load(InputStream in, Class<T> clazz ) throws IOException {
T obj = clazz.cast( xs.fromXML( in ) );
//call resolve() to ensure that any references created during loading
// get resolved to actual objects, for instance for links from datastores
// to workspaces
if ( obj instanceof CatalogImpl ) {
((CatalogImpl)obj).resolve();
}
return obj;
}
/**
* Builds a converter that will marshal/unmarshal the target class by reference, that is, by
* storing the object id as opposed to fully serializing it
* @param clazz
* @return
*/
public ReferenceConverter buildReferenceConverter(Class clazz) {
return new ReferenceConverter(clazz);
}
/**
* Same as {@link #buildReferenceConverter(Class)}, but works against a collection of objects
* @param clazz
* @return
*/
public ReferenceCollectionConverter buildReferenceCollectionConverter(Class clazz) {
return new ReferenceCollectionConverter(clazz);
}
/**
* Sets up mappings from interface to implementation classes.
*
*/
protected void initImplementationDefaults(XStream xs) {
//configuration
xs.addDefaultImplementation(GeoServerInfoImpl.class, GeoServerInfo.class);
xs.addDefaultImplementation(LoggingInfoImpl.class, LoggingInfo.class);
xs.addDefaultImplementation(JAIInfoImpl.class, JAIInfo.class);
xs.addDefaultImplementation(CoverageAccessInfoImpl.class, CoverageAccessInfo.class);
xs.addDefaultImplementation(ContactInfoImpl.class, ContactInfo.class);
xs.addDefaultImplementation(AttributionInfoImpl.class, AttributionInfo.class);
//catalog
xs.addDefaultImplementation(CatalogImpl.class, Catalog.class);
xs.addDefaultImplementation(NamespaceInfoImpl.class, NamespaceInfo.class);
xs.addDefaultImplementation(WorkspaceInfoImpl.class, WorkspaceInfo.class);
xs.addDefaultImplementation(DataStoreInfoImpl.class, DataStoreInfo.class);
xs.addDefaultImplementation(WMSStoreInfoImpl.class, WMSStoreInfo.class);
xs.addDefaultImplementation(CoverageStoreInfoImpl.class, CoverageStoreInfo.class);
xs.addDefaultImplementation(StyleInfoImpl.class, StyleInfo.class);
xs.addDefaultImplementation(FeatureTypeInfoImpl.class, FeatureTypeInfo.class );
xs.addDefaultImplementation(CoverageInfoImpl.class, CoverageInfo.class);
xs.addDefaultImplementation(WMSLayerInfoImpl.class, WMSLayerInfo.class);
xs.addDefaultImplementation(CoverageDimensionImpl.class, CoverageDimensionInfo.class);
xs.addDefaultImplementation(MetadataLinkInfoImpl.class, MetadataLinkInfo.class);
xs.addDefaultImplementation(AttributeTypeInfoImpl.class, AttributeTypeInfo.class );
xs.addDefaultImplementation(LayerInfoImpl.class, LayerInfo.class);
xs.addDefaultImplementation(LayerGroupInfoImpl.class, LayerGroupInfo.class );
xs.addDefaultImplementation(LayerIdentifier.class, LayerIdentifierInfo.class );
xs.addDefaultImplementation(AuthorityURL.class, AuthorityURLInfo.class );
//supporting objects
xs.addDefaultImplementation(GridGeometry2D.class, GridGeometry.class );
xs.addDefaultImplementation(DefaultGeographicCRS.class, CoordinateReferenceSystem.class);
//collections
xs.addDefaultImplementation(ArrayList.class, List.class);
}
protected Class impl(Class interfce) {
//special case case classes, they don't get registered as default implementations
// only concrete classes do
if (interfce == ServiceInfo.class) {
return ServiceInfoImpl.class;
}
if (interfce == StoreInfo.class) {
return StoreInfoImpl.class;
}
if (interfce == ResourceInfo.class) {
return ResourceInfoImpl.class;
}
Class clazz = getXStream().getMapper().defaultImplementationOf(interfce);
if (clazz == null) {
throw new RuntimeException("No default mapping for " + interfce);
}
return clazz;
}
/**
* Custom reflection provider which unwraps proxies, and skips empty collections
* and maps.
*/
class CustomReflectionProvider extends Sun14ReflectionProvider {
public CustomReflectionProvider( FieldDictionary fd ) {
super( fd );
}
@Override
public void visitSerializableFields(Object object, Visitor visitor) {
super.visitSerializableFields(object, new VisitorWrapper(visitor));
}
class VisitorWrapper implements ReflectionProvider.Visitor {
Visitor wrapped;
public VisitorWrapper( Visitor wrapped ) {
this.wrapped = wrapped;
}
public void visit(String name, Class type, Class definedIn,
Object value) {
//skip empty collections + maps
if ( value instanceof Collection && ((Collection)value).isEmpty() ) {
return;
}
if ( value instanceof Map && ((Map)value).isEmpty() ) {
return;
}
//unwrap any proxies
value = unwrapProxies(value);
wrapped.visit( name, type, definedIn, value);
}
}
}
//
// custom converters
//
//simple object converters
/**
* Map converter which encodes a map more breifly than the standard map converter.
*/
protected class BreifMapConverter extends MapConverter {
public BreifMapConverter() {
super(getXStream().getMapper());
}
@Override
public boolean canConvert(Class type) {
//handle all types of maps
return Map.class.isAssignableFrom(type);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Map map = (Map) source;
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
if ( entry.getValue() == null ) {
continue;
}
writer.startNode("entry");
writer.addAttribute( "key", entry.getKey().toString());
if ( entry.getValue() != null ) {
Object value = entry.getValue();
String complexTypeId = getComplexTypeId(value.getClass());
if(complexTypeId == null) {
String str = Converters.convert(value, String.class);
if(str == null) {
str = value.toString();
}
writer.setValue(str);
} else {
writer.startNode(complexTypeId);
context.convertAnother(value);
writer.endNode();
}
}
writer.endNode();
}
}
@Override
protected void populateMap(HierarchicalStreamReader reader,
UnmarshallingContext context, Map map) {
while (reader.hasMoreChildren()) {
reader.moveDown();
//we support four syntaxes here:
// 1) <key>value</key>
// 2) <key><type>value</type></key>
// 3) <entry key="">value</entry>
// 4) <entry>
// <type>key</type>
// <type>value</type>
// </entry>
String key = reader.getNodeName();
Object value = null;
if ( "entry".equals( key ) ) {
if ( reader.getAttribute( "key") != null ) {
//this is case 3
key = reader.getAttribute( "key" );
// in this case we also support complex objects
if(reader.hasMoreChildren()) {
reader.moveDown();
String typeId = reader.getNodeName();
value = context.convertAnother(null, getComplexTypeClass(typeId));
reader.moveUp();
} else {
value = reader.getValue();
}
}
else if ( reader.hasMoreChildren() ){
//this is case 4
reader.moveDown();
key = reader.getValue();
reader.moveUp();
reader.moveDown();
value = reader.getValue();
reader.moveUp();
}
}
else {
boolean old = false;
if (reader.hasMoreChildren()) {
//this handles case 2
old = true;
reader.moveDown();
}
value = readItem(reader, context, map);
if ( old ) {
reader.moveUp();
}
}
map.put(key, value);
reader.moveUp();
}
}
@Override
protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context,
Object current) {
return reader.getValue();
}
private Class getComplexTypeClass(String typeId) {
return forwardBreifMap.get(typeId);
}
private String getComplexTypeId(Class clazz) {
String typeId = backwardBreifMap.get(clazz);
if(typeId == null) {
List<Class> matches = new ArrayList<Class>();
collectSuperclasses(clazz, matches);
for (Iterator it = matches.iterator(); it.hasNext();) {
Class sper = (Class) it.next();
if(backwardBreifMap.get(sper) == null) {
it.remove();
}
}
if(matches.size() > 1) {
Comparator comparator = new Comparator<Class>() {
public int compare(Class c1, Class c2) {
if (c2.isAssignableFrom(c1)) {
return -1;
} else {
return 1;
}
}
};
Collections.sort(matches, comparator);
}
if(matches.size() > 0) {
typeId = backwardBreifMap.get(matches.get(0));
}
}
return typeId;
}
void collectSuperclasses(Class clazz, List<Class> matches) {
matches.add(clazz);
if(clazz.getSuperclass() == null && clazz.getInterfaces().length == 0) {
return;
}
if(clazz.getSuperclass() != null) {
collectSuperclasses(clazz.getSuperclass(), matches);
}
for(Class iface : clazz.getInterfaces()) {
collectSuperclasses(iface, matches);
}
}
}
/**
* Custom converter for the special metadata map.
*/
class MetadataMapConverter extends BreifMapConverter {
@Override
public boolean canConvert(Class type) {
return MetadataMap.class.equals(type) || super.canConvert(type);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
if ( source instanceof MetadataMap) {
MetadataMap mdmap = (MetadataMap) source;
source = mdmap.getMap();
}
super.marshal(source, writer, context);
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
Map map = (Map) super.unmarshal(reader, context);
if ( !(map instanceof MetadataMap ) ) {
map = new MetadataMap(map);
}
return map;
}
}
/**
* Converters which encodes an object by a reference, or its id.
*/
//class ReferenceConverter extends AbstractSingleValueConverter {
class ReferenceConverter implements Converter {
Class clazz;
public ReferenceConverter( Class clazz ) {
this.clazz = clazz;
}
public boolean canConvert(Class type) {
return clazz.isAssignableFrom( type );
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
//could be a proxy, unwrap it
source = CatalogImpl.unwrap( source );
//gets its id
String id = (String) OwsUtils.get( source, "id" );
if ( id != null && !referenceByName) {
writer.startNode("id");
writer.setValue( id );
writer.endNode();
callback.postEncodeReference( source, id, writer, context );
}
else {
//use name if no id set
String name = (String) OwsUtils.get( source, "name" );
if ( name != null ) {
writer.startNode("name");
writer.setValue( name );
writer.endNode();
callback.postEncodeReference( source, name, writer, context );
}
else {
throw new IllegalArgumentException( "Unable to marshal reference with no id or name.");
}
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
String ref = null;
if ( reader.hasMoreChildren() ) {
reader.moveDown();
ref = reader.getValue();
reader.moveUp();
}
else {
ref = reader.getValue();
}
Object proxy = ResolvingProxy.create( ref, clazz );
Object resolved = proxy;
if ( catalog != null ) {
resolved = ResolvingProxy.resolve( catalog, proxy );
}
return CatalogImpl.unwrap( resolved );
}
}
class ReferenceCollectionConverter extends LaxCollectionConverter {
Class clazz;
public ReferenceCollectionConverter(Class clazz) {
super( getXStream().getMapper() );
this.clazz = clazz;
}
@Override
protected void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
ClassAliasingMapper cam =
(ClassAliasingMapper) mapper().lookupMapperOfType( ClassAliasingMapper.class );
String elementName = cam.serializedClass( clazz );
if ( elementName == null ) {
elementName = cam.serializedClass( item.getClass() );
}
writer.startNode(elementName);
if(item != null)
context.convertAnother( item, new ReferenceConverter( clazz ) );
writer.endNode();
}
@Override
protected Object readItem(HierarchicalStreamReader reader,
UnmarshallingContext context, Object current) {
return context.convertAnother( current, clazz, new ReferenceConverter( clazz ) );
}
}
/**
* Converter which unwraps proxies in a collection.
*/
class ProxyCollectionConverter extends CollectionConverter {
public ProxyCollectionConverter(Mapper mapper) {
super(mapper);
}
@Override
protected void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
super.writeItem(unwrapProxies(item), context, writer);
}
}
/**
* Converter for coordinate reference system objects that converts by SRS code.
*/
static class SRSConverter extends AbstractSingleValueConverter {
public boolean canConvert(Class type) {
return CoordinateReferenceSystem.class.isAssignableFrom(type);
}
@Override
public String toString(Object obj) {
CoordinateReferenceSystem crs = (CoordinateReferenceSystem) obj;
try {
Integer epsg = CRS.lookupEpsgCode(crs, true);
if (epsg != null) {
return "EPSG:" + epsg;
}
}
catch (FactoryException e) {
XStreamPersister.LOGGER.warning( "Could not determine epsg code of crs, encoding as WKT");
}
return crs.toWKT();
}
@Override
public Object fromString(String str) {
if ( str.toUpperCase().startsWith( "EPSG:") ) {
try {
return CRS.decode( str );
}
catch (Exception e) {
XStreamPersister.LOGGER.log( Level.WARNING, "Error decode epsg code: "+str, e );
}
}
else {
try {
return CRS.parseWKT( str );
}
catch (FactoryException e) {
XStreamPersister.LOGGER.log( Level.WARNING, "Error decode wkt: "+str, e );
}
}
return null;
}
}
/**
* Converter for coordinate reference system objects that converts by WKT.
*
*/
static class CRSConverter extends AbstractSingleValueConverter {
@Override
public boolean canConvert(Class type) {
return CoordinateReferenceSystem.class.isAssignableFrom(type);
}
@Override
public String toString(Object obj) {
return ((CoordinateReferenceSystem)obj).toWKT();
}
@Override
public Object fromString(String str) {
try {
return CRS.parseWKT( str );
}
catch (Exception e) {
try {
return new SRSConverter().fromString( str );
}
catch( Exception e1 ) {}
throw new RuntimeException( e );
}
}
}
/**
* Converter for coverage grid geometry.
*
*/
class GridGeometry2DConverter extends AbstractReflectionConverter {
public GridGeometry2DConverter() {
super( GridGeometry2D.class );
}
@Override
protected void doMarshal(Object source,
HierarchicalStreamWriter writer, MarshallingContext context) {
GridGeometry2D g = (GridGeometry2D) source;
MathTransform tx = g.getGridToCRS();
writer.addAttribute("dimension", String.valueOf(g.getGridRange().getDimension()));
//grid range
StringBuffer low = new StringBuffer();
StringBuffer high = new StringBuffer();
for (int r = 0; r < g.getGridRange().getDimension(); r++) {
low.append(g.getGridRange().getLow(r)).append(" ");
high.append(g.getGridRange().getHigh(r)+1).append(" ");
}
low.setLength(low.length()-1);
high.setLength(high.length()-1);
writer.startNode("range");
writer.startNode( "low" ); writer.setValue( low.toString() ); writer.endNode();
writer.startNode( "high" ); writer.setValue( high.toString() ); writer.endNode();
writer.endNode();
//transform
if (tx instanceof AffineTransform) {
AffineTransform atx = (AffineTransform) tx;
writer.startNode("transform");
writer.startNode("scaleX"); writer.setValue(Double.toString( atx.getScaleX())); writer.endNode();
writer.startNode("scaleY"); writer.setValue(Double.toString( atx.getScaleY())); writer.endNode();
writer.startNode("shearX"); writer.setValue(Double.toString( atx.getShearX())); writer.endNode();
writer.startNode("shearY"); writer.setValue(Double.toString( atx.getShearY())); writer.endNode();
writer.startNode("translateX"); writer.setValue(Double.toString( atx.getTranslateX())); writer.endNode();
writer.startNode("translateY"); writer.setValue(Double.toString( atx.getTranslateY())); writer.endNode();
writer.endNode();
}
//crs
writer.startNode("crs");
context.convertAnother( g.getCoordinateReferenceSystem(),
new SingleValueConverterWrapper( new SRSConverter() ) );
writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
int[] high,low;
//reader.moveDown(); //grid
reader.moveDown(); //range
reader.moveDown(); //low
low = toIntArray( reader.getValue() );
reader.moveUp();
reader.moveDown(); //high
high = toIntArray( reader.getValue() );
reader.moveUp();
reader.moveUp(); //range
if ( reader.hasMoreChildren() ) {
reader.moveDown(); //transform or crs
}
AffineTransform2D gridToCRS = null;
if ( "transform".equals( reader.getNodeName() ) ) {
double sx,sy,shx,shy,tx,ty;
reader.moveDown(); //scaleX
sx = Double.parseDouble( reader.getValue() );
reader.moveUp();
reader.moveDown(); //scaleY
sy = Double.parseDouble( reader.getValue() );
reader.moveUp();
reader.moveDown(); //shearX
shx = Double.parseDouble( reader.getValue() );
reader.moveUp();
reader.moveDown(); //shearY
shy = Double.parseDouble( reader.getValue() );
reader.moveUp();
reader.moveDown(); //translateX
tx = Double.parseDouble( reader.getValue() );
reader.moveUp();
reader.moveDown(); //translateY
ty = Double.parseDouble( reader.getValue() );
reader.moveUp();
// set tranform
gridToCRS = new AffineTransform2D(sx, shx, shy, sy, tx, ty);
reader.moveUp();
if ( reader.hasMoreChildren() ) {
reader.moveDown(); //crs
}
}
CoordinateReferenceSystem crs = null;
if ( "crs".equals( reader.getNodeName() ) ) {
crs = (CoordinateReferenceSystem) context.convertAnother( null, CoordinateReferenceSystem.class,
new SingleValueConverterWrapper( new SRSConverter() ));
reader.moveUp();
}
// new grid range
GeneralGridEnvelope gridRange = new GeneralGridEnvelope(low, high);
GridGeometry2D gg = new GridGeometry2D( gridRange, gridToCRS, crs );
return serializationMethodInvoker.callReadResolve(gg);
}
int[] toIntArray( String s ) {
String[] split = s.split( " " );
int[] ints = new int[split.length];
for ( int i = 0; i < split.length; i++ ) {
ints[i] = Integer.parseInt( split[i] );
}
return ints;
}
}
class NumberRangeConverter extends AbstractReflectionConverter {
@Override
public boolean canConvert(Class clazz) {
return NumberRange.class.isAssignableFrom( clazz );
}
@Override
public void marshal(Object original, HierarchicalStreamWriter writer,
MarshallingContext context) {
NumberRange range = (NumberRange) original;
writer.startNode("min");
if ( Double.isInfinite( ((Number)range.getMinValue()).doubleValue() ) ) {
context.convertAnother( "-inf" );
}
else {
context.convertAnother( range.getMinValue() );
}
writer.endNode();
writer.startNode("max");
if ( Double.isInfinite( ((Number)range.getMaxValue()).doubleValue() )) {
context.convertAnother( "inf");
}
else {
context.convertAnother( range.getMaxValue() );
}
writer.endNode();
}
@Override
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
//JD: we handle infinite manually b/c the json serializer chokes on inifinte values
// b/c JSON does not support it
Double min = null, max = null;
while( reader.hasMoreChildren() ) {
reader.moveDown();
if ( "min".equals( reader.getNodeName() ) ) {
if ( !"-inf".equals( reader.getValue() ) ) {
min = Double.parseDouble( reader.getValue() );
}
}
if ( "max".equals( reader.getNodeName() ) ) {
if ( !"inf".equals( reader.getValue() ) ) {
max = Double.parseDouble( reader.getValue() );
}
}
reader.moveUp();
}
min = min != null ? min : Double.NEGATIVE_INFINITY;
max = max != null ? max : Double.POSITIVE_INFINITY;
return NumberRange.create( min.doubleValue(), true, max.doubleValue(), true );
}
}
//catalog object converters
/**
* Base class for all custom reflection based converters.
*/
class AbstractReflectionConverter extends ReflectionConverter {
Class clazz;
public AbstractReflectionConverter() {
this(Object.class);
}
public AbstractReflectionConverter(Class clazz) {
super(getXStream().getMapper(),getXStream().getReflectionProvider());
this.clazz = clazz;
}
@Override
public boolean canConvert(Class type) {
return clazz.isAssignableFrom( type );
}
@Override
protected void doMarshal(Object source,
HierarchicalStreamWriter writer, MarshallingContext context) {
super.doMarshal(source, writer, context);
postDoMarshal(source,writer,context);
}
protected void postDoMarshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
}
}
/**
* Converter for workspaces and namespaces.
*/
class SpaceInfoConverter extends AbstractReflectionConverter {
@Override
public boolean canConvert(Class type) {
return WorkspaceInfo.class.isAssignableFrom(type) ||
NamespaceInfo.class.isAssignableFrom(type);
}
@Override
protected void postDoMarshal(Object source,
HierarchicalStreamWriter writer, MarshallingContext context) {
if ( source instanceof WorkspaceInfo ) {
callback.postEncodeWorkspace( (WorkspaceInfo)source,writer,context );
}
else {
callback.postEncodeNamespace( (NamespaceInfo) source,writer,context );
}
}
}
/**
* Converter for {@link DataStoreInfo}, {@link CoverageStoreInfo}, and {@link WMSStoreInfo}
*/
class StoreInfoConverter extends AbstractReflectionConverter {
public StoreInfoConverter() {
super(StoreInfo.class);
}
@Override
protected void postDoMarshal(Object result,
HierarchicalStreamWriter writer, MarshallingContext context) {
StoreInfo store = (StoreInfo) result;
if ( store instanceof DataStoreInfo ) {
callback.postEncodeDataStore( (DataStoreInfo) store, writer, context );
} else if( store instanceof CoverageStoreInfo ){
callback.postEncodeCoverageStore( (CoverageStoreInfo) store, writer, context );
} else if (store instanceof WMSStoreInfo){
callback.postEncodeWMSStore( (WMSStoreInfo) store, writer, context );
} else {
throw new IllegalArgumentException("Unknown store type: "
+ (store == null ? "null" : store.getClass().getName()));
}
}
@Override
public Object doUnmarshal(Object result,
HierarchicalStreamReader reader, UnmarshallingContext context) {
StoreInfo store = (StoreInfo) super.doUnmarshal(result, reader, context);
// 2.1.3+ backwards compatibility check
if (store instanceof WMSStoreInfo) {
WMSStoreInfo wmsStore = (WMSStoreInfo) store;
MetadataMap metadata = wmsStore.getMetadata();
Integer maxConnections = null;
Integer connectTimeout = null;
Integer readTimeout = null;
if (metadata != null) {
maxConnections = metadata.get("maxConnections", Integer.class);
connectTimeout = metadata.get("connectTimeout", Integer.class);
readTimeout = metadata.get("readTimeout", Integer.class);
metadata.remove("maxConnections");
metadata.remove("connectTimeout");
metadata.remove("readTimeout");
}
if (wmsStore.getMaxConnections() <= 0) {
wmsStore.setMaxConnections(maxConnections != null
&& maxConnections.intValue() > 0 ? maxConnections
: WMSStoreInfoImpl.DEFAULT_MAX_CONNECTIONS);
}
if (wmsStore.getConnectTimeout() <= 0) {
wmsStore.setConnectTimeout(connectTimeout != null
&& connectTimeout.intValue() > 0 ? connectTimeout
: WMSStoreInfoImpl.DEFAULT_CONNECT_TIMEOUT);
}
if (wmsStore.getReadTimeout() <= 0) {
wmsStore.setReadTimeout(readTimeout != null && readTimeout.intValue() > 0 ? readTimeout
: WMSStoreInfoImpl.DEFAULT_READ_TIMEOUT);
}
}
LOGGER.info( "Loaded store '" + store.getName() + "', " + (store.isEnabled() ? "enabled" : "disabled") );
return store;
}
}
/**
* Converter for multi hash maps containing coverage stores and data stores.
*/
static class StoreMultiHashMapConverter implements Converter {
public boolean canConvert(Class type) {
return MultiHashMap.class.equals(type);
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
MultiHashMap map = (MultiHashMap) source;
for (Object v : map.values()) {
if (v instanceof DataStoreInfo) {
writer.startNode("dataStore");
context.convertAnother(v);
writer.endNode();
}
if (v instanceof CoverageStoreInfo ) {
writer.startNode( "coverageStore" );
context.convertAnother(v);
writer.endNode();
}
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
MultiHashMap map = new MultiHashMap();
while( reader.hasMoreChildren() ) {
reader.moveDown();
Object o = 0;
if ( "dataStore".equals( reader.getNodeName() ) ) {
o = context.convertAnother( map, DataStoreInfoImpl.class );
}
else {
o = context.convertAnother( map, CoverageStoreInfoImpl.class );
}
map.put( o.getClass(), o );
reader.moveUp();
}
return map;
}
}
/**
* Converter for handling maps containing workspaces and namespaces.
*
*/
static class SpaceMapConverter implements Converter {
String name;
public SpaceMapConverter( String name ) {
this.name = name;
}
public boolean canConvert(Class type) {
return Map.class.isAssignableFrom(type);
}
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
Map map = (Map) source;
for (Object o : map.entrySet()) {
Map.Entry e = (Map.Entry) o;
if ( e.getKey() == null ) {
continue;
}
writer.startNode(name);
if ( map.get( null ) == e.getValue() ) {
writer.addAttribute("default", "true");
}
context.convertAnother(e.getValue());
writer.endNode();
}
}
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Map map = new HashMap();
while( reader.hasMoreChildren() ) {
reader.moveDown();
boolean def = "true".equals( reader.getAttribute( "default") );
if ( "namespace".equals( name ) ) {
NamespaceInfoImpl ns = (NamespaceInfoImpl) context.convertAnother( map, NamespaceInfoImpl.class );
map.put( ns.getPrefix() , ns );
if ( def ) {
map.put( null, ns );
}
LOGGER.info( "Loading namespace '" + ns.getPrefix() + "'" );
}
else {
WorkspaceInfoImpl ws = (WorkspaceInfoImpl) context.convertAnother( map, WorkspaceInfoImpl.class );
map.put( ws.getName() , ws );
if ( def ) {
map.put( null, ws );
}
LOGGER.info( "Loading workspace '" + ws.getName() + "'" );
}
reader.moveUp();
}
return map;
}
}
/**
* Base converter for handling resources.
*/
class ResourceInfoConverter extends AbstractReflectionConverter {
public ResourceInfoConverter() {
this(ResourceInfo.class);
}
public ResourceInfoConverter(Class clazz ) {
super(clazz);
}
public Object doUnmarshal(Object result,
HierarchicalStreamReader reader, UnmarshallingContext context) {
ResourceInfo obj = (ResourceInfo) super.doUnmarshal(result, reader, context);
String enabled = obj.isEnabled() ? "enabled" : "disabled";
String type = obj instanceof CoverageInfo ? "coverage" :
obj instanceof FeatureTypeInfo ? "feature type" : "resource";
LOGGER.info( "Loaded " + type + " '" + obj.getName() + "', " + enabled );
return obj;
}
}
/**
* Converter for feature types.
*/
class FeatureTypeInfoConverter extends ResourceInfoConverter {
public FeatureTypeInfoConverter() {
super(FeatureTypeInfo.class);
}
@Override
protected void postDoMarshal(Object result,
HierarchicalStreamWriter writer, MarshallingContext context) {
FeatureTypeInfoImpl featureType = (FeatureTypeInfoImpl) result;
//ensure null list does not result
if ( featureType.getAttributes() == null ){
featureType.setAttributes(new ArrayList());
}
if( featureType.getMetadata() == null) {
featureType.setMetadata(new MetadataMap());
}
callback.postEncodeFeatureType(featureType, writer, context);
}
}
/**
* Converter for feature types.
*/
class CoverageInfoConverter extends ResourceInfoConverter {
public CoverageInfoConverter() {
super( CoverageInfo.class );
}
@Override
protected void postDoMarshal(Object result,
HierarchicalStreamWriter writer, MarshallingContext context) {
callback.postEncodeCoverage((CoverageInfo)result, writer, context);
}
}
/**
* Converter for layers.
*/
class LayerInfoConverter extends AbstractReflectionConverter {
public LayerInfoConverter() {
super( LayerInfo.class );
}
@Override
protected void doMarshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
// write out the name, which is a derived property now
// TODO: remove this when resource/publishing split is done
LayerInfo l = (LayerInfo) source;
writer.startNode("name");
writer.setValue(l.getName());
writer.endNode();
// {
// String authUrlsSerializedForm = AuthorityURLInfoInfoListConverter.toString(l
// .getAuthorityURLs());
// if (null != authUrlsSerializedForm) {
// l.getMetadata().put("authorityURLs", authUrlsSerializedForm);
// }
// }
//
// {
// String identifiersSerializedForm = LayerIdentifierInfoListConverter.toString(l
// .getIdentifiers());
// if (null != identifiersSerializedForm) {
// l.getMetadata().put("identifiers", identifiersSerializedForm);
// }
// }
super.doMarshal(source, writer, context);
}
@Override
protected void postDoMarshal(Object result,
HierarchicalStreamWriter writer, MarshallingContext context) {
/*
LayerInfo l = (LayerInfo) result;
writer.startNode("resource");
context.convertAnother( l.getResource(), new ReferenceConverter( ResourceInfo.class ) );
writer.endNode();
*/
callback.postEncodeLayer( (LayerInfo) result, writer, context );
}
@Override
public Object doUnmarshal(Object result, HierarchicalStreamReader reader,
UnmarshallingContext context) {
LayerInfoImpl li = (LayerInfoImpl) super.doUnmarshal(result, reader, context);
MetadataMap metadata = li.getMetadata();
if (li.getAuthorityURLs() == null && metadata != null) {
String serialized = metadata.get("authorityURLs", String.class);
List<AuthorityURLInfo> authorities;
if (serialized == null) {
authorities = new ArrayList<AuthorityURLInfo>(1);
} else {
authorities = AuthorityURLInfoInfoListConverter.fromString(serialized);
}
li.setAuthorityURLs(authorities);
}
if (li.getIdentifiers() == null && metadata != null) {
String serialized = metadata.get("identifiers", String.class);
List<LayerIdentifierInfo> identifiers;
if (serialized == null) {
identifiers = new ArrayList<LayerIdentifierInfo>(1);
} else {
identifiers = LayerIdentifierInfoListConverter.fromString(serialized);
}
li.setIdentifiers(identifiers);
}
return li;
}
}
/**
* Converter for layer groups.
*/
class LayerGroupInfoConverter extends AbstractReflectionConverter {
public LayerGroupInfoConverter() {
super( LayerGroupInfo.class );
}
@Override
protected void postDoMarshal(Object result,
HierarchicalStreamWriter writer, MarshallingContext context) {
callback.postEncodeLayerGroup((LayerGroupInfo)result, writer, context);
}
@Override
public Object doUnmarshal(Object result, HierarchicalStreamReader reader,
UnmarshallingContext context) {
LayerGroupInfoImpl lgi = (LayerGroupInfoImpl) super
.doUnmarshal(result, reader, context);
MetadataMap metadata = lgi.getMetadata();
if (lgi.getAuthorityURLs() == null && metadata != null) {
String serialized = metadata.get("authorityURLs", String.class);
List<AuthorityURLInfo> authorities;
if (serialized == null) {
authorities = new ArrayList<AuthorityURLInfo>(1);
} else {
authorities = AuthorityURLInfoInfoListConverter.fromString(serialized);
}
lgi.setAuthorityURLs(authorities);
}
if (lgi.getIdentifiers() == null && metadata != null) {
String serialized = metadata.get("identifiers", String.class);
List<LayerIdentifierInfo> identifiers;
if (serialized == null) {
identifiers = new ArrayList<LayerIdentifierInfo>(1);
} else {
identifiers = LayerIdentifierInfoListConverter.fromString(serialized);
}
lgi.setIdentifiers(identifiers);
}
return lgi;
}
@Override
protected void doMarshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
// LayerGroupInfo l = (LayerGroupInfo) source;
//
// {
// String authUrlsSerializedForm = AuthorityURLInfoInfoListConverter.toString(l
// .getAuthorityURLs());
// if (null != authUrlsSerializedForm) {
// l.getMetadata().put("authorityURLs", authUrlsSerializedForm);
// }
// }
//
// {
// String identifiersSerializedForm = LayerIdentifierInfoListConverter.toString(l
// .getIdentifiers());
// if (null != identifiersSerializedForm) {
// l.getMetadata().put("identifiers", identifiersSerializedForm);
// }
// }
super.doMarshal(source, writer, context);
}
}
class VirtualTableConverter implements Converter {
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
VirtualTable vt = (VirtualTable) source;
writer.startNode("name");
writer.setValue(vt.getName());
writer.endNode();
writer.startNode("sql");
writer.setValue(vt.getSql());
writer.endNode();
if(vt.getPrimaryKeyColumns() != null) {
for(String pk : vt.getPrimaryKeyColumns()) {
writer.startNode("keyColumn");
writer.setValue(pk);
writer.endNode();
}
}
if(vt.getGeometries() != null) {
for (String geom : vt.getGeometries()) {
writer.startNode("geometry");
writer.startNode("name");
writer.setValue(geom);
writer.endNode();
writer.startNode("type");
writer.setValue(Geometries.getForBinding(vt.getGeometryType(geom)).getName());
writer.endNode();
writer.startNode("srid");
writer.setValue(String.valueOf(vt.getNativeSrid(geom)));
writer.endNode();
writer.endNode();
}
}
if(vt.getParameterNames().size() > 0) {
for(String name : vt.getParameterNames()) {
VirtualTableParameter param = vt.getParameter(name);
writer.startNode("parameter");
writer.startNode("name");
writer.setValue(name);
writer.endNode();
if(param.getDefaultValue() != null) {
writer.startNode("defaultValue");
writer.setValue(param.getDefaultValue());
writer.endNode();
}
if(param.getValidator() != null) {
if(param.getValidator() instanceof RegexpValidator) {
writer.startNode("regexpValidator");
writer.setValue(((RegexpValidator) param.getValidator()).getPattern().pattern());
writer.endNode();
} else {
throw new RuntimeException("Cannot handle this type of validator," +
" please extend the VirtualTableConverter " + param.getValidator().getClass());
}
}
writer.endNode();
}
}
}
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
String name = readValue("name", String.class, reader);
String sql = readValue("sql", String.class, reader);
VirtualTable vt = new VirtualTable(name, sql);
List<String> primaryKeys = new ArrayList<String>();
while(reader.hasMoreChildren()) {
reader.moveDown();
if(reader.getNodeName().equals("keyColumn")) {
primaryKeys.add(reader.getValue());
} else if(reader.getNodeName().equals("geometry")) {
String geomName = readValue("name", String.class, reader);
Geometries geomType = Geometries.getForName(readValue("type", String.class, reader));
Class type = geomType == null ? Geometry.class : geomType.getBinding();
int srid = readValue("srid", Integer.class, reader);
vt.addGeometryMetadatata(geomName, type, srid);
} else if(reader.getNodeName().equals("parameter")) {
String pname = readValue("name", String.class, reader);
String defaultValue = null;
Validator validator = null;
while(reader.hasMoreChildren()) {
reader.moveDown();
if(reader.getNodeName().equals("defaultValue")) {
defaultValue = reader.getValue();
} else if(reader.getNodeName().equals("regexpValidator")) {
validator = new RegexpValidator(reader.getValue());
}
reader.moveUp();
}
vt.addParameter(new VirtualTableParameter(pname, defaultValue, validator));
}
reader.moveUp();
}
vt.setPrimaryKeyColumns(primaryKeys);
return vt;
}
<T> T readValue(String name, Class<T> type, HierarchicalStreamReader reader) {
if(!reader.hasMoreChildren()) {
throw new IllegalArgumentException("Expected element " + name + " but could not find it");
}
reader.moveDown();
if(!name.equals(reader.getNodeName())) {
throw new IllegalArgumentException("Expected element " + name + " but found " + reader.getNodeName() + " instead");
}
String value = reader.getValue();
reader.moveUp();
return Converters.convert(value, type);
}
public boolean canConvert(Class type) {
return VirtualTable.class.isAssignableFrom(type);
}
}
static class KeywordInfoConverter extends AbstractSingleValueConverter {
static Pattern RE = Pattern.compile(
"([^\\\\]+)(?:\\\\@language=([^\\\\]+)\\\\;)?(?:\\\\@vocabulary=([^\\\\]+)\\\\;)?");
@Override
public boolean canConvert(Class type) {
return Keyword.class.isAssignableFrom(type);
}
@Override
public Object fromString(String str) {
Matcher m = RE.matcher(str);
if (!m.matches()) {
throw new IllegalArgumentException(
String.format("%s does not match regular expression: %s", str, RE));
}
KeywordInfo kw = new Keyword(m.group(1));
if (m.group(2) != null) {
kw.setLanguage(m.group(2));
}
if (m.group(3) != null) {
kw.setVocabulary(m.group(3));
}
return kw;
}
@Override
public String toString(Object obj) {
KeywordInfo kw = (KeywordInfo) obj;
StringBuilder sb = new StringBuilder();
sb.append(kw.getValue());
if (kw.getLanguage() != null) {
sb.append("\\@language=").append(kw.getLanguage()).append("\\;");
}
if (kw.getVocabulary() != null) {
sb.append("\\@vocabulary=").append(kw.getVocabulary()).append("\\;");
}
return sb.toString();
}
}
class KeywordListConverter extends LaxCollectionConverter {
public KeywordListConverter() {
super(getXStream().getMapper());
}
@Override
protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context,
Object current) {
return context.convertAnother(current, Keyword.class);
}
@Override
protected void writeItem(Object item, MarshallingContext context,
HierarchicalStreamWriter writer) {
ExtendedHierarchicalStreamWriterHelper.startNode(writer, "string", Keyword.class);
context.convertAnother(item);
writer.endNode();
}
}
}