Package org.geoserver.config.util

Source Code of org.geoserver.config.util.XStreamPersister$KeywordListConverter

/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.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 javax.measure.quantity.Quantity;
import javax.measure.unit.Unit;

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.CoverageView;
import org.geoserver.catalog.CoverageView.InputCoverageBand;
import org.geoserver.catalog.CoverageView.CoverageBand;
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.measure.Measure;
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.MeasureConverterFactory;
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);
       
        // ignore unkonwn fields, this should help using data dirs that has new config elements
        // with older versions of GeoServer
        xs.ignoreUnknownElements();
       
        // 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( "coverageBand", CoverageBand.class);
        xs.alias( "inputCoverageBand", InputCoverageBand.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());
        xs.registerConverter(new SettingsInfoConverter());
        xs.registerConverter(new MeasureConverter());
       
        // register Virtual structure handling
        registerBreifMapComplexType("virtualTable", VirtualTable.class);
        registerBreifMapComplexType("coverageView", CoverageView.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();
                    String nodeName = reader.getNodeName();
                    if ("workspace".equals(nodeName)) {
                        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 if("name".equals(nodeName) || "id".equals(nodeName) || "prefix".equals(nodeName)) {
                        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.getResponseSRS() == null) {
                featureType.setResponseSRS(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);
               
            VirtualTable vt = new VirtualTable(name, sql, false);
            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));
                } else if(reader.getNodeName().equals("escapeSql")) {
                vt.setEscapeSql(Boolean.valueOf(reader.getValue()));
                }
                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();
           try {
               if(!name.equals(reader.getNodeName())) {
                   throw new IllegalArgumentException("Expected element " + name + " but found " + reader.getNodeName() + " instead");
               }
               String value = reader.getValue();
               return Converters.convert(value, type);
           } finally {
               reader.moveUp();
           }
          
        }

        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();
        }
    }
   
    static class MeasureConverter extends AbstractSingleValueConverter {

      org.geotools.util.Converter str2Measure = new MeasureConverterFactory().createConverter(String.class, Measure.class, null);
      org.geotools.util.Converter measure2Str = new MeasureConverterFactory().createConverter(Measure.class, String.class, null);

     
        @Override
        public boolean canConvert(Class type) {
            return Measure.class.isAssignableFrom(type);
        }

        @Override
        public Object fromString(String str) {
          try {
        return str2Measure.convert(str, Measure.class);
      } catch (Exception e) {
        throw new IllegalArgumentException(e);
      }
        }

        @Override
        public String toString(Object obj) {
          try {
        return measure2Str.convert(obj, String.class);
      } catch (Exception e) {
        throw new IllegalArgumentException(e);
      }
        }
    }


    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();
        }

    }
   
    /**
     * Converter for SettingsInfo class
     */
    class SettingsInfoConverter extends AbstractReflectionConverter {
       
        public SettingsInfoConverter() {
            super(SettingsInfo.class);
        }
       
        public Object doUnmarshal(Object result,
                HierarchicalStreamReader reader, UnmarshallingContext context) {
            SettingsInfoImpl obj = (SettingsInfoImpl) super.doUnmarshal(result, reader, context);
            if(obj.getMetadata() == null){
                obj.setMetadata(new MetadataMap());
            }
            if(obj.getContact() == null){
                obj.setContact(new ContactInfoImpl());
            }
            if(obj.getClientProperties() == null){
                obj.setClientProperties(new HashMap<Object, Object>());
            }
            return obj;
        }
    }
}
TOP

Related Classes of org.geoserver.config.util.XStreamPersister$KeywordListConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
y>