Java tutorial
/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.catalog; import java.awt.RenderingHints; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.IOUtils; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDSchema; import org.eclipse.xsd.XSDTypeDefinition; import org.geoserver.catalog.event.CatalogAddEvent; import org.geoserver.catalog.event.CatalogListener; import org.geoserver.catalog.event.CatalogModifyEvent; import org.geoserver.catalog.event.CatalogPostModifyEvent; import org.geoserver.catalog.event.CatalogRemoveEvent; import org.geoserver.catalog.impl.ModificationProxy; import org.geoserver.config.GeoServerDataDirectory; import org.geoserver.data.util.CoverageStoreUtils; import org.geoserver.data.util.CoverageUtils; import org.geoserver.feature.retype.RetypingFeatureSource; import org.geoserver.platform.GeoServerExtensions; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.data.DataAccess; import org.geotools.data.DataAccessFactory; import org.geotools.data.DataAccessFactory.Param; import org.geotools.data.DataAccessFinder; import org.geotools.data.DataSourceException; import org.geotools.data.DataStore; import org.geotools.data.DataUtilities; import org.geotools.data.FeatureSource; import org.geotools.data.Join; import org.geotools.data.Repository; import org.geotools.data.ows.HTTPClient; import org.geotools.data.ows.Layer; import org.geotools.data.ows.MultithreadedHttpClient; import org.geotools.data.ows.SimpleHttpClient; import org.geotools.data.ows.WMSCapabilities; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.data.wms.WebMapServer; import org.geotools.factory.Hints; import org.geotools.feature.AttributeTypeBuilder; import org.geotools.feature.FeatureTypes; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.geometry.GeneralEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.gml2.GML; import org.geotools.jdbc.JDBCDataStore; import org.geotools.jdbc.VirtualTable; import org.geotools.referencing.CRS; import org.geotools.styling.Style; import org.geotools.util.SoftValueHashMap; import org.geotools.util.logging.Logging; import org.geotools.xml.Schemas; import org.opengis.coverage.grid.GridCoverage; import org.opengis.coverage.grid.GridCoverageReader; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.GeometryDescriptor; import org.opengis.feature.type.PropertyDescriptor; import org.opengis.filter.Filter; import org.opengis.referencing.FactoryException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; import org.springframework.context.ApplicationContext; import org.vfny.geoserver.global.GeoServerFeatureLocking; import org.vfny.geoserver.global.GeoserverDataDirectory; import org.vfny.geoserver.util.DataStoreUtils; /** * Provides access to resources such as datastores, coverage readers, and * feature types. * <p> * * </p> * * @author Justin Deoliveira, The Open Planning Project * */ public class ResourcePool { private static final String PROJECTION_POLICY_SEPARATOR = "_pp_"; /** * Hint to specify if reprojection should occur while loading a * resource. */ public static Hints.Key REPROJECT = new Hints.Key(Boolean.class); /** * Hint to specify additional joined attributes when loading a feature type */ public static Hints.Key JOINS = new Hints.Key(List.class); /** logging */ static Logger LOGGER = Logging.getLogger("org.geoserver.catalog"); static Class VERSIONING_FS = null; static Class GS_VERSIONING_FS = null; static { try { // only support versioning if on classpath VERSIONING_FS = Class.forName("org.geotools.data.VersioningFeatureSource"); GS_VERSIONING_FS = Class.forName("org.vfny.geoserver.global.GeoServerVersioningFeatureSource"); } catch (ClassNotFoundException e) { //fall through } } /** * Default number of hard references */ static int FEATURETYPE_CACHE_SIZE_DEFAULT = 100; private static final String IMAGE_PYRAMID = "ImagePyramid"; private static final String IMAGE_MOSAIC = "ImageMosaic"; Catalog catalog; Map<String, CoordinateReferenceSystem> crsCache; Map<String, DataAccess> dataStoreCache; Map<String, FeatureType> featureTypeCache; Map<String, List<AttributeTypeInfo>> featureTypeAttributeCache; Map<String, WebMapServer> wmsCache; Map<String, GridCoverageReader> coverageReaderCache; Map<CoverageHintReaderKey, GridCoverageReader> hintCoverageReaderCache; Map<StyleInfo, Style> styleCache; List<Listener> listeners; ThreadPoolExecutor coverageExecutor; CatalogRepository repository; /** * Creates a new instance of the resource pool. */ public static ResourcePool create(Catalog catalog) { return create(catalog, null); } /** * Creates a new instance of the resource pool explicitly supplying the application * context. */ public static ResourcePool create(Catalog catalog, ApplicationContext appContext) { //look for an implementation in spring context ResourcePool pool = appContext == null ? GeoServerExtensions.bean(ResourcePool.class) : GeoServerExtensions.bean(ResourcePool.class, appContext); if (pool == null) { pool = new ResourcePool(); } pool.setCatalog(catalog); return pool; } protected ResourcePool() { crsCache = createCrsCache(); dataStoreCache = createDataStoreCache(); featureTypeCache = createFeatureTypeCache(FEATURETYPE_CACHE_SIZE_DEFAULT); featureTypeAttributeCache = createFeatureTypeAttributeCache(FEATURETYPE_CACHE_SIZE_DEFAULT); coverageReaderCache = createCoverageReaderCache(); hintCoverageReaderCache = createHintCoverageReaderCache(); wmsCache = createWmsCache(); styleCache = createStyleCache(); listeners = new CopyOnWriteArrayList<Listener>(); } /** * Creates the resource pool. * <p> * Client code should use {@link ResourcePool#create(Catalog)} instead of calling * this constructor directly. * </p> */ protected ResourcePool(Catalog catalog) { this(); setCatalog(catalog); } public Catalog getCatalog() { return catalog; } public void setCatalog(Catalog catalog) { this.catalog = catalog; this.repository = new CatalogRepository(catalog); catalog.removeListeners(CacheClearingListener.class); catalog.addListener(new CacheClearingListener()); } /** * Returns the cache for {@link CoordinateReferenceSystem} objects. * <p> * The cache key is the CRS identifier (see {@link #getCRS(String)}) for allowable forms. * </p> * <p> * The concrete Map implementation is determined by {@link #createCrsCache()}. * </p> */ public Map<String, CoordinateReferenceSystem> getCrsCache() { return crsCache; } protected Map<String, CoordinateReferenceSystem> createCrsCache() { return new HashMap<String, CoordinateReferenceSystem>(); } /** * Returns the cache for {@link DataAccess} objects. * <p> * The cache key is the corresponding DataStoreInfo id ({@link CatalogInfo#getId()}). * </p> * <p> * The concrete Map implementation is determined by {@link #createDataStoreCache()}. * </p> */ public Map<String, DataAccess> getDataStoreCache() { return dataStoreCache; } protected Map<String, DataAccess> createDataStoreCache() { return new DataStoreCache(); } /** * Returns the cache for {@link FeatureType} objects. * <p> * The cache key is the corresponding FeatureTypeInfo id ({@link CatalogInfo#getId()}. * </p> * <p> * The concrete Map implementation is determined by {@link #createFeatureTypeCache(int)}. * </p> */ public Map<String, FeatureType> getFeatureTypeCache() { return featureTypeCache; } protected Map<String, FeatureType> createFeatureTypeCache(int size) { // for each feature type we cache two versions, one with the projection policy applied, one // without it return new FeatureTypeCache(size * 2); } /** * Returns the cache for {@link AttributeTypeInfo} objects for a particular feature type. * <p> * The cache key is the corresponding FeatureTypeInfo id ({@link CatalogInfo#getId()}. * </p> * <p> * The concrete Map implementation is determined by {@link #createFeatureTypeAttributeCache(int)} * </p> */ public Map<String, List<AttributeTypeInfo>> getFeatureTypeAttributeCache() { return featureTypeAttributeCache; } protected Map<String, List<AttributeTypeInfo>> createFeatureTypeAttributeCache(int size) { // for each feature type we cache two versions, one with the projection policy applied, one // without it return new FeatureTypeAttributeCache(size * 2); } /** * Returns the cache for {@link GridCoverageReader} objects for a particular coverage. * <p> * The cache key is the corresponding Coverage id ({@link CatalogInfo#getId()}. * </p> * <p> * The concrete Map implementation is determined by {@link #createCoverageReaderCache()} * </p> */ public Map<String, GridCoverageReader> getCoverageReaderCache() { return coverageReaderCache; } protected Map<String, GridCoverageReader> createCoverageReaderCache() { return new CoverageReaderCache(); } /** * Returns the cache for {@link GridCoverageReader} objects for a particular coverage hint. * <p> * The concrete Map implementation is determined by {@link #createHintCoverageReaderCache()} * </p> */ public Map<CoverageHintReaderKey, GridCoverageReader> getHintCoverageReaderCache() { return hintCoverageReaderCache; } protected Map<CoverageHintReaderKey, GridCoverageReader> createHintCoverageReaderCache() { return new CoverageHintReaderCache(); } /** * Returns the cache for {@link Style} objects for a particular style. * <p> * The concrete Map implementation is determined by {@link #createStyleCache()} * </p> */ public Map<StyleInfo, Style> getStyleCache() { return styleCache; } protected Map<StyleInfo, Style> createStyleCache() { return new HashMap<StyleInfo, Style>(); } /** * Returns the cache for {@link WebMapServer} objects for a particular {@link WMSStoreInfo}. * <p> * The cache key is the corresponding {@link WMSStoreInfo} id ({@link CatalogInfo#getId()}. * </p> * <p> * The concrete Map implementation is determined by {@link #createWmsCache()} * </p> */ public Map<String, WebMapServer> getWmsCache() { return wmsCache; } protected Map<String, WebMapServer> createWmsCache() { return new WMSCache(); } /** * Sets the size of the feature type cache. * <p> * A warning that calling this method will blow away the existing cache. * </p> */ public void setFeatureTypeCacheSize(int featureTypeCacheSize) { synchronized (this) { featureTypeCache.clear(); featureTypeCache = createFeatureTypeCache(featureTypeCacheSize); featureTypeAttributeCache.clear(); featureTypeAttributeCache = createFeatureTypeAttributeCache(featureTypeCacheSize); } } /** * Sets the size of the feature type cache. * <p> * A warning that calling this method will blow away the existing cache. * </p> */ public void setCoverageExecutor(ThreadPoolExecutor coverageExecutor) { synchronized (this) { this.coverageExecutor = coverageExecutor; } } /** * Adds a pool listener. */ public void addListener(Listener l) { listeners.add(l); } /** * Removes a pool listener. */ public void removeListener(Listener l) { listeners.remove(l); } /** * Returns a {@link CoordinateReferenceSystem} object based on its identifier * caching the result. * <p> * The <tt>srsName</tt> parameter should have one of the forms: * <ul> * <li>EPSG:XXXX * <li>http://www.opengis.net/gml/srs/epsg.xml#XXXX * <li>urn:x-ogc:def:crs:EPSG:XXXX * </ul> * OR be something parsable by {@link CRS#decode(String)}. * </p> * @param srsName The coordinate reference system identifier. * * @throws IOException In the event the srsName can not be parsed or leads * to an exception in the underlying call to CRS.decode. */ public CoordinateReferenceSystem getCRS(String srsName) throws IOException { if (srsName == null) return null; CoordinateReferenceSystem crs = crsCache.get(srsName); if (crs == null) { synchronized (crsCache) { crs = crsCache.get(srsName); if (crs == null) { try { crs = CRS.decode(srsName); crsCache.put(srsName, crs); } catch (Exception e) { throw (IOException) new IOException().initCause(e); } } } } return crs; } /** * Returns the datastore factory used to create underlying resources for a datastore. * <p> * This method first uses {@link DataStoreInfo#getType()} to obtain the datastore. In the * event of a failure it falls back on {@link DataStoreInfo#getConnectionParameters()}. * </p> * @param info The data store metadata. * * @return The datastore factory, or null if no such factory could be found, or the factory * is not available. * * @throws IOException Any I/O errors. */ public DataAccessFactory getDataStoreFactory(DataStoreInfo info) throws IOException { DataAccessFactory factory = null; if (info.getType() != null) { factory = DataStoreUtils.aquireFactory(info.getType()); } if (factory == null && info.getConnectionParameters() != null) { factory = DataStoreUtils.aquireFactory(getParams(info.getConnectionParameters(), GeoserverDataDirectory.getGeoserverDataDirectory().getCanonicalPath())); } return factory; } /** * Returns the underlying resource for a datastore, caching the result. * <p> * In the result of the resource not being in the cache {@link DataStoreInfo#getConnectionParameters()} * is used to connect to it. * </p> * @param info the data store metadata. * * @throws IOException Any errors that occur connecting to the resource. */ public DataAccess<? extends FeatureType, ? extends Feature> getDataStore(DataStoreInfo info) throws IOException { DataAccess<? extends FeatureType, ? extends Feature> dataStore = null; try { String id = info.getId(); dataStore = (DataAccess<? extends FeatureType, ? extends Feature>) dataStoreCache.get(id); if (dataStore == null) { synchronized (dataStoreCache) { dataStore = (DataAccess<? extends FeatureType, ? extends Feature>) dataStoreCache.get(id); if (dataStore == null) { //create data store Map<String, Serializable> connectionParameters = info.getConnectionParameters(); //call this methdo to execute the hack which recognizes // urls which are relative to the data directory // TODO: find a better way to do this connectionParameters = DataStoreUtils.getParams(connectionParameters, null); // obtain the factory DataAccessFactory factory = null; try { factory = getDataStoreFactory(info); } catch (IOException e) { throw new IOException("Failed to find the datastore factory for " + info.getName() + ", did you forget to install the store extension jar?"); } Param[] params = factory.getParametersInfo(); //ensure that the namespace parameter is set for the datastore if (!connectionParameters.containsKey("namespace") && params != null) { //if we grabbed the factory, check that the factory actually supports // a namespace parameter, if we could not get the factory, assume that // it does boolean supportsNamespace = true; supportsNamespace = false; for (Param p : params) { if ("namespace".equalsIgnoreCase(p.key)) { supportsNamespace = true; break; } } if (supportsNamespace) { WorkspaceInfo ws = info.getWorkspace(); NamespaceInfo ns = info.getCatalog().getNamespaceByPrefix(ws.getName()); if (ns == null) { ns = info.getCatalog().getDefaultNamespace(); } if (ns != null) { connectionParameters.put("namespace", ns.getURI()); } } } // see if the store has a repository param, if so, pass the one wrapping // the store if (params != null) { for (Param p : params) { if (Repository.class.equals(p.getType())) { connectionParameters.put(p.getName(), repository); } } } dataStore = DataStoreUtils.getDataAccess(connectionParameters); if (dataStore == null) { /* * Preserve DataStore retyping behaviour by calling * DataAccessFinder.getDataStore after the call to * DataStoreUtils.getDataStore above. * * TODO: DataAccessFinder can also find DataStores, and when retyping is * supported for DataAccess, we can use a single mechanism. */ dataStore = DataAccessFinder.getDataStore(connectionParameters); } if (dataStore == null) { throw new NullPointerException( "Could not acquire data access '" + info.getName() + "'"); } // cache only if the id is not null, no need to cache the stores // returned from un-saved DataStoreInfo objects (it would be actually // harmful, NPE when trying to dispose of them) if (id != null) { dataStoreCache.put(id, dataStore); } } } } return dataStore; } catch (Exception e) { // if anything goes wrong we have to clean up the store anyways if (dataStore != null) { try { dataStore.dispose(); } catch (Exception ex) { // fine, we had to try } } if (e instanceof IOException) { throw (IOException) e; } else { throw (IOException) new IOException().initCause(e); } } } /** * Get Connect params. * * <p> * This is used to smooth any relative path kind of issues for any file * URLS or directory. This code should be expanded to deal with any other context * sensitve isses dataStores tend to have. * </p> * * @return DOCUMENT ME! * * @task REVISIT: cache these? */ public static Map getParams(Map m, String baseDir) { Map params = Collections.synchronizedMap(new HashMap(m)); for (Iterator i = params.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); String key = (String) entry.getKey(); Object value = entry.getValue(); //TODO: this code is a pretty big hack, using the name to // determine if the key is a url, could be named something else // and still be a url if ((key != null) && key.matches(".* *url") && value instanceof String) { String path = (String) value; if (path.startsWith("file:")) { File fixedPath = GeoserverDataDirectory.findDataFile(path); entry.setValue(DataUtilities.fileToURL(fixedPath).toExternalForm()); } } else if (value instanceof URL && ((URL) value).getProtocol().equals("file")) { File fixedPath = GeoserverDataDirectory.findDataFile(((URL) value).toString()); entry.setValue(DataUtilities.fileToURL(fixedPath)); } else if ((key != null) && key.equals("directory") && value instanceof String) { String path = (String) value; //if a url is used for a directory (for example property store), convert it to path if (path.startsWith("file:")) { File fixedPath = GeoserverDataDirectory.findDataFile((String) value); entry.setValue(fixedPath.toString()); } } } return params; } /** * Clears the cached resource for a data store. * * @param info The data store metadata. */ public void clear(DataStoreInfo info) { dataStoreCache.remove(info.getId()); } public List<AttributeTypeInfo> getAttributes(FeatureTypeInfo info) throws IOException { //first check the feature type itself // workaround for GEOS-3294, upgrading from 2.0 data directory, // simply ignore any stored attributes // Also check if the bindings has been set, if it's not set it means we're reloading // an old set of attributes, by forcing them to be reloaded the binding and length // will be added into the info classes if (info.getAttributes() != null && !info.getAttributes().isEmpty() && info.getAttributes().get(0).getBinding() != null) { return info.getAttributes(); } //check the cache List<AttributeTypeInfo> atts = (List<AttributeTypeInfo>) featureTypeAttributeCache.get(info.getId()); if (atts == null) { synchronized (featureTypeAttributeCache) { atts = (List<AttributeTypeInfo>) featureTypeAttributeCache.get(info.getId()); if (atts == null) { //load from feature type atts = loadAttributes(info); //check for a schema override try { handleSchemaOverride(atts, info); } catch (Exception e) { LOGGER.log(Level.WARNING, "Error occured applying schema override for " + info.getName(), e); } // cache attributes only if the id is not null -> the feature type is not new if (info.getId() != null) { featureTypeAttributeCache.put(info.getId(), atts); } } } } return atts; } public List<AttributeTypeInfo> loadAttributes(FeatureTypeInfo info) throws IOException { List<AttributeTypeInfo> attributes = new ArrayList(); FeatureType ft = getFeatureType(info); for (PropertyDescriptor pd : ft.getDescriptors()) { AttributeTypeInfo att = catalog.getFactory().createAttribute(); att.setFeatureType(info); att.setName(pd.getName().getLocalPart()); att.setMinOccurs(pd.getMinOccurs()); att.setMaxOccurs(pd.getMaxOccurs()); att.setNillable(pd.isNillable()); att.setBinding(pd.getType().getBinding()); int length = FeatureTypes.getFieldLength((AttributeDescriptor) pd); if (length > 0) { att.setLength(length); } attributes.add(att); } return attributes; } void handleSchemaOverride(List<AttributeTypeInfo> atts, FeatureTypeInfo ft) throws IOException { GeoServerDataDirectory dd = new GeoServerDataDirectory(catalog.getResourceLoader()); File schemaFile = dd.findSuppResourceFile(ft, "schema.xsd"); if (schemaFile == null) { schemaFile = dd.findSuppLegacyResourceFile(ft, "schema.xsd"); if (schemaFile == null) { //check for the old style schema.xml File oldSchemaFile = dd.findSuppResourceFile(ft, "schema.xml"); if (oldSchemaFile == null) { oldSchemaFile = dd.findSuppLegacyResourceFile(ft, "schema.xml"); } if (oldSchemaFile != null) { schemaFile = new File(oldSchemaFile.getParentFile(), "schema.xsd"); BufferedWriter out = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(schemaFile))); out.write("<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'"); out.write(" xmlns:gml='http://www.opengis.net/gml'"); out.write(">"); FileInputStream fis = null; try { fis = new FileInputStream(oldSchemaFile); IOUtils.copy(fis, out); } finally { IOUtils.closeQuietly(fis); } out.write("</xs:schema>"); out.flush(); out.close(); } } } if (schemaFile != null) { //TODO: farm this schema loading stuff to some utility class //parse the schema + generate attributes from that List locators = Arrays.asList(GML.getInstance().createSchemaLocator()); XSDSchema schema = null; try { schema = Schemas.parse(schemaFile.getAbsolutePath(), locators, null); } catch (Exception e) { LOGGER.warning("Unable to parse " + schemaFile.getAbsolutePath() + "." + " Falling back on native feature type"); } if (schema != null) { XSDTypeDefinition type = null; for (Iterator e = schema.getElementDeclarations().iterator(); e.hasNext();) { XSDElementDeclaration element = (XSDElementDeclaration) e.next(); if (ft.getName().equals(element.getName())) { type = element.getTypeDefinition(); break; } } if (type == null) { for (Iterator t = schema.getTypeDefinitions().iterator(); t.hasNext();) { XSDTypeDefinition typedef = (XSDTypeDefinition) t.next(); if ((ft.getName() + "_Type").equals(typedef.getName())) { type = typedef; break; } } } if (type != null) { List children = Schemas.getChildElementDeclarations(type, true); for (Iterator<AttributeTypeInfo> i = atts.iterator(); i.hasNext();) { AttributeTypeInfo at = i.next(); boolean found = false; for (Iterator c = children.iterator(); c.hasNext();) { XSDElementDeclaration ce = (XSDElementDeclaration) c.next(); if (at.getName().equals(ce.getName())) { found = true; if (ce.getContainer() instanceof XSDParticle) { XSDParticle part = (XSDParticle) ce.getContainer(); at.setMinOccurs(part.getMinOccurs()); at.setMaxOccurs(part.getMaxOccurs()); } break; } } if (!found) { i.remove(); } } } } } } /** * Returns the underlying resource for a feature type, caching the result. * <p> * In the event that the resource is not in the cache the associated data store * resource is loaded, and the feature type resource obtained. During loading * the underlying feature type resource is "wrapped" to take into account * feature type name aliasing and reprojection. * </p> * @param info The feature type metadata. * * @throws IOException Any errors that occure while loading the resource. */ public FeatureType getFeatureType(FeatureTypeInfo info) throws IOException { return getFeatureType(info, true); } FeatureType getFeatureType(FeatureTypeInfo info, boolean handleProjectionPolicy) throws IOException { boolean cacheable = isCacheable(info); String key = getFeatureTypeInfoKey(info, handleProjectionPolicy); FeatureType ft = (FeatureType) featureTypeCache.get(key); if (ft == null || !cacheable) { synchronized (featureTypeCache) { ft = (FeatureType) featureTypeCache.get(key); if (ft == null || !cacheable) { //grab the underlying feature type DataAccess<? extends FeatureType, ? extends Feature> dataAccess = getDataStore(info.getStore()); // sql view handling VirtualTable vt = null; String vtName = null; if (dataAccess instanceof JDBCDataStore && info.getMetadata() != null && (info.getMetadata() .get(FeatureTypeInfo.JDBC_VIRTUAL_TABLE) instanceof VirtualTable)) { JDBCDataStore jstore = (JDBCDataStore) dataAccess; vt = info.getMetadata().get(FeatureTypeInfo.JDBC_VIRTUAL_TABLE, VirtualTable.class); if (!cacheable) { // use a highly random name, we don't want to actually add the // virtual table to the store as this feature type is not cacheable, // it is "dirty" or un-saved. The renaming below will take care // of making the user see the actual name final String[] typeNames = jstore.getTypeNames(); do { vtName = UUID.randomUUID().toString(); } while (Arrays.asList(typeNames).contains(vtName)); // try adding the vt and see if that works jstore.addVirtualTable(new VirtualTable(vtName, vt)); ft = jstore.getSchema(vtName); } else { vtName = vt.getName(); if (!jstore.getVirtualTables().containsValue(vt)) { jstore.addVirtualTable(vt); } ft = jstore.getSchema(vt.getName()); } } else { ft = dataAccess.getSchema(info.getQualifiedNativeName()); } // TODO: support reprojection for non-simple FeatureType if (ft instanceof SimpleFeatureType) { SimpleFeatureType sft = (SimpleFeatureType) ft; //create the feature type so it lines up with the "declared" schema SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder(); tb.setName(info.getName()); tb.setNamespaceURI(info.getNamespace().getURI()); if (info.getAttributes() == null || info.getAttributes().isEmpty()) { //take this to mean just load all native for (PropertyDescriptor pd : ft.getDescriptors()) { if (!(pd instanceof AttributeDescriptor)) { continue; } AttributeDescriptor ad = (AttributeDescriptor) pd; if (handleProjectionPolicy) { ad = handleDescriptor(ad, info); } tb.add(ad); } } else { //only load native attributes configured for (AttributeTypeInfo att : info.getAttributes()) { String attName = att.getName(); //load the actual underlying attribute type PropertyDescriptor pd = ft.getDescriptor(attName); if (pd == null || !(pd instanceof AttributeDescriptor)) { throw new IOException("the SimpleFeatureType " + info.getPrefixedName() + " does not contains the configured attribute " + attName + ". Check your schema configuration"); } AttributeDescriptor ad = (AttributeDescriptor) pd; ad = handleDescriptor(ad, info); tb.add((AttributeDescriptor) ad); } } ft = tb.buildFeatureType(); } // end special case for SimpleFeatureType if (cacheable) { featureTypeCache.put(key, ft); } else if (vtName != null) { JDBCDataStore jstore = (JDBCDataStore) dataAccess; jstore.removeVirtualTable(vtName); } } } } return ft; } private String getFeatureTypeInfoKey(FeatureTypeInfo info, boolean handleProjectionPolicy) { return info.getId() + PROJECTION_POLICY_SEPARATOR + handleProjectionPolicy; } /** * Returns true if this object is saved in the catalog and not a modified proxy. We don't want to * cache the result of computations made against a dirty object, nor the ones made against an * object that still haven't been saved * @param info * @return */ boolean isCacheable(CatalogInfo info) { // saved? if (info.getId() == null) { return false; } // dirty? if (Proxy.isProxyClass(info.getClass())) { Object invocationHandler = Proxy.getInvocationHandler(info); if (invocationHandler instanceof ModificationProxy && ((ModificationProxy) invocationHandler).isDirty()) { return false; } } return true; } /* * Helper method which overrides geometric attributes based on the reprojection policy. */ AttributeDescriptor handleDescriptor(AttributeDescriptor ad, FeatureTypeInfo info) { // force the user specified CRS if the data has no CRS, or reproject it // if necessary if (ad instanceof GeometryDescriptor) { GeometryDescriptor old = (GeometryDescriptor) ad; try { //if old has no crs, change the projection handlign policy // to be the declared boolean rebuild = false; if (old.getCoordinateReferenceSystem() == null) { //(JD) TODO: this is kind of wierd... we should at least // log something here, and this is not thread safe!! if (info.getProjectionPolicy() != ProjectionPolicy.FORCE_DECLARED) { // modify the actual type info if possible, not the modification // proxy around it if (Proxy.isProxyClass(info.getClass())) { FeatureTypeInfo inner = ModificationProxy.unwrap(info); inner.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); } else { info.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED); } } rebuild = true; } else { ProjectionPolicy projPolicy = info.getProjectionPolicy(); if (projPolicy == ProjectionPolicy.REPROJECT_TO_DECLARED || projPolicy == ProjectionPolicy.FORCE_DECLARED) { rebuild = true; } } if (rebuild) { //rebuild with proper crs AttributeTypeBuilder b = new AttributeTypeBuilder(); b.init(old); b.setCRS(getCRS(info.getSRS())); ad = b.buildDescriptor(old.getLocalName()); } } catch (Exception e) { //log exception } } return ad; } /** * Loads an attribute descriptor from feature type and attribute type metadata. * <p> * This method returns null if the attribute descriptor could not be loaded. * </p> */ public AttributeDescriptor getAttributeDescriptor(FeatureTypeInfo ftInfo, AttributeTypeInfo atInfo) throws Exception { FeatureType featureType = getFeatureType(ftInfo); if (featureType != null) { for (PropertyDescriptor pd : featureType.getDescriptors()) { if (pd instanceof AttributeDescriptor) { AttributeDescriptor ad = (AttributeDescriptor) pd; if (atInfo.getName().equals(ad.getLocalName())) { return ad; } } } } return null; } /** * Clears a feature type resource from the cache. * * @param info The feature type metadata. */ public void clear(FeatureTypeInfo info) { featureTypeCache.remove(getFeatureTypeInfoKey(info, true)); featureTypeCache.remove(getFeatureTypeInfoKey(info, false)); featureTypeAttributeCache.remove(info.getId()); } /** * Loads the feature source for a feature type. * <p> * The <tt>hints</tt> parameter is used to control how the feature source is * loaded. An example is using the {@link #REPROJECT} hint to control if the * resulting feature source is reprojected or not. * </p> * @param info The feature type info. * @param hints Any hints to take into account while loading the feature source, * may be <code>null</code>. * * @throws IOException Any errors that occur while loading the feature source. */ public FeatureSource<? extends FeatureType, ? extends Feature> getFeatureSource(FeatureTypeInfo info, Hints hints) throws IOException { DataAccess<? extends FeatureType, ? extends Feature> dataAccess = getDataStore(info.getStore()); // TODO: support aliasing (renaming), reprojection, versioning, and locking for DataAccess if (!(dataAccess instanceof DataStore)) { return dataAccess.getFeatureSource(info.getQualifiedName()); } DataStore dataStore = (DataStore) dataAccess; SimpleFeatureSource fs; // sql view handling if (dataStore instanceof JDBCDataStore && info.getMetadata() != null && info.getMetadata().containsKey(FeatureTypeInfo.JDBC_VIRTUAL_TABLE)) { VirtualTable vt = (VirtualTable) info.getMetadata().get(FeatureTypeInfo.JDBC_VIRTUAL_TABLE); JDBCDataStore jstore = (JDBCDataStore) dataStore; if (!jstore.getVirtualTables().containsValue(vt)) { jstore.addVirtualTable(vt); } } // // aliasing and type mapping // final String typeName = info.getNativeName(); final String alias = info.getName(); final SimpleFeatureType nativeFeatureType = dataStore.getSchema(typeName); final SimpleFeatureType renamedFeatureType = (SimpleFeatureType) getFeatureType(info, false); if (!typeName.equals(alias) || DataUtilities.compare(nativeFeatureType, renamedFeatureType) != 0) { // rename and retype as necessary fs = RetypingFeatureSource.getRetypingSource(dataStore.getFeatureSource(typeName), renamedFeatureType); } else { //normal case fs = dataStore.getFeatureSource(info.getQualifiedName()); } // // reprojection // Boolean reproject = Boolean.TRUE; if (hints != null) { if (hints.get(REPROJECT) != null) { reproject = (Boolean) hints.get(REPROJECT); } } //get the reprojection policy ProjectionPolicy ppolicy = info.getProjectionPolicy(); //if projection policy says to reproject, but calling code specified hint // not to, respect hint if (ppolicy == ProjectionPolicy.REPROJECT_TO_DECLARED && !reproject) { ppolicy = ProjectionPolicy.NONE; } List<AttributeTypeInfo> attributes = info.attributes(); if (attributes == null || attributes.isEmpty()) { return fs; } else { CoordinateReferenceSystem resultCRS = null; GeometryDescriptor gd = fs.getSchema().getGeometryDescriptor(); CoordinateReferenceSystem nativeCRS = gd != null ? gd.getCoordinateReferenceSystem() : null; if (ppolicy == ProjectionPolicy.NONE && nativeCRS != null) { resultCRS = nativeCRS; } else { resultCRS = getCRS(info.getSRS()); } // make sure we create the appropriate schema, with the right crs // we checked above we are using DataStore/SimpleFeature/SimpleFeatureType (DSSFSFT) SimpleFeatureType schema = (SimpleFeatureType) getFeatureType(info); try { if (!CRS.equalsIgnoreMetadata(resultCRS, schema.getCoordinateReferenceSystem())) schema = FeatureTypes.transform(schema, resultCRS); } catch (Exception e) { throw new DataSourceException("Problem forcing CRS onto feature type", e); } // // versioning // try { // only support versioning if on classpath if (VERSIONING_FS != null && GS_VERSIONING_FS != null && VERSIONING_FS.isAssignableFrom(fs.getClass())) { //class implements versioning, reflectively create the versioning wrapper try { Method m = GS_VERSIONING_FS.getMethod("create", VERSIONING_FS, SimpleFeatureType.class, Filter.class, CoordinateReferenceSystem.class, int.class); return (FeatureSource) m.invoke(null, fs, schema, info.getFilter(), resultCRS, info.getProjectionPolicy().getCode()); } catch (Exception e) { throw new DataSourceException("Creation of a versioning wrapper failed", e); } } } catch (ClassCastException e) { //fall through } //joining, check for join hint which requires us to create a shcema with some additional // attributes if (hints != null && hints.containsKey(JOINS)) { List<Join> joins = (List<Join>) hints.get(JOINS); SimpleFeatureTypeBuilder typeBuilder = new SimpleFeatureTypeBuilder(); typeBuilder.init(schema); for (Join j : joins) { String attName = j.getAlias() != null ? j.getAlias() : j.getTypeName(); typeBuilder.add(attName, SimpleFeature.class); } schema = typeBuilder.buildFeatureType(); } //return a normal return GeoServerFeatureLocking.create(fs, schema, info.getFilter(), resultCRS, info.getProjectionPolicy().getCode()); } } /** * Returns a coverage reader, caching the result. * * @param info The coverage metadata. * @param hints Hints to use when loading the coverage, may be <code>null</code>. * * @throws IOException Any errors that occur loading the reader. */ @SuppressWarnings("deprecation") public GridCoverageReader getGridCoverageReader(CoverageStoreInfo info, Hints hints) throws IOException { return getGridCoverageReader(info, (String) null, hints); } /** * Returns a coverage reader, caching the result. * * @param info The coverage metadata. * @param hints Hints to use when loading the coverage, may be <code>null</code>. * * @throws IOException Any errors that occur loading the reader. */ @SuppressWarnings("deprecation") public GridCoverageReader getGridCoverageReader(CoverageStoreInfo info, String coverageName, Hints hints) throws IOException { final AbstractGridFormat gridFormat = info.getFormat(); if (gridFormat == null) { throw new IOException("Could not find the raster plugin for format " + info.getType()); } // look into the cache GridCoverageReader reader = null; Object key; if (hints != null && info.getId() != null) { // expand the hints if necessary final String formatName = gridFormat.getName(); if (formatName.equalsIgnoreCase(IMAGE_MOSAIC) || formatName.equalsIgnoreCase(IMAGE_PYRAMID)) { if (coverageExecutor != null) { if (hints != null) { // do not modify the caller hints hints = new Hints(hints); hints.add(new RenderingHints(Hints.EXECUTOR_SERVICE, coverageExecutor)); } else { hints = new Hints(new RenderingHints(Hints.EXECUTOR_SERVICE, coverageExecutor)); } } } key = new CoverageHintReaderKey(info.getId(), hints); reader = (GridCoverage2DReader) hintCoverageReaderCache.get(key); } else { key = info.getId(); if (key != null) { reader = (GridCoverageReader) coverageReaderCache.get(key); } } // if not found in cache, create it if (reader == null) { synchronized (hints != null ? hintCoverageReaderCache : coverageReaderCache) { if (key != null) { if (hints != null) { reader = (GridCoverageReader) hintCoverageReaderCache.get(key); } else { reader = (GridCoverageReader) coverageReaderCache.get(key); } } if (reader == null) { ///////////////////////////////////////////////////////// // // Getting coverage reader using the format and the real path. // // ///////////////////////////////////////////////////////// final String url = info.getURL(); final File obj = GeoserverDataDirectory.findDataFile(url); // In case no File is returned, provide the original String url final Object input = obj != null ? obj : url; // readers might change the provided hints, pass down a defensive copy reader = gridFormat.getReader(input, new Hints(hints)); if (reader == null) { throw new IOException("Failed to create reader from " + url + " and hints " + hints); } if (key != null) { if (hints != null) { hintCoverageReaderCache.put((CoverageHintReaderKey) key, reader); } else { coverageReaderCache.put((String) key, reader); } } } } } // wrap it if we are dealing with a multi-coverage reader if (coverageName != null) { // force the result to work against a single coverage, so that the OGC service portion of // GeoServer does not need to be updated to the multicoverage stuff // (we might want to introduce a hint later for code that really wants to get the // multi-coverage reader) return SingleGridCoverage2DReader.wrap((GridCoverage2DReader) reader, coverageName); } else { return (GridCoverage2DReader) reader; } } /** * Clears any cached readers for the coverage. */ public void clear(CoverageStoreInfo info) { String storeId = info.getId(); coverageReaderCache.remove(storeId); HashSet<CoverageHintReaderKey> keys = new HashSet<CoverageHintReaderKey>(hintCoverageReaderCache.keySet()); for (CoverageHintReaderKey key : keys) { if (key.id != null && key.id.equals(storeId)) { hintCoverageReaderCache.remove(key); } } } /** * Loads a grid coverage. * <p> * * </p> * * @param info The grid coverage metadata. * @param envelope The section of the coverage to load. * @param hints Hints to use while loading the coverage. * * @throws IOException Any errors that occur loading the coverage. */ @SuppressWarnings("deprecation") public GridCoverage getGridCoverage(CoverageInfo info, ReferencedEnvelope env, Hints hints) throws IOException { final GridCoverageReader reader = getGridCoverageReader(info.getStore(), hints); if (reader == null) { return null; } return getGridCoverage(info, reader, env, hints); } /** * Loads a grid coverage. * <p> * * </p> * * @param info The grid coverage metadata. * @param envelope The section of the coverage to load. * @param hints Hints to use while loading the coverage. * * @throws IOException Any errors that occur loading the coverage. */ @SuppressWarnings("deprecation") public GridCoverage getGridCoverage(CoverageInfo info, GridCoverageReader reader, ReferencedEnvelope env, Hints hints) throws IOException { ReferencedEnvelope coverageBounds; try { coverageBounds = info.boundingBox(); } catch (Exception e) { throw (IOException) new IOException("unable to calculate coverage bounds").initCause(e); } GeneralEnvelope envelope = null; if (env == null) { envelope = new GeneralEnvelope(coverageBounds); } else { envelope = new GeneralEnvelope(env); } // ///////////////////////////////////////////////////////// // // Do we need to proceed? // I need to check the requested envelope in order to see if the // coverage we ask intersect it otherwise it is pointless to load it // since its reader might return null; // ///////////////////////////////////////////////////////// final CoordinateReferenceSystem sourceCRS = envelope.getCoordinateReferenceSystem(); CoordinateReferenceSystem destCRS; try { destCRS = info.getCRS(); } catch (Exception e) { final IOException ioe = new IOException("unable to determine coverage crs"); ioe.initCause(e); throw ioe; } if (!CRS.equalsIgnoreMetadata(sourceCRS, destCRS)) { // get a math transform MathTransform transform; try { transform = CRS.findMathTransform(sourceCRS, destCRS, true); } catch (FactoryException e) { final IOException ioe = new IOException("unable to determine coverage crs"); ioe.initCause(e); throw ioe; } // transform the envelope if (!transform.isIdentity()) { try { envelope = CRS.transform(transform, envelope); } catch (TransformException e) { throw (IOException) new IOException("error occured transforming envelope").initCause(e); } } } // just do the intersection since envelope.intersect(coverageBounds); if (envelope.isEmpty()) { return null; } envelope.setCoordinateReferenceSystem(destCRS); // ///////////////////////////////////////////////////////// // // Reading the coverage // // ///////////////////////////////////////////////////////// GridCoverage gc = reader .read(CoverageUtils.getParameters(reader.getFormat().getReadParameters(), info.getParameters())); if ((gc == null) || !(gc instanceof GridCoverage2D)) { throw new IOException("The requested coverage could not be found."); } return gc; } /** * Returns the format for a coverage. * <p> * The format is inferred from {@link CoverageStoreInfo#getType()} * </p> * @param info The coverage metadata. * * @return The format, or null. */ @SuppressWarnings("deprecation") public AbstractGridFormat getGridCoverageFormat(CoverageStoreInfo info) { final int length = CoverageStoreUtils.formats.length; for (int i = 0; i < length; i++) { if (CoverageStoreUtils.formats[i].getName().equals(info.getType())) { return (AbstractGridFormat) CoverageStoreUtils.formats[i]; } } return null; } /** * Returns the {@link WebMapServer} for a {@link WMSStoreInfo} object * @param info The WMS configuration * @throws IOException */ public WebMapServer getWebMapServer(WMSStoreInfo info) throws IOException { try { String id = info.getId(); WebMapServer wms = (WebMapServer) wmsCache.get(id); if (wms == null) { synchronized (wmsCache) { wms = (WebMapServer) wmsCache.get(id); if (wms == null) { HTTPClient client = getHTTPClient(info); String capabilitiesURL = info.getCapabilitiesURL(); URL serverURL = new URL(capabilitiesURL); wms = new WebMapServer(serverURL, client); wmsCache.put(id, wms); } } } return wms; } catch (IOException ioe) { throw ioe; } catch (Exception e) { throw (IOException) new IOException().initCause(e); } } private HTTPClient getHTTPClient(WMSStoreInfo info) { String capabilitiesURL = info.getCapabilitiesURL(); // check for mock bindings. Since we are going to run this code in production as well, // guard it so that it only triggers if the MockHttpClientProvider has any active binding if (TestHttpClientProvider.testModeEnabled() && capabilitiesURL.startsWith(TestHttpClientProvider.MOCKSERVER)) { HTTPClient client = TestHttpClientProvider.get(capabilitiesURL); return client; } HTTPClient client; if (info.isUseConnectionPooling()) { client = new MultithreadedHttpClient(); if (info.getMaxConnections() > 0) { int maxConnections = info.getMaxConnections(); MultithreadedHttpClient mtClient = (MultithreadedHttpClient) client; mtClient.setMaxConnections(maxConnections); } } else { client = new SimpleHttpClient(); } String username = info.getUsername(); String password = info.getPassword(); int connectTimeout = info.getConnectTimeout(); int readTimeout = info.getReadTimeout(); client.setUser(username); client.setPassword(password); client.setConnectTimeout(connectTimeout); client.setReadTimeout(readTimeout); return client; } /** * Locates and returns a WMS {@link Layer} based on the configuration stored in WMSLayerInfo * @param info * @return */ public Layer getWMSLayer(WMSLayerInfo info) throws IOException { // check which actual name we have to use String name = info.getName(); if (info.getNativeName() != null) { name = info.getNativeName(); } WMSCapabilities caps = info.getStore().getWebMapServer(null).getCapabilities(); for (Layer layer : caps.getLayerList()) { if (name.equals(layer.getName())) { return layer; } } throw new IOException("Could not find layer " + info.getName() + " in the server capabilitiles document"); } /** * Clears the cached resource for a web map server */ public void clear(WMSStoreInfo info) { wmsCache.remove(info.getId()); } /** * Returns a style resource, caching the result. * <p> * The resource is loaded by parsing {@link StyleInfo#getFilename()} as an * SLD. * </p> * @param info The style metadata. * * @throws IOException Any parsing errors. */ public Style getStyle(StyleInfo info) throws IOException { Style style = styleCache.get(info); if (style == null) { synchronized (styleCache) { style = styleCache.get(info); if (style == null) { //JD: it is important that we call the SLDParser(File) constructor because // if not the sourceURL will not be set which will mean it will fail to //resolve relative references to online resources File styleFile = dataDir().findStyleSldFile(info); if (styleFile == null) { throw new IOException("No such file: " + info.getFilename()); } style = Styles.style(Styles.parse(styleFile, null, info.getSLDVersion())); //set the name of the style to be the name of hte style metadata // remove this when wms works off style info style.setName(info.getName()); styleCache.put(info, style); } } } return style; } /** * Clears a style resource from the cache. * * @param info The style metadata. */ public void clear(StyleInfo info) { styleCache.remove(info); } /** * Reads a raw style from persistence. * * @param style The configuration for the style. * * @return A reader for the style. */ public BufferedReader readStyle(StyleInfo style) throws IOException { File styleFile = dataDir().findStyleSldFile(style); if (styleFile == null) { throw new IOException("No such file: " + style.getFilename()); } return new BufferedReader(new InputStreamReader(new FileInputStream(styleFile))); } /** * Serializes a style to configuration. * * @param info The configuration for the style. * @param style The style object. * */ public void writeStyle(StyleInfo info, Style style) throws IOException { writeStyle(info, style, false); } /** * Serializes a style to configuration optionally formatting the style when writing it. * * @param info The configuration for the style. * @param style The style object. * @param format Whether to format the style */ public void writeStyle(StyleInfo info, Style style, boolean format) throws IOException { synchronized (styleCache) { File styleFile = dataDir().findOrCreateStyleSldFile(info); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(styleFile)); try { Styles.encode(Styles.sld(style), info.getSLDVersion(), format, out); clear(info); } finally { out.close(); } } } /** * Writes a raw style to configuration. * * @param style The configuration for the style. * @param in input stream representing the raw a style. * */ public void writeStyle(StyleInfo style, InputStream in) throws IOException { synchronized (styleCache) { File styleFile = dataDir().findOrCreateStyleSldFile(style); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(styleFile)); try { IOUtils.copy(in, out); out.flush(); clear(style); } finally { out.close(); } } } /** * Deletes a style from the configuration. * * @param style The configuration for the style. * @param purge Whether to delete the file from disk. * */ public void deleteStyle(StyleInfo style, boolean purgeFile) throws IOException { synchronized (styleCache) { if (purgeFile) { File styleFile = dataDir().findStyleSldFile(style); if (styleFile != null && styleFile.exists()) { styleFile.delete(); } } } } GeoServerDataDirectory dataDir() { return new GeoServerDataDirectory(catalog.getResourceLoader()); } /** * Disposes all cached resources. * */ public void dispose() { crsCache.clear(); dataStoreCache.clear(); featureTypeCache.clear(); featureTypeAttributeCache.clear(); coverageReaderCache.clear(); hintCoverageReaderCache.clear(); wmsCache.clear(); styleCache.clear(); listeners.clear(); } /** * Base class for all the resource caches, ensures type safety and provides * an easier way to handle with resource disposal * @author Andrea Aime * * @param <K> * @param <V> */ abstract class CatalogResourceCache<K, V> extends SoftValueHashMap<K, V> { public CatalogResourceCache() { this(100); } public CatalogResourceCache(int hardReferences) { super(hardReferences); super.cleaner = new ValueCleaner() { @Override public void clean(Object key, Object object) { dispose((K) key, (V) object); } }; } @Override public V remove(Object key) { V object = super.remove(key); if (object != null) { dispose((K) key, (V) object); } return object; } @Override public void clear() { for (Entry entry : entrySet()) { try { dispose((K) entry.getKey(), (V) entry.getValue()); } catch (Exception e) { LOGGER.log(Level.WARNING, "Error dispoing entry: " + entry, e); } } super.clear(); } protected abstract void dispose(K key, V object); } class FeatureTypeCache extends CatalogResourceCache<String, FeatureType> { public FeatureTypeCache(int maxSize) { super(maxSize); } protected void dispose(String key, FeatureType featureType) { String id = key.substring(0, key.indexOf(PROJECTION_POLICY_SEPARATOR)); FeatureTypeInfo info = catalog.getFeatureType(id); LOGGER.info("Disposing feature type '" + info.getName() + "'"); fireDisposed(info, featureType); } } class DataStoreCache extends CatalogResourceCache<String, DataAccess> { protected void dispose(String id, DataAccess da) { DataStoreInfo info = catalog.getDataStore(id); String name = null; if (info != null) { name = info.getName(); LOGGER.info("Disposing datastore '" + name + "'"); fireDisposed(info, da); } try { da.dispose(); } catch (Exception e) { LOGGER.warning("Error occured disposing datastore '" + name + "'"); LOGGER.log(Level.FINE, "", e); } } } class CoverageReaderCache extends CatalogResourceCache<String, GridCoverageReader> { protected void dispose(String id, GridCoverageReader reader) { CoverageStoreInfo info = catalog.getCoverageStore(id); if (info != null) { String name = info.getName(); LOGGER.info("Disposing coverage store '" + name + "'"); fireDisposed(info, reader); } try { reader.dispose(); } catch (Exception e) { LOGGER.warning("Error occured disposing coverage reader '" + info.getName() + "'"); LOGGER.log(Level.FINE, "", e); } } } class CoverageHintReaderCache extends CatalogResourceCache<CoverageHintReaderKey, GridCoverageReader> { protected void dispose(CoverageHintReaderKey key, GridCoverageReader reader) { CoverageStoreInfo info = catalog.getCoverageStore(key.id); if (info != null) { String name = info.getName(); LOGGER.info("Disposing coverage store '" + name + "'"); fireDisposed(info, reader); } try { reader.dispose(); } catch (Exception e) { LOGGER.warning("Error occured disposing coverage reader '" + info.getName() + "'"); LOGGER.log(Level.FINE, "", e); } } } /** * The key in the {@link CoverageHintReaderCache} * * @author Andrea Aime - GeoSolutions */ public static class CoverageHintReaderKey { String id; Hints hints; public CoverageHintReaderKey(String id, Hints hints) { this.id = id; this.hints = hints; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((hints == null) ? 0 : hints.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CoverageHintReaderKey other = (CoverageHintReaderKey) obj; if (hints == null) { if (other.hints != null) return false; } else if (!hints.equals(other.hints)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } } class FeatureTypeAttributeCache extends CatalogResourceCache<String, List<AttributeTypeInfo>> { FeatureTypeAttributeCache(int size) { super(size); } @Override protected void dispose(String key, List<AttributeTypeInfo> object) { // nothing to do actually } } class WMSCache extends CatalogResourceCache<String, WebMapServer> { @Override protected void dispose(String key, WebMapServer object) { // nothing to do } } /** * Listens to catalog events clearing cache entires when resources are modified. */ public class CacheClearingListener extends CatalogVisitorAdapter implements CatalogListener { public void handleAddEvent(CatalogAddEvent event) { } public void handleModifyEvent(CatalogModifyEvent event) { } public void handlePostModifyEvent(CatalogPostModifyEvent event) { event.getSource().accept(this); } public void handleRemoveEvent(CatalogRemoveEvent event) { event.getSource().accept(this); } public void reloaded() { } @Override public void visit(DataStoreInfo dataStore) { clear(dataStore); } @Override public void visit(CoverageStoreInfo coverageStore) { clear(coverageStore); } @Override public void visit(FeatureTypeInfo featureType) { clear(featureType); } @Override public void visit(WMSStoreInfo wmsStore) { clear(wmsStore); } @Override public void visit(StyleInfo style) { clear(style); } } void fireDisposed(DataStoreInfo dataStore, DataAccess da) { for (Listener l : listeners) { try { l.disposed(dataStore, da); } catch (Throwable t) { LOGGER.warning("Resource pool listener threw error"); LOGGER.log(Level.INFO, t.getLocalizedMessage(), t); } } } void fireDisposed(FeatureTypeInfo featureType, FeatureType ft) { for (Listener l : listeners) { try { l.disposed(featureType, ft); } catch (Throwable t) { LOGGER.warning("Resource pool listener threw error"); LOGGER.log(Level.INFO, t.getLocalizedMessage(), t); } } } void fireDisposed(CoverageStoreInfo coverageStore, GridCoverageReader gcr) { for (Listener l : listeners) { try { l.disposed(coverageStore, gcr); } catch (Throwable t) { LOGGER.warning("Resource pool listener threw error"); LOGGER.log(Level.INFO, t.getLocalizedMessage(), t); } } } /** * Listener for resource pool events. * * @author Justin Deoliveira, OpenGeo * */ public static interface Listener { /** * Event fired when a data store is evicted from the resource pool. */ void disposed(DataStoreInfo dataStore, DataAccess da); /** * Event fired when a coverage store is evicted from the resource pool. */ void disposed(CoverageStoreInfo coverageStore, GridCoverageReader gcr); /** * Event fired when a feature type is evicted from the resource pool. */ void disposed(FeatureTypeInfo featureType, FeatureType ft); } }