Java tutorial
/* Copyright (c) 2001 - 2013 OpenPlans - 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.Set; 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.LayerGroupInfo.Mode; 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.PublishedInfo; 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.SettingsInfo; 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.config.impl.SettingsInfoImpl; import org.geoserver.ows.util.OwsUtils; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.security.GeoServerSecurityManager; 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.referencing.wkt.Formattable; 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.ConversionException; 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 { private boolean unwrapNulls = true; /** * 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) { } /** * @deprecated use {@link #postEncodeReference(Object, String, String, HierarchicalStreamWriter, MarshallingContext)} */ protected void postEncodeReference(Object obj, String ref, HierarchicalStreamWriter writer, MarshallingContext context) { } protected void postEncodeReference(Object obj, String ref, String prefix, HierarchicalStreamWriter writer, MarshallingContext context) { if (prefix == null) { //call other method for backward compatability postEncodeReference(obj, ref, writer, 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>(); private Level forceLevel = LOGGER.getLevel() == null ? Level.INFO : LOGGER.getLevel(); /** * Flag controlling whether the persister should perform encryption on password fields */ boolean encryptPasswordFields = true; /** * 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); } /** * Sets null handling in proxy objects. * Defaults to unwrap. If set to false, proxy object are not transformed to nulls. * * @param unwrapNulls */ public void setUnwrapNulls(boolean unwrapNulls) { this.unwrapNulls = unwrapNulls; } protected void init(XStream xs) { // Default implementations initImplementationDefaults(xs); // Aliases xs.alias("global", GeoServerInfo.class); xs.alias("settings", SettingsInfo.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("published", PublishedInfo.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), "workspace", new ReferenceConverter(WorkspaceInfo.class)); 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), "workspace", new ReferenceConverter(WorkspaceInfo.class)); xs.registerLocalConverter(impl(LayerGroupInfo.class), "rootLayer", new ReferenceConverter(LayerInfo.class)); xs.registerLocalConverter(impl(LayerGroupInfo.class), "rootLayerStyle", new ReferenceConverter(StyleInfo.class)); xs.registerLocalConverter(impl(LayerGroupInfo.class), "layers", new ReferenceCollectionConverter(LayerInfo.class)); xs.registerLocalConverter(impl(LayerGroupInfo.class), "publishables", new ReferenceCollectionConverter(PublishedInfo.class, LayerInfo.class, LayerGroupInfo.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 GeoServerSecurityManager getSecurityManager() { return GeoServerExtensions.bean(GeoServerSecurityManager.class); } public void setCallback(Callback callback) { this.callback = callback; } public void setReferenceByName(boolean referenceByName) { this.referenceByName = referenceByName; } public void setExcludeIds() { xs.omitField(impl(WorkspaceInfo.class), "id"); xs.omitField(impl(NamespaceInfo.class), "id"); xs.omitField(impl(StoreInfo.class), "id"); xs.omitField(impl(StyleInfo.class), "id"); xs.omitField(impl(ResourceInfo.class), "id"); xs.omitField(impl(LayerInfo.class), "id"); xs.omitField(impl(LayerGroupInfo.class), "id"); xs.omitField(impl(AttributeTypeInfo.class), "id"); xs.omitField(impl(ServiceInfo.class), "id"); } public void setHideFeatureTypeAttributes() { xs.omitField(FeatureTypeInfoImpl.class, "attributes"); } public void setEncryptPasswordFields(boolean encryptPasswordFields) { this.encryptPasswordFields = encryptPasswordFields; } public boolean isEncryptPasswordFields() { return encryptPasswordFields; } /** * Sets the minimum level at which messages should be logged by the persister. * <p> * When this level is set even messages that the underlying logger is configured to emit will * be skipped. * </p> */ public void setLoggingLevel(Level level) { this.forceLevel = level; } /* * Helper to log a message forgoing if level is <= forceLevel. */ void log(Level level, String msg) { if ((LOGGER.isLoggable(level) && forceLevel.intValue() <= level.intValue())) { LOGGER.log(level, msg); } } /** * 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(SettingsInfoImpl.class, SettingsInfo.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 { static final String ENCRYPTED_FIELDS_KEY = "org.geoserver.config.encryptedFields"; 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) { Set<String> encryptionFields = (Set<String>) context.get(ENCRYPTED_FIELDS_KEY); if (encryptionFields == null) { encryptionFields = Collections.emptySet(); } GeoServerSecurityManager secMgr = encryptPasswordFields ? getSecurityManager() : null; 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(); } if (encryptionFields.contains(entry.getKey()) && secMgr != null) { str = secMgr.getConfigPasswordEncryptionHelper().encode(str); } 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, null, writer, context); } else { //use name if no id set String name = (String) OwsUtils.get(source, "name"); //use workspace name as a prefix if available String wsName = null; if (OwsUtils.has(source, "workspace")) { WorkspaceInfo workspace = (WorkspaceInfo) OwsUtils.get(source, "workspace"); if (workspace != null) { wsName = workspace.getName(); } } if (name != null) { writer.startNode("name"); writer.setValue(name); writer.endNode(); callback.postEncodeReference(source, name, wsName, 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; String pre = null; if (reader.hasMoreChildren()) { while (reader.hasMoreChildren()) { reader.moveDown(); if ("workspace".equals(reader.getNodeName())) { if (reader.hasMoreChildren()) { //specified as <workspace><name>[name]</name></workspace> reader.moveDown(); pre = reader.getValue(); reader.moveUp(); } else { //specified as <workspace>[name]</workspace> pre = reader.getValue(); } } else { ref = reader.getValue(); } reader.moveUp(); } } else { ref = reader.getValue(); } Object proxy = ResolvingProxy.create(ref, pre, clazz); Object resolved = proxy; if (catalog != null) { resolved = ResolvingProxy.resolve(catalog, proxy); } if (unwrapNulls) { return CatalogImpl.unwrap(resolved); } else { return resolved != null ? CatalogImpl.unwrap(resolved) : proxy; } // } } class ReferenceCollectionConverter extends LaxCollectionConverter { Class clazz; Class[] subclasses; public ReferenceCollectionConverter(Class clazz) { super(getXStream().getMapper()); this.clazz = clazz; } public ReferenceCollectionConverter(Class clazz, Class... subclasses) { super(getXStream().getMapper()); this.clazz = clazz; this.subclasses = subclasses; } @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) { if (subclasses != null) { Class theClass = null; for (Class clazz : subclasses) { if (clazz.isInstance(item)) { theClass = clazz; break; } } if (theClass == null) { throw new ConversionException( "Unexpected item " + item + " whose type is not among: " + subclasses); } String typeName = cam.serializedClass(theClass); writer.addAttribute("type", typeName); } context.convertAnother(item, new ReferenceConverter(clazz)); } writer.endNode(); } @Override protected Object readItem(HierarchicalStreamReader reader, UnmarshallingContext context, Object current) { Class theClass = clazz; if (subclasses != null) { String attribute = reader.getAttribute("type"); if (attribute != null) { theClass = mapper().realClass(attribute); } } return context.convertAnother(current, theClass, new ReferenceConverter(theClass)); } } /** * 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. */ public 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, false); if (epsg != null) { return "EPSG:" + epsg; } } catch (FactoryException e) { XStreamPersister.LOGGER.warning("Could not determine epsg code of crs, encoding as WKT"); } return new CRSConverter().toString(crs); } @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. * */ public static class CRSConverter extends AbstractSingleValueConverter { @Override public boolean canConvert(Class type) { return CoordinateReferenceSystem.class.isAssignableFrom(type); } @Override public String toString(Object obj) { return ((Formattable) obj).toWKT(2, false); } @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. */ protected 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 doMarshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { GeoServerSecurityManager secMgr = encryptPasswordFields ? getSecurityManager() : null; if (secMgr != null && secMgr.isInitialized()) { //set the hint for the map converter as to which fields to encode in the connection // parameter of this store context.put(BreifMapConverter.ENCRYPTED_FIELDS_KEY, secMgr.getConfigPasswordEncryptionHelper().getEncryptedFields((StoreInfo) source)); } super.doMarshal(source, writer, context); } @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); } } //process any parameters that require decryption GeoServerSecurityManager secMgr = encryptPasswordFields ? getSecurityManager() : null; if (secMgr != null) { secMgr.getConfigPasswordEncryptionHelper().decode(store); } log(Level.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. * */ 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); } log(Level.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); } log(Level.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"; log(Level.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); if (lgi.getMode() == null) { lgi.setMode(Mode.SINGLE); } lgi.convertLegacyLayers(); MetadataMap metadata = lgi.getMetadata(); /** * If we're upgrading from a 2.2.x server we have to read * property 'title' from metadata */ if (lgi.getTitle() == null && metadata != null) { String title = metadata.get("title", String.class); if (title != null) { lgi.setTitle(title); metadata.remove("title"); } } /** * If we're upgrading from a 2.2.x server we have to read * property 'abstract' from metadata */ if (lgi.getAbstract() == null && metadata != null) { String abstractTxt = metadata.get("abstract", String.class); if (abstractTxt != null) { lgi.setAbstract(abstractTxt); metadata.remove("abstract"); } } 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(); writer.startNode("escapeSql"); writer.setValue(Boolean.toString(vt.isEscapeSql())); 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); // escapeSql value may be missing from existing definitions. In this // case set it to false to prevent changing behaviour boolean escapeSql; try { escapeSql = Boolean.valueOf(readValue("escapeSql", String.class, reader)); } catch (IllegalArgumentException e) { escapeSql = false; // the reader is now in the wrong position, it must be moved back a line reader.moveUp(); } VirtualTable vt = new VirtualTable(name, sql, escapeSql); 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(); } } }