/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.referencing.factory; import java.util.Map; import java.util.Set; import java.util.Iterator; import java.util.Collection; import java.util.Collections; import java.util.WeakHashMap; import java.util.logging.LogRecord; import java.util.logging.Level; import javax.measure.unit.Unit; import org.opengis.metadata.extent.Extent; import org.opengis.metadata.citation.Citation; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.FactoryException; import org.opengis.referencing.IdentifiedObject; import org.opengis.referencing.cs.*; import org.opengis.referencing.crs.*; import org.opengis.referencing.datum.*; import org.opengis.referencing.operation.*; import org.opengis.util.InternationalString; import org.geotools.factory.Hints; import org.geotools.factory.BufferedFactory; import org.geotools.util.Utilities; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Loggings; import org.geotools.resources.i18n.LoggingKeys; /** * An authority factory that caches all objects created by an other factory. All * {@code createFoo(String)} methods first looks if a previously created object * exists for the given code. If such an object exists, it is returned. Otherwise, * the object creation is delegated to the {@linkplain AbstractAuthorityFactory authority factory} * specified at creation time, and the result is cached in this buffered factory. *
* Objects are cached by strong references, up to the amount of objects specified at
* construction time. If a greater amount of objects are cached, the oldest ones will
* be retained through a {@linkplain java.lang.ref.WeakReference weak reference} instead
* of a strong one. This means that this buffered factory will continue to returns them
* as long as they are in use somewhere else in the Java virtual machine, but will be
* discarted (and recreated on the fly if needed) otherwise.
*
* @since 2.4
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ThreadedAuthorityFactory extends AbstractAuthorityFactory implements BufferedFactory {
/**
* The default value for {@link #maxStrongReferences}.
*/
static final int DEFAULT_MAX = 20;
/**
* The underlying authority factory. This field may be {@code null} if this object was
* created by the {@linkplain #ThreadedAuthorityFactory(AbstractAuthorityFactory,int)
* package protected constructor}. In this case, the subclass is responsible for creating
* the backing store when {@link DeferredAuthorityFactory#createBackingStore} is invoked.
*
* @see #getBackingStore
* @see DeferredAuthorityFactory#createBackingStore
*/
AbstractAuthorityFactory backingStore;
/**
* The cache for referencing objects.
*/
private final OldReferencingObjectCache objectCache;
/**
* The pool of objects identified by {@link find}.
*/
private final Map
* This constructor is protected because subclasses must declare which of the
* {@link DatumAuthorityFactory}, {@link CSAuthorityFactory}, {@link CRSAuthorityFactory}
* and {@link CoordinateOperationAuthorityFactory} interfaces they choose to implement.
*
* @param factory The factory to cache. Can not be {@code null}.
*/
protected ThreadedAuthorityFactory(final AbstractAuthorityFactory factory) {
this(factory, DEFAULT_MAX);
}
/**
* Constructs an instance wrapping the specified factory. The {@code maxStrongReferences}
* argument specify the maximum number of objects to keep by strong reference. If a greater
* amount of objects are created, then the strong references for the oldest ones are replaced
* by weak references.
*
* This constructor is protected because subclasses must declare which of the
* {@link DatumAuthorityFactory}, {@link CSAuthorityFactory}, {@link CRSAuthorityFactory}
* and {@link CoordinateOperationAuthorityFactory} interfaces they choose to implement.
*
* @param factory The factory to cache. Can not be {@code null}.
* @param maxStrongReferences The maximum number of objects to keep by strong reference.
*/
protected ThreadedAuthorityFactory(AbstractAuthorityFactory factory,
final int maxStrongReferences)
{
super(factory.getPriority());
while (factory instanceof ThreadedAuthorityFactory) {
factory = ((ThreadedAuthorityFactory) factory).backingStore;
}
this.backingStore = factory;
this.objectCache = new OldReferencingObjectCache(maxStrongReferences);
completeHints();
}
/**
* Constructs an instance without initial backing store. This constructor is for subclass
* constructors only. Subclasses are responsible for creating an appropriate backing store
* when the {@link DeferredAuthorityFactory#createBackingStore} method is invoked.
*
* @param priority The priority for this factory, as a number between
* {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
* {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
* @param maxStrongReferences The maximum number of objects to keep by strong reference.
*
* @see DeferredAuthorityFactory#createBackingStore
*/
ThreadedAuthorityFactory(final int priority, final int maxStrongReferences) {
super(priority);
this.objectCache = new OldReferencingObjectCache(maxStrongReferences);
// completeHints() will be invoked by DeferredAuthorityFactory.getBackingStore()
}
/**
* Completes the set of hints according the value currently set in this object. This method
* is invoked by {@code BufferedAuthorityFactory} or by {@code DeferredAuthorityFactory} at
* backing store creation time.
*
* The backing store is of course an important dependency. This method gives a chance
* to {@link org.geotools.factory.FactoryRegistry} to compare the user-requested hints
* (especially {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER}) against the backing store
* hints, by following the dependency declared there.
*
* DON'T FORGET to set those hints to {@code null} when {@link DeferredAuthorityFactory}
* dispose the backing store.
*/
final void completeHints() {
if (backingStore instanceof DatumAuthorityFactory) {
hints.put(Hints.DATUM_AUTHORITY_FACTORY, backingStore);
}
if (backingStore instanceof CSAuthorityFactory) {
hints.put(Hints.CS_AUTHORITY_FACTORY, backingStore);
}
if (backingStore instanceof CRSAuthorityFactory) {
hints.put(Hints.CRS_AUTHORITY_FACTORY, backingStore);
}
if (backingStore instanceof CoordinateOperationAuthorityFactory) {
hints.put(Hints.COORDINATE_OPERATION_AUTHORITY_FACTORY, backingStore);
}
}
/**
* Returns the direct dependencies. The returned list contains the backing store
* specified at construction time, or the exception if it can't be obtained.
*/
@Override
Collection super AuthorityFactory> dependencies() {
Object factory;
try {
factory = getBackingStore();
} catch (FactoryException e) {
factory = e;
}
return Collections.singleton(factory);
}
/**
* Returns the backing store authority factory.
*
* @return The backing store to uses in {@code createXXX(...)} methods.
* @throws FactoryException if the creation of backing store failed.
*/
AbstractAuthorityFactory getBackingStore() throws FactoryException {
if (backingStore == null) {
throw new FactoryException(Errors.format(ErrorKeys.DISPOSED_FACTORY));
}
return backingStore;
}
/**
* Returns {@code true} if this factory is available. The default implementation returns
* {@code false} if no backing store were setup and
* {@link DeferredAuthorityFactory#createBackingStore} throws an exception.
*/
@Override
boolean isAvailable() {
try {
return getBackingStore().isAvailable();
} catch (FactoryNotFoundException exception) {
/*
* The factory is not available. This is error may be normal; it happens
* for example if no gt2-epsg-hsql.jar (or similar JAR) are found in the
* classpath, which is the case for example in GeoServer 1.3. Do not log
* any stack trace, since stack traces suggest more serious errors than
* what we really have here.
*/
} catch (FactoryException exception) {
/*
* The factory creation failed for an other reason, which may be more
* serious. Now it is time to log a warning with a stack trace.
*/
final Citation citation = getAuthority();
final Collection titles = citation.getAlternateTitles();
InternationalString title = citation.getTitle();
if (titles != null) {
for (final Iterator it=titles.iterator(); it.hasNext();) {
/*
* Uses the longuest title instead of the main one. In Geotools
* implementation, the alternate title may contains usefull informations
* like the EPSG database version number and the database engine.
*/
final InternationalString candidate = (InternationalString) it.next();
if (candidate.length() > title.length()) {
title = candidate;
}
}
}
final LogRecord record = Loggings.format(Level.WARNING,
LoggingKeys.UNAVAILABLE_AUTHORITY_FACTORY_$1, title);
record.setSourceClassName(getClass().getName());
record.setSourceMethodName("isAvailable");
record.setThrown(exception);
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
return false;
}
/**
* If this factory is a wrapper for the specified factory that do not add any additional
* {@linkplain #getAuthorityCodes authority codes}, returns {@code true}. This method is
* for {@link FallbackAuthorityFactory} internal use only and should not be public. A
* cheap test without {@link #getBackingStore} invocation is suffisient for our needs.
*/
@Override
boolean sameAuthorityCodes(final AuthorityFactory factory) {
final AbstractAuthorityFactory backingStore = this.backingStore; // Protect from changes.
if (backingStore != null && backingStore.sameAuthorityCodes(factory)) {
return true;
}
return super.sameAuthorityCodes(factory);
}
/**
* Returns the vendor responsible for creating the underlying factory implementation.
*/
@Override
public Citation getVendor() {
return (backingStore!=null) ? backingStore.getVendor() : super.getVendor();
}
/**
* Returns the organization or party responsible for definition and maintenance of the
* underlying database.
*/
public Citation getAuthority() {
return (backingStore!=null) ? backingStore.getAuthority() : null;
}
/**
* Returns a description of the underlying backing store, or {@code null} if unknow.
* This is for example the database software used for storing the data.
*
* @throws FactoryException if a failure occured while fetching the engine description.
*/
@Override
public String getBackingStoreDescription() throws FactoryException {
return getBackingStore().getBackingStoreDescription();
}
/**
* Returns the set of authority codes of the given type. The {@code type}
* argument specify the base class.
*
* @param type The spatial reference objects type.
* @return The set of authority codes for spatial reference objects of the given type.
* If this factory doesn't contains any object of the given type, then this method
* returns an {@linkplain java.util.Collections#EMPTY_SET empty set}.
* @throws FactoryException if access to the underlying database failed.
*/
public Set
* Implementation note: we will create objects using directly the underlying backing
* store, not using the cache. This is because hundred of objects may be created during a
* scan while only one will be typically retained. We don't want to overload the cache with
* every false candidates that we encounter during the scan.
*/
private final class Finder extends IdentifiedObjectFinder.Adapter {
/**
* Creates a finder for the underlying backing store.
*/
Finder(final IdentifiedObjectFinder finder) {
super(finder);
}
/**
* Looks up an object from this authority factory which is equals, ignoring metadata,
* to the specified object. The default implementation performs the same lookup than
* the backing store and caches the result.
*/
@Override
public IdentifiedObject find(final IdentifiedObject object) throws FactoryException {
/*
* Do not synchronize on 'BufferedAuthorityFactory.this'. This method may take a
* while to execute and we don't want to block other threads. The synchronizations
* in the 'create' methods and in the 'findPool' map should be suffisient.
*
* TODO: avoid to search for the same object twice. For now we consider that this
* is not a big deal if the same object is searched twice; it is "just" a
* waste of CPU.
*/
IdentifiedObject candidate;
synchronized (findPool) {
candidate = findPool.get(object);
}
if (candidate == null) {
// Must delegates to 'finder' (not to 'super') in order to take
// advantage of the method overriden by AllAuthoritiesFactory.
candidate = finder.find(object);
if (candidate != null) {
synchronized (findPool) {
findPool.put(object, candidate);
}
}
}
return candidate;
}
/**
* Returns the identifier for the specified object.
*/
@Override
public String findIdentifier(final IdentifiedObject object) throws FactoryException {
IdentifiedObject candidate;
synchronized (findPool) {
candidate = findPool.get(object);
}
if (candidate != null) {
return getIdentifier(candidate);
}
// We don't rely on super-class implementation, because we want to
// take advantage of the method overriden by AllAuthoritiesFactory.
return finder.findIdentifier(object);
}
}
/**
* Releases resources immediately instead of waiting for the garbage collector.
*/
@Override
public synchronized void dispose() throws FactoryException {
if (backingStore != null) {
backingStore.dispose();
backingStore = null;
}
objectCache.clear();
super.dispose();
}
}