/* Copyright (c) 2001 - 2008 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.config; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.io.filefilter.SuffixFileFilter; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDTypeDefinition; import org.geoserver.catalog.AttributeTypeInfo; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogFactory; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.CoverageStoreInfo; import org.geoserver.catalog.DataStoreInfo; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.catalog.Wrapper; import org.geoserver.catalog.event.CatalogListener; import org.geoserver.catalog.impl.CatalogImpl; import org.geoserver.catalog.util.LegacyCatalogImporter; import org.geoserver.catalog.util.LegacyCatalogReader; import org.geoserver.catalog.util.LegacyFeatureTypeInfoReader; import org.geoserver.config.impl.GeoServerInfoImpl; import org.geoserver.config.util.LegacyConfigurationImporter; import org.geoserver.config.util.XStreamPersister; import org.geoserver.config.util.XStreamPersisterFactory; import org.geoserver.config.util.XStreamServiceLoader; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerResourceLoader; import org.geotools.gml2.GML; import org.geotools.util.logging.Logging; import org.geotools.xml.Schemas; import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.web.context.WebApplicationContext; import org.vfny.geoserver.global.GeoserverDataDirectory; /** * Initializes GeoServer configuration and catalog on startup. * <p> * This class is registered in a spring context and post processes the * singleton beans {@link Catalog} and {@link GeoServer}, populating them * with data from the GeoServer data directory. * </p> * @author Justin Deoliveira, The Open Planning Project * */ public class GeoServerLoader implements BeanPostProcessor, DisposableBean, ApplicationContextAware { static Logger LOGGER = Logging.getLogger( "org.geoserver" ); GeoServerResourceLoader resourceLoader; GeoServer geoserver; XStreamPersisterFactory xpf = new XStreamPersisterFactory(); //JD: this is a hack for the moment, it is used only to maintain tests since the test setup relies // on the old data directory structure, once the tests have been ported to the new structure // this ugly hack can die static boolean legacy = false; public GeoServerLoader( GeoServerResourceLoader resourceLoader ) { this.resourceLoader = resourceLoader; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { GeoserverDataDirectory.init((WebApplicationContext)applicationContext); } public void setXStreamPeristerFactory(XStreamPersisterFactory xpf) { this.xpf = xpf; } public static void setLegacy(boolean legacy) { GeoServerLoader.legacy = legacy; } public final Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } public final Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if ( bean instanceof Catalog ) { //ensure this is not a wrapper but the real deal if ( bean instanceof Wrapper && ((Wrapper) bean).isWrapperFor(Catalog.class) ) { return bean; } //load try { Catalog catalog = (Catalog) bean; XStreamPersister xp = xpf.createXMLPersister(); xp.setCatalog( catalog ); loadCatalog( catalog, xp ); } catch (Exception e) { throw new RuntimeException( e ); } } if ( bean instanceof GeoServer ) { geoserver = (GeoServer) bean; try { loadGeoServer( geoserver, xpf.createXMLPersister() ); } catch (Exception e) { throw new RuntimeException( e ); } //initialize(); } return bean; } protected void loadCatalog(Catalog catalog, XStreamPersister xp) throws Exception { catalog.setResourceLoader(resourceLoader); //look for catalog.xml, if it exists assume we are dealing with // an old data directory File f = resourceLoader.find( "catalog.xml" ); if ( f == null ) { //assume 2.x style data directory CatalogImpl catalog2 = (CatalogImpl) readCatalog( xp ); ((CatalogImpl)catalog).sync( catalog2 ); } else { // import old style catalog, register the persister now so that we start // with a new version of the catalog CatalogImpl catalog2 = (CatalogImpl) readLegacyCatalog( f, xp ); ((CatalogImpl)catalog).sync( catalog2 ); } //initialize styles initializeStyles(catalog); if ( !legacy ) { //add the listener which will persist changes catalog.addListener( new GeoServerPersister( resourceLoader, xp ) ); } } protected void loadGeoServer(final GeoServer geoServer, XStreamPersister xp) throws Exception { //add event listener which persists services final List<XStreamServiceLoader> loaders = GeoServerExtensions.extensions( XStreamServiceLoader.class ); geoServer.addListener( new ConfigurationListenerAdapter() { @Override public void handlePostServiceChange(ServiceInfo service) { for ( XStreamServiceLoader<ServiceInfo> l : loaders ) { if ( l.getServiceClass().isInstance( service ) ) { try { l.save( service, geoServer ); } catch (Throwable t) { //TODO: log this t.printStackTrace(); } } } } } ); //look for services.xml, if it exists assume we are dealing with // an old data directory File f = resourceLoader.find( "services.xml" ); if ( f == null ) { //assume 2.x style f = resourceLoader.find( "global.xml"); if ( f != null ) { BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); GeoServerInfoImpl global = (GeoServerInfoImpl) xpf.createXMLPersister().load( in, GeoServerInfo.class ); // fill in default collection values if needed //JD: this should not be here, it should be moved to a resolve() method // on GeoServer, like the way the catalog does it if(global.getMetadata() == null) global.setMetadata(new MetadataMap()); if(global.getClientProperties() == null) global.setClientProperties(new HashMap<Object, Object>()); geoServer.setGlobal( global ); } //load logging f = resourceLoader.find( "logging.xml" ); if ( f != null ) { BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); LoggingInfo logging = xpf.createXMLPersister().load( in, LoggingInfo.class ); geoServer.setLogging( logging ); } //load services for ( XStreamServiceLoader<ServiceInfo> l : loaders ) { try { ServiceInfo s = l.load( geoServer ); geoServer.add( s ); LOGGER.info( "Loaded service '" + s.getId() + "', " + (s.isEnabled()?"enabled":"disabled") ); } catch( Throwable t ) { //TODO: log this t.printStackTrace(); } } } else { //add listener now as a converter which will convert from the old style // data directory to the new GeoServerPersister p = new GeoServerPersister( resourceLoader, xp ); geoServer.addListener( p ); //import old style services.xml new LegacyConfigurationImporter(geoServer).imprt(resourceLoader.getBaseDirectory()); geoServer.removeListener( p ); //rename the services.xml file f.renameTo( new File( f.getParentFile(), "services.xml.old" ) ); } //load initializer extensions List<GeoServerInitializer> initializers = GeoServerExtensions.extensions( GeoServerInitializer.class ); for ( GeoServerInitializer initer : initializers ) { try { initer.initialize( geoServer ); } catch( Throwable t ) { //TODO: log this t.printStackTrace(); } } geoServer.addListener( new GeoServerPersister( resourceLoader, xp ) ); } //JD: NOTE! This method is no longer used on trunk protected void initialize() { //load catalog LegacyCatalogImporter catalogImporter = new LegacyCatalogImporter(); catalogImporter.setResourceLoader(resourceLoader); Catalog catalog = geoserver.getCatalog(); if(catalog instanceof Wrapper && ((Wrapper) catalog).isWrapperFor(Catalog.class)) { catalog = ((Wrapper) catalog).unwrap(Catalog.class); } catalogImporter.setCatalog(catalog); try { catalogImporter.imprt( resourceLoader.getBaseDirectory() ); } catch(Exception e) { throw new RuntimeException( e ); } //load configuration LegacyConfigurationImporter importer = new LegacyConfigurationImporter(); importer.setConfiguration(geoserver); try { importer.imprt( resourceLoader.getBaseDirectory() ); } catch (Exception e) { throw new RuntimeException( e ); } //load initializer extensions List<GeoServerInitializer> initializers = GeoServerExtensions.extensions( GeoServerInitializer.class ); for ( GeoServerInitializer initer : initializers ) { try { initer.initialize( geoserver ); } catch( Throwable t ) { //TODO: log this t.printStackTrace(); } } //load listeners List<CatalogListener> catalogListeners = GeoServerExtensions.extensions( CatalogListener.class ); for ( CatalogListener l : catalogListeners ) { catalog.addListener( l ); } List<ConfigurationListener> configListeners = GeoServerExtensions.extensions( ConfigurationListener.class ); for ( ConfigurationListener l : configListeners ) { geoserver.addListener( l ); } } /** * Does some post processing on the catalog to ensure that the "well-known" styles * are always around. */ void initializeStyles( Catalog catalog ) throws IOException { if ( catalog.getStyleByName( StyleInfo.DEFAULT_POINT ) == null ) { initializeStyle( catalog, StyleInfo.DEFAULT_POINT, "default_point.sld" ); } if ( catalog.getStyleByName( StyleInfo.DEFAULT_LINE ) == null ) { initializeStyle( catalog, StyleInfo.DEFAULT_LINE, "default_line.sld" ); } if ( catalog.getStyleByName( StyleInfo.DEFAULT_POLYGON ) == null ) { initializeStyle( catalog, StyleInfo.DEFAULT_POLYGON, "default_line.sld" ); } if ( catalog.getStyleByName( StyleInfo.DEFAULT_RASTER ) == null ) { initializeStyle( catalog, StyleInfo.DEFAULT_RASTER, "default_raster.sld" ); } } /** * Copies a well known style out to the data directory and adds a catalog entry for it. */ void initializeStyle( Catalog catalog, String styleName, String sld ) throws IOException { //copy the file out to the data directory if necessary if ( resourceLoader.find( "styles", sld ) == null ) { FileUtils.copyURLToFile(getClass().getResource(sld), new File( resourceLoader.find( "styles" ), sld) ); } //create a style for it StyleInfo s = catalog.getFactory().createStyle(); s.setName( styleName ); s.setFilename( sld ); catalog.add( s ); } public void reload() throws Exception { destroy(); //reload catalog, make sure we reload the underlying catalog, not any wrappers Catalog catalog = geoserver.getCatalog(); if ( catalog instanceof Wrapper ) { catalog = ((Wrapper)geoserver.getCatalog()).unwrap(Catalog.class); } XStreamPersister xp = xpf.createXMLPersister(); xp.setCatalog( catalog ); loadCatalog( catalog, xp ); loadGeoServer( geoserver, xp); } //TODO: kill this method, it is not longer needed since persistance is event based public void persist() throws Exception { //TODO: make the configuration backend pluggable... or loadable // from application context, or web.xml, or env variable, etc... XStreamPersister p = xpf.createXMLPersister(); BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( resourceLoader.createFile( "catalog2.xml" ) ) ); //persist catalog Catalog catalog = geoserver.getCatalog(); if( catalog instanceof Wrapper ) { catalog = ((Wrapper)catalog).unwrap( Catalog.class ); } p.save( catalog, out ); out.flush(); out.close(); //persist resources File workspaces = resourceLoader.findOrCreateDirectory( "workspaces" ); for ( ResourceInfo r : catalog.getResources( ResourceInfo.class ) ) { WorkspaceInfo ws = r.getStore().getWorkspace(); File workspace = new File( workspaces, ws.getName() ); if ( !workspace.exists() ) { workspace.mkdir(); } String dirName = r.getStore().getName() + "_" + r.getNativeName(); //dirName = URLEncoder.encode( dirName, "UTF-8" ); File dir = new File( workspace, dirName ); if ( !dir.exists() ) { dir.mkdir(); } File info = new File( dir, "resource.xml" ); try { persist( p, r, info ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Error persisting '" + r.getName() + "'", e ); } //persist layers publishing the resource LayerInfo l = catalog.getLayers( r ).get( 0 ); try { persist( p, l, new File( dir, "layer.xml" ) ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Error persisting layer '" + l.getName() + "'", e ); } } //persist global try { persist( p, geoserver.getGlobal(), resourceLoader.createFile( "global.xml" ) ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Error persisting global configuration.", e ); } //persist services Collection services = geoserver.getServices(); List<ServiceLoader> loaders = GeoServerExtensions.extensions( ServiceLoader.class ); for ( Iterator s = services.iterator(); s.hasNext(); ) { ServiceInfo service = (ServiceInfo) s.next(); for ( ServiceLoader loader : loaders ) { if (loader.getServiceClass().isInstance( service ) ) { try { loader.save( service, geoserver ); break; } catch( Throwable t ) { LOGGER.warning( "Error persisting service: " + service.getId() ); LOGGER.log( Level.INFO, "", t ); } } } } } /** * Reads the catalog from disk. */ Catalog readCatalog( XStreamPersister xp ) throws Exception { Catalog catalog = new CatalogImpl(); catalog.setResourceLoader(resourceLoader); xp.setCatalog( catalog ); CatalogFactory factory = catalog.getFactory(); //styles File styles = resourceLoader.find( "styles" ); for ( File sf : list(styles,new SuffixFileFilter(".xml") ) ) { try { //handle the .xml.xml case if (new File(styles,sf.getName()+".xml").exists()) { continue; } StyleInfo s = depersist( xp, sf, StyleInfo.class ); catalog.add( s ); LOGGER.info( "Loaded style '" + s.getName() + "'" ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load style from file '" + sf.getName() + "'" , e ); } } //workspaces, stores, and resources File workspaces = resourceLoader.find( "workspaces" ); if ( workspaces != null ) { //do a first quick scan over all workspaces, setting the default File dws = new File(workspaces, "default.xml"); WorkspaceInfo defaultWorkspace = null; if (dws.exists()) { try { defaultWorkspace = depersist(xp, dws, WorkspaceInfo.class); LOGGER.info("Loaded default workspace " + defaultWorkspace.getName()); } catch( Exception e ) { LOGGER.log(Level.WARNING, "Failed to load default workspace", e); } } else { LOGGER.warning("No default workspace was found."); } for ( File wsd : list(workspaces, DirectoryFileFilter.INSTANCE ) ) { File f = new File( wsd, "workspace.xml"); if ( !f.exists() ) { continue; } WorkspaceInfo ws = null; try { ws = depersist( xp, f, WorkspaceInfo.class ); catalog.add( ws ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load workspace '" + wsd.getName() + "'" , e ); continue; } LOGGER.info( "Loaded workspace '" + ws.getName() +"'"); //load the namespace File nsf = new File( wsd, "namespace.xml" ); NamespaceInfo ns = null; if ( nsf.exists() ) { try { ns = depersist( xp, nsf, NamespaceInfo.class ); catalog.add( ns ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load namespace for '" + wsd.getName() + "'" , e ); } } //set the default workspace, this value might be null in the case of coming from a // 2.0.0 data directory. See http://jira.codehaus.org/browse/GEOS-3440 if (defaultWorkspace != null ) { if (ws.getName().equals(defaultWorkspace.getName())) { catalog.setDefaultWorkspace(ws); if (ns != null) { catalog.setDefaultNamespace(ns); } } } else { //create the default.xml file defaultWorkspace = catalog.getDefaultWorkspace(); if (defaultWorkspace != null) { try { persist(xp, defaultWorkspace, dws); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to persist default workspace '" + wsd.getName() + "'" , e ); } } } } for ( File wsd : list(workspaces, DirectoryFileFilter.INSTANCE ) ) { //load the stores for this workspace for ( File sd : list(wsd, DirectoryFileFilter.INSTANCE) ) { File f = new File( sd, "datastore.xml"); if ( f.exists() ) { //load as a datastore DataStoreInfo ds = null; try { ds = depersist( xp, f, DataStoreInfo.class ); catalog.add( ds ); LOGGER.info( "Loaded data store '" + ds.getName() +"'"); if (ds.isEnabled()) { //connect to the datastore to determine if we should disable it try { ds.getDataStore(null); } catch( Throwable t ) { LOGGER.warning( "Error connecting to '" + ds.getName() + "'. Disabling." ); LOGGER.log( Level.INFO, "", t ); ds.setError(t); ds.setEnabled(false); } } } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load data store '" + sd.getName() +"'", e); continue; } //load feature types for ( File ftd : list(sd,DirectoryFileFilter.INSTANCE) ) { f = new File( ftd, "featuretype.xml" ); if( f.exists() ) { FeatureTypeInfo ft = null; try { ft = depersist(xp,f,FeatureTypeInfo.class); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load feature type '" + ftd.getName() +"'", e); continue; } catalog.add( ft ); LOGGER.info( "Loaded feature type '" + ds.getName() +"'"); f = new File( ftd, "layer.xml" ); if ( f.exists() ) { try { LayerInfo l = depersist(xp, f, LayerInfo.class ); catalog.add( l ); LOGGER.info( "Loaded layer '" + l.getName() + "'" ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load layer for feature type '" + ft.getName() +"'", e); } } } else { LOGGER.warning( "Ignoring feature type directory " + ftd.getAbsolutePath() ); } } } else { //look for a coverage store f = new File( sd, "coveragestore.xml" ); if ( f.exists() ) { CoverageStoreInfo cs = null; try { cs = depersist( xp, f, CoverageStoreInfo.class ); catalog.add( cs ); LOGGER.info( "Loaded coverage store '" + cs.getName() +"'"); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load coverage store '" + sd.getName() +"'", e); continue; } //load coverages for ( File cd : list(sd,DirectoryFileFilter.INSTANCE) ) { f = new File( cd, "coverage.xml" ); if( f.exists() ) { CoverageInfo c = null; try { c = depersist(xp,f,CoverageInfo.class); catalog.add( c ); LOGGER.info( "Loaded coverage '" + cs.getName() +"'"); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load coverage '" + cd.getName() +"'", e); continue; } f = new File( cd, "layer.xml" ); if ( f.exists() ) { try { LayerInfo l = depersist(xp, f, LayerInfo.class ); catalog.add( l ); LOGGER.info( "Loaded layer '" + l.getName() + "'" ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load layer coverage '" + c.getName() +"'", e); } } } else { LOGGER.warning( "Ignoring coverage directory " + cd.getAbsolutePath() ); } } } else { LOGGER.warning( "Ignoring store directory '" + sd.getName() + "'"); continue; } } } } } else { LOGGER.warning( "No 'workspaces' directory found, unable to load any stores." ); } //namespaces //layergroups File layergroups = resourceLoader.find( "layergroups" ); if ( layergroups != null ) { for ( File lgf : list( layergroups, new SuffixFileFilter( ".xml" ) ) ) { try { LayerGroupInfo lg = depersist( xp, lgf, LayerGroupInfo.class ); catalog.add( lg ); LOGGER.info( "Loaded layer group '" + lg.getName() + "'" ); } catch( Exception e ) { LOGGER.log( Level.WARNING, "Failed to load layer group '" + lgf.getName() + "'", e ); } } } return catalog; } /** * Reads the legacy (1.x) catalog from disk. */ Catalog readLegacyCatalog(File f, XStreamPersister xp) throws Exception { Catalog catalog2 = new CatalogImpl(); catalog2.setResourceLoader(resourceLoader); //add listener now as a converter which will convert from the old style // data directory to the new GeoServerPersister p = new GeoServerPersister( resourceLoader, xp ); if ( !legacy ) { catalog2.addListener( p ); } LegacyCatalogImporter importer = new LegacyCatalogImporter(catalog2); importer.setResourceLoader(resourceLoader); importer.imprt(resourceLoader.getBaseDirectory()); if ( !legacy ) { catalog2.removeListener( p ); } if ( !legacy ) { //copy files from old feature type directories to new File featureTypesDir = resourceLoader.find( "featureTypes" ); if ( featureTypesDir != null ) { LegacyCatalogReader creader = new LegacyCatalogReader(); creader.read( f ); Map<String,Map<String,Object>> dataStores = creader.dataStores(); for ( File featureTypeDir : featureTypesDir.listFiles() ) { if ( !featureTypeDir.isDirectory() ) { continue; } File featureTypeInfo = new File( featureTypeDir, "info.xml" ); if ( !featureTypeInfo.exists() ) { continue; } LegacyFeatureTypeInfoReader reader = new LegacyFeatureTypeInfoReader(); reader.read( featureTypeInfo ); Map<String,Object> dataStore = dataStores.get( reader.dataStore() ); if ( dataStore == null ) { continue; } String namespace = (String) dataStore.get( "namespace" ); File destFeatureTypeDir = resourceLoader.find( "workspaces", namespace, reader.dataStore(), reader.name() ); if ( destFeatureTypeDir != null ) { //copy all the files over for ( File file : featureTypeDir.listFiles() ) { if ( file.isFile() && !featureTypeInfo.equals( file ) ) { FileUtils.copyFile( file, new File( destFeatureTypeDir, file.getName() ) ) ; } } } } } //rename catalog.xml f.renameTo( new File( f.getParentFile(), "catalog.xml.old" ) ); } return catalog2; } /** * Helper method which uses xstream to persist an object as xml on disk. */ void persist( XStreamPersister xp, Object obj, File f ) throws Exception { BufferedOutputStream out = new BufferedOutputStream( new FileOutputStream( f ) ); xp.save( obj, out ); out.flush(); out.close(); } /** * Helper method which uses xstream to depersist an object as xml from disk. */ <T> T depersist( XStreamPersister xp, File f , Class<T> clazz ) throws IOException { BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); T obj = xp.load( in, clazz ); in.close(); return obj; } /** * Helper method for listing files in a directory. */ Collection<File> list( File d, IOFileFilter filter ) { if (d == null) { return Collections.EMPTY_LIST; } ArrayList<File> files = new ArrayList(); for ( File f : d.listFiles() ) { if ( filter.accept( f ) ) { files.add( f ); } } return files; } public void destroy() throws Exception { //dispose geoserver.dispose(); } }