/*
* 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.Set;
import javax.measure.unit.Unit;
import org.geotools.factory.BufferedFactory;
import org.geotools.factory.Hints;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.util.NameFactory;
import org.geotools.util.ObjectCache;
import org.geotools.util.ObjectCaches;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CompoundCRS;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.DerivedCRS;
import org.opengis.referencing.crs.EngineeringCRS;
import org.opengis.referencing.crs.GeocentricCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.ImageCRS;
import org.opengis.referencing.crs.ProjectedCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.cs.CylindricalCS;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.PolarCS;
import org.opengis.referencing.cs.SphericalCS;
import org.opengis.referencing.cs.TimeCS;
import org.opengis.referencing.cs.VerticalCS;
import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.datum.DatumAuthorityFactory;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.EngineeringDatum;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.ImageDatum;
import org.opengis.referencing.datum.PrimeMeridian;
import org.opengis.referencing.datum.TemporalDatum;
import org.opengis.referencing.datum.VerticalDatum;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
import org.opengis.util.GenericName;
import org.opengis.util.InternationalString;
/**
* An authority factory that consults (a possibly shared) cache before generating
* content itself.
* </p>
* The behavior of the {@code createFoo(String)} methods first looks if a
* previously created object exists for the given code. If such an object
* exists, it is returned directly. The testing of the cache is synchronized and
* may block if the referencing object is under construction.
* <p>
* If the object is not yet created, the definition is delegated to the
* appropriate the {@code generateFoo} method and the result is cached for
* next time.
* <p>
* This object is responsible for using a provided {{ReferencingObjectCache}}.
* </p>
*
* @since 2.4
*
*
* @source $URL$
* @version $Id: BufferedAuthorityDecorator.java 26038 2007-06-27 01:58:12Z
* jgarnett $
* @author Jody Garnett
*/
public abstract class AbstractCachedAuthorityFactory extends AbstractAuthorityFactory
implements AuthorityFactory, CRSAuthorityFactory, CSAuthorityFactory,
DatumAuthorityFactory, CoordinateOperationAuthorityFactory,
BufferedFactory {
/**
* Cache to be used for referencing objects defined by this authority. Please note that this
* cache may be shared!
* <p>
* Your cache may grow to considerable size during actual use; in addition to storing
* CoordinateReferenceSystems (by code); it will also store all the component parts
* (each under its own code), along with MathTransformations between two
* CoordinateReferenceSystems. So even if you are only planning on working with
* 50 CoordianteReferenceSystems please keep in mind that you will need larger
* cache size in order to prevent a bottleneck.
*/
protected ObjectCache cache;
/**
* The findCache is used to store search results; often match a "raw"
* CoordinateReferenceSystem created from WKT (as the key) with a
* "real" CoordianteReferenceSystem as defined by this authority.
*/
ObjectCache findCache;
/**
* A container of the "real factories" actually used to construct objects.
*/
protected ReferencingFactoryContainer factories;
/**
* Constructs an instance making use of the default cache.
*
* @param factory
* The factory to cache. Can not be {@code null}.
*/
protected AbstractCachedAuthorityFactory( int priority ) {
this( priority, ObjectCaches.create("weak", 50 ), ReferencingFactoryContainer.instance( null ) );
}
/**
* Constructs an instance making use of the default cache.
*
* @param factory
* The factory to cache. Can not be {@code null}.
*/
protected AbstractCachedAuthorityFactory( int priority, Hints hints ) {
this( priority, ObjectCaches.create( hints ), ReferencingFactoryContainer.instance( hints ) );
}
/**
* Constructs an instance making use of the indicated cache.
* <p>
* 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 AbstractCachedAuthorityFactory(int priority, ObjectCache cache, ReferencingFactoryContainer container) {
super( priority );
this.factories = container;
this.cache = cache;
this.findCache = ObjectCaches.create("weak",0);
}
final void completeHints() {
hints.put(Hints.DATUM_AUTHORITY_FACTORY, this );
hints.put(Hints.CS_AUTHORITY_FACTORY, this );
hints.put(Hints.CRS_AUTHORITY_FACTORY, this );
hints.put(Hints.COORDINATE_OPERATION_AUTHORITY_FACTORY, this );
}
//
// Utility Methods and Cache Care and Feeding
//
protected String toKey(String code) {
return ObjectCaches.toKey( getAuthority(), code);
}
/**
* Trims the authority scope, if present. For example if this factory is an EPSG authority
* factory and the specified code start with the "EPSG:" prefix, then the prefix is removed.
* Otherwise, the string is returned unchanged (except for leading and trailing spaces).
*
* @param code The code to trim.
* @return The code without the authority scope.
*/
protected String trimAuthority(String code) {
/*
* IMPLEMENTATION NOTE: This method is overridden in
* PropertyAuthorityFactory. If the implementation below is modified, it
* is probably worth revisiting the overridden method as well.
*/
code = code.trim();
final GenericName name = NameFactory.create(code);
final GenericName scope = name.scope().name();
if (scope == null) {
return code;
}
if (Citations.identifierMatches(getAuthority(), scope.toString())) {
return name.tip().toString().trim();
}
return code;
}
/**
* Creates an exception for an unknown authority code. This convenience method is provided
* for implementation of {@code createXXX} methods.
*
* @param type The GeoAPI interface that was to be created
* (e.g. {@code CoordinateReferenceSystem.class}).
* @param code The unknown authority code.
* @param cause The cause of this error, or {@code null}.
* @return An exception initialized with an error message built
* from the specified informations.
*/
protected NoSuchAuthorityCodeException noSuchAuthorityCode(final Class type,
final String code,
final ClassCastException cause)
{
final NoSuchAuthorityCodeException exception = noSuchAuthorityCode(type, code);
exception.initCause(cause);
return exception;
}
//
// AuthorityFactory
//
public abstract Citation getAuthority();
public Set getAuthorityCodes(Class type) throws FactoryException{
Set codes = (Set) cache.get(type);
if (codes == null) {
try {
cache.writeLock(type);
codes = (Set) cache.peek(type);
if (codes == null) {
codes = generateAuthorityCodes(type);
cache.put(type, codes);
}
} finally {
cache.writeUnLock(type);
}
}
return codes;
}
protected abstract Set generateAuthorityCodes( Class type ) throws FactoryException;
public abstract InternationalString getDescriptionText(String code) throws FactoryException;
public IdentifiedObject createObject(String code) throws FactoryException {
final String key = toKey(code);
IdentifiedObject obj = (IdentifiedObject) cache.get(key);
if (obj == null) {
try {
cache.writeLock(key);
obj = (IdentifiedObject) cache.peek(key);
if (obj == null) {
obj = generateObject(code);
cache.put(key, obj);
}
} finally {
cache.writeUnLock(key);
}
}
return obj;
}
protected abstract IdentifiedObject generateObject( String code ) throws FactoryException;
//
// CRSAuthority
//
/**
* Creates a 3D coordinate reference system from a code.
*
* @param code Value allocated by authority.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
public CompoundCRS createCompoundCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (CompoundCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(CompoundCRS.class, code, exception);
}
}
public CoordinateReferenceSystem createCoordinateReferenceSystem(String code)
throws FactoryException {
final String key = toKey(code);
CoordinateReferenceSystem crs = (CoordinateReferenceSystem) cache
.get(key);
if (crs == null) {
try {
cache.writeLock(key);
crs = (CoordinateReferenceSystem) cache.peek(key);
if (crs == null) {
crs = generateCoordinateReferenceSystem(code);
cache.put(key, crs);
}
} finally {
cache.writeUnLock(key);
}
}
return crs;
}
protected abstract CoordinateReferenceSystem generateCoordinateReferenceSystem(String code) throws FactoryException;
public DerivedCRS createDerivedCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (DerivedCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(DerivedCRS.class, code, exception);
}
}
public EngineeringCRS createEngineeringCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (EngineeringCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(EngineeringCRS.class, code, exception);
}
}
public GeocentricCRS createGeocentricCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (GeocentricCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(GeocentricCRS.class, code, exception);
}
}
public GeographicCRS createGeographicCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (GeographicCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(GeographicCRS.class, code, exception);
}
}
public ImageCRS createImageCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (ImageCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(ImageCRS.class, code, exception);
}
}
public ProjectedCRS createProjectedCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (ProjectedCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(ProjectedCRS.class, code, exception);
}
}
public TemporalCRS createTemporalCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (TemporalCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(TemporalCRS.class, code, exception);
}
}
public VerticalCRS createVerticalCRS(final String code) throws FactoryException {
final CoordinateReferenceSystem crs = createCoordinateReferenceSystem(code);
try {
return (VerticalCRS) crs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(VerticalCRS.class, code, exception);
}
}
//
// CSAuthority
//
/**
* Creates a cartesian coordinate system from a code.
* The default implementation invokes
* <code>{@linkplain #createCoordinateSystem createCoordinateSystem}(code)</code>.
*
* @param code Value allocated by authority.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
public CartesianCS createCartesianCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (CartesianCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(CartesianCS.class, code, exception);
}
}
public CoordinateSystem createCoordinateSystem(String code)
throws FactoryException {
final String key = toKey(code);
CoordinateSystem cs = (CoordinateSystem) cache.get(key);
if (cs == null) {
try {
cache.writeLock(key);
cs = (CoordinateSystem) cache.peek(key);
if (cs == null) {
cs = generateCoordinateSystem(code);
cache.put(key, cs);
}
} finally {
cache.writeUnLock(key);
}
}
return cs;
}
protected abstract CoordinateSystem generateCoordinateSystem(String code) throws FactoryException;
// sample implemenation with get/test
public CoordinateSystemAxis createCoordinateSystemAxis(String code)
throws FactoryException {
final String key = toKey(code);
CoordinateSystemAxis axis = (CoordinateSystemAxis) cache.get(key);
if (axis == null) {
try {
cache.writeLock(key);
axis = (CoordinateSystemAxis) cache.peek(key);
if (axis == null) {
axis = generateCoordinateSystemAxis(code);
cache.put(key, axis);
}
} finally {
cache.writeUnLock(key);
}
}
return axis;
}
protected abstract CoordinateSystemAxis generateCoordinateSystemAxis(String code) throws FactoryException;
/**
* The default implementation invokes
* <code>{@linkplain #createCoordinateSystem createCoordinateSystem}(code)</code>.
*
* @param code Value allocated by authority.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the object creation failed for some other reason.
*/
public CylindricalCS createCylindricalCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (CylindricalCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(CylindricalCS.class, code, exception);
}
}
public EllipsoidalCS createEllipsoidalCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (EllipsoidalCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(EllipsoidalCS.class, code, exception);
}
}
public PolarCS createPolarCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (PolarCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(PolarCS.class, code, exception);
}
}
public SphericalCS createSphericalCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (SphericalCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(SphericalCS.class, code, exception);
}
}
public TimeCS createTimeCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (TimeCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(TimeCS.class, code, exception);
}
}
public Unit<?> createUnit(String code) throws FactoryException {
final String key = toKey(code);
Unit<?> unit = (Unit) cache.get(key);
if (unit == null) {
try {
cache.writeLock(key);
unit = (Unit) cache.peek(key);
if (unit == null) {
unit = generateUnit(code);
cache.put(key, unit);
}
} finally {
cache.writeUnLock(key);
}
}
return unit;
}
protected abstract Unit<?> generateUnit(String code) throws FactoryException;
public VerticalCS createVerticalCS(final String code) throws FactoryException {
final CoordinateSystem cs = createCoordinateSystem(code);
try {
return (VerticalCS) cs;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(VerticalCS.class, code, exception);
}
}
//
// DatumAuthorityFactory
//
public Datum createDatum(String code) throws FactoryException {
final String key = toKey(code);
Datum datum = (Datum) cache.get(key);
if (datum == null) {
try {
cache.writeLock(key);
datum = (Datum) cache.peek(key);
if (datum == null) {
datum = generateDatum(code);
cache.put(key, datum);
}
} finally {
cache.writeUnLock(key);
}
}
return datum;
}
protected abstract Datum generateDatum(String code) throws FactoryException;
public Ellipsoid createEllipsoid(String code) throws FactoryException {
final String key = toKey(code);
Ellipsoid ellipsoid = (Ellipsoid) cache.get(key);
if (ellipsoid == null) {
try {
cache.writeLock(key);
ellipsoid = (Ellipsoid) cache.peek(key);
if (ellipsoid == null) {
ellipsoid = generateEllipsoid(code);
cache.put(key, ellipsoid);
}
} finally {
cache.writeUnLock(key);
}
}
return ellipsoid;
}
protected abstract Ellipsoid generateEllipsoid(String code) throws FactoryException;
public EngineeringDatum createEngineeringDatum(final String code) throws FactoryException {
final Datum datum = createDatum(code);
try {
return (EngineeringDatum) datum;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(EngineeringDatum.class, code, exception);
}
}
public GeodeticDatum createGeodeticDatum(final String code) throws FactoryException {
final Datum datum = createDatum(code);
try {
return (GeodeticDatum) datum;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(GeodeticDatum.class, code, exception);
}
}
public ImageDatum createImageDatum(final String code) throws FactoryException {
final Datum datum = createDatum(code);
try {
return (ImageDatum) datum;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(ImageDatum.class, code, exception);
}
}
public PrimeMeridian createPrimeMeridian(String code)
throws FactoryException {
final String key = toKey(code);
PrimeMeridian datum = (PrimeMeridian) cache.get(key);
if (datum == null) {
try {
cache.writeLock(key);
datum = (PrimeMeridian) cache.peek(key);
if (datum == null) {
datum = generatePrimeMeridian(code);
cache.put(key, datum);
}
} finally {
cache.writeUnLock(key);
}
}
return datum;
}
protected abstract PrimeMeridian generatePrimeMeridian(String code) throws FactoryException;
public TemporalDatum createTemporalDatum(final String code) throws FactoryException {
final Datum datum = createDatum(code);
try {
return (TemporalDatum) datum;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(TemporalDatum.class, code, exception);
}
}
public VerticalDatum createVerticalDatum(final String code) throws FactoryException {
final Datum datum = createDatum(code);
try {
return (VerticalDatum) datum;
} catch (ClassCastException exception) {
throw noSuchAuthorityCode(VerticalDatum.class, code, exception);
}
}
public CoordinateOperation createCoordinateOperation(String code)
throws FactoryException {
final String key = toKey(code);
CoordinateOperation operation = (CoordinateOperation) cache.get(key);
if (operation == null) {
try {
cache.writeLock(key);
operation = (CoordinateOperation) cache.peek(key);
if (operation == null) {
operation = generateCoordinateOperation(code);
cache.put(key, operation);
}
} finally {
cache.writeUnLock(key);
}
}
return operation;
}
protected abstract CoordinateOperation generateCoordinateOperation(String code) throws FactoryException;
public synchronized Set/*<CoordinateOperation>*/ createFromCoordinateReferenceSystemCodes(
final String sourceCode, final String targetCode)
throws FactoryException {
final Object key = ObjectCaches.toKey( getAuthority(), sourceCode, targetCode );
Set operations = (Set) cache.get(key);
if (operations == null) {
try {
cache.writeLock(key);
operations = (Set) cache.peek(key);
if (operations == null) {
operations = generateFromCoordinateReferenceSystemCodes( sourceCode, targetCode );
// can we not trust operationAuthority to return us an unmodifiableSet ?
//operations = Collections.unmodifiableSet( operations );
cache.put( key, operations );
}
}
finally {
cache.writeUnLock(key);
}
}
return operations;
}
protected abstract Set generateFromCoordinateReferenceSystemCodes(String sourceCode, String targetCode) throws FactoryException;
/** We will clear out our cache and factories reference
* @throws FactoryException */
public void dispose() throws FactoryException {
this.cache = null;
this.factories = null;
}
/**
* Returns a finder which can be used for looking up unidentified objects.
* The default implementation delegates lookup to the underlying backing
* store and caches the result.
*
* @since 2.4
*/
@Override
public synchronized IdentifiedObjectFinder getIdentifiedObjectFinder(
final Class/*<? extends IdentifiedObject>*/ type) throws FactoryException
{
return new CachedFinder( type );
}
/**
* An implementation of {@link IdentifiedObjectFinder} which delegates
* the work to the underlying backing store and caches the result.
* <p>
* A separate ObjectCache, findCache, is used to store the values created over the course
* of finding. The findCache is set up as a "chain" allowing it to use our cache
* to prevent duplication of effort. In the future this findCache may be shared between
* instances.
* <p>
* <b>Implementation note:</b> 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 CachedFinder extends IdentifiedObjectFinder {
/**
* Creates a finder for the underlying backing store.
*/
CachedFinder(Class type) {
super( AbstractCachedAuthorityFactory.this, type );
}
/**
* 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 {
IdentifiedObject candidate;
candidate = (IdentifiedObject) findCache.get(object);
if (candidate != null) {
return candidate;
}
try {
findCache.writeLock(object); // avoid searching for the same object twice
IdentifiedObject found = super.find(object);
if( found == null) {
return null; // not found
}
candidate = (IdentifiedObject) findCache.peek(object);
if( candidate == null ){
findCache.put(object, found);
return found;
}
else {
return candidate;
}
} finally {
findCache.writeUnLock(object);
}
}
/**
* Returns the identifier for the specified object.
*/
@Override
public String findIdentifier(final IdentifiedObject object) throws FactoryException {
IdentifiedObject candidate;
candidate = (IdentifiedObject) findCache.get(object);
if (candidate != null) {
return getIdentifier(candidate);
}
return super.findIdentifier(object);
}
}
}