blob: ad00040aaeaf189a4d5d9b1a95f29c0ae5a6aefd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing.factory;
import java.util.Set;
import java.util.Map;
import java.util.List;
import java.util.Deque;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.WeakHashMap;
import java.util.IdentityHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.logging.LogRecord;
import java.util.logging.Level;
import java.lang.ref.WeakReference;
import java.lang.ref.PhantomReference;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import javax.measure.Unit;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.ParameterDescriptor;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Debug;
import org.apache.sis.util.Disposable;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.internal.simple.SimpleCitation;
import org.apache.sis.internal.system.ReferenceQueueConsumer;
import org.apache.sis.internal.system.DelayedExecutor;
import org.apache.sis.internal.system.DelayedRunnable;
import org.apache.sis.internal.system.Shutdown;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.util.StandardDateFormat;
import org.apache.sis.util.logging.PerformanceLevel;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.resources.Messages;
/**
* A concurrent authority factory that caches all objects created by another factory.
* All {@code createFoo(String)} methods first check if a previously created object exists for the given code.
* If such object exists, it is returned. Otherwise, the object creation is delegated to another factory given
* by {@link #newDataAccess()} and the result is cached in this factory.
*
* <p>{@code ConcurrentAuthorityFactory} delays the call to {@code newDataAccess()} until first needed,
* and {@linkplain AutoCloseable#close() closes} the factory used as a <cite>Data Access Object</cite>
* (DAO) after some timeout. This approach allows to establish a connection to a database (for example)
* and keep it only for a relatively short amount of time.</p>
*
* <div class="section">Caching strategy</div>
* Objects are cached by strong references, up to the amount of objects specified at construction time.
* If a greater amount of objects are cached, then the oldest ones will be retained through a
* {@linkplain WeakReference weak reference} instead of a strong one.
* This means that this caching factory will continue to return those objects as long as they are in use somewhere
* else in the Java virtual machine, but will be discarded (and recreated on the fly if needed) otherwise.
*
* <div class="section">Multi-threading</div>
* The cache managed by this class is concurrent. However the Data Access Objects (DAO) are assumed non-concurrent.
* If two or more threads are accessing this factory in same time, then two or more Data Access Object instances
* may be created. The maximal amount of instances to create is specified at {@code ConcurrentAuthorityFactory}
* construction time. If more Data Access Object instances are needed, some of the threads will block until an
* instance become available.
*
* <div class="section">Note for subclasses</div>
* This abstract class does not implement any of the {@link DatumAuthorityFactory}, {@link CSAuthorityFactory},
* {@link CRSAuthorityFactory} and {@link CoordinateOperationAuthorityFactory} interfaces.
* Subclasses should select the interfaces that they choose to implement.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 0.8
*
* @param <DAO> the type of factory used as Data Access Object (DAO).
*
* @since 0.7
* @module
*/
public abstract class ConcurrentAuthorityFactory<DAO extends GeodeticAuthorityFactory>
extends GeodeticAuthorityFactory implements AutoCloseable
{
/**
* Duration of data access operations that should be logged, in nanoseconds.
* Any operation that take longer than this amount of time to execute will have a message logged.
* The log level depends on the execution duration as specified in {@link PerformanceLevel}.
*/
private static final long DURATION_FOR_LOGGING = 10_000_000L; // 10 milliseconds.
/**
* Sentinel value when {@link #authority} can not be determined because the data access object
* can not be constructed.
*/
private static final Citation UNAVAILABLE = new SimpleCitation("unavailable");
/**
* The authority, cached after first requested.
*/
private transient volatile Citation authority;
/**
* The {@code createFoo(String)} methods that are <strong>not</strong> overridden in the Data Access Object (DAO).
* This map is created at construction time and should not be modified after construction.
*
* @see #isDefault(Class)
*/
private final Map<Class<?>,Boolean> inherited = new IdentityHashMap<>();
/**
* The pool of cached objects.
*/
private final Cache<Key,Object> cache;
/**
* The pool of objects identified by {@link Finder#find(IdentifiedObject)}.
* Values may be an empty set if an object has been searched but has not been found.
*
* <p>Every access to this pool must be synchronized on {@code findPool}.</p>
*/
private final Map<IdentifiedObject,FindEntry> findPool = new WeakHashMap<>();
/**
* Holds the reference to a Data Access Object used by {@link ConcurrentAuthorityFactory}, together with
* information about its usage. In a mono-thread application, there is typically only one {@code DataAccessRef}
* instance at a given time. However if more than one than one thread are requesting new objects concurrently,
* then many instances may exist for the same {@code ConcurrentAuthorityFactory}.
*
* <p>If the Data Access Object is currently in use, then {@code DataAccessRef} counts how many recursive
* invocations of a {@link #factory} {@code createFoo(String)} method is under way in the current thread.
* This information is used in order to reuse the same factory instead than creating new instances
* when a {@code GeodeticAuthorityFactory} implementation invokes itself indirectly through the
* {@link ConcurrentAuthorityFactory}. This assumes that factory implementations are reentrant.</p>
*
* <p>If the Data Access Object has been released, then {@code DataAccessRef} keep the release timestamp.
* This information is used for prioritize the Data Access Objects to close.</p>
*/
private static final class DataAccessRef<DAO extends GeodeticAuthorityFactory> {
/**
* The factory used for data access.
*/
final DAO factory;
/**
* Incremented on every call to {@link ConcurrentAuthorityFactory#getDataAccess()} and decremented on every
* call to {@link ConcurrentAuthorityFactory#release(String, Class, String)}. When this value reach zero,
* the factory is really released.
*/
int depth;
/**
* The timestamp (<strong>not</strong> relative to epoch) at the time the Data Access Object has been released.
* This timestamp shall be obtained by {@link System#nanoTime()} for consistency with {@link DelayedRunnable}.
*/
long timestamp;
/**
* Creates new Data Access Object information for the given factory.
*/
DataAccessRef(final DAO factory) {
this.factory = factory;
}
/**
* Returns a string representation for debugging purpose only.
*/
@Override
public String toString() {
final String text;
final Number value;
if (depth != 0) {
text = "%s in use at depth %d";
value = depth;
} else {
text = "%s made available %d seconds ago";
value = Math.round((System.nanoTime() - timestamp) / (double) StandardDateFormat.NANOS_PER_SECOND);
}
return String.format(text, Classes.getShortClassName(factory), value);
}
}
/**
* The Data Access Object in use by the current thread.
*/
private final ThreadLocal<DataAccessRef<DAO>> currentDAO = new ThreadLocal<>();
/**
* The Data Access Object instances previously created and released for future reuse.
* Last used factories must be {@linkplain Deque#addLast(Object) added last}.
* This is used as a LIFO stack.
*/
private final Deque<DataAccessRef<DAO>> availableDAOs = new LinkedList<>();
/**
* The amount of Data Access Objects that can still be created. This number is decremented in a block
* synchronized on {@link #availableDAOs} every time a Data Access Object is in use, and incremented
* once released.
*/
private int remainingDAOs;
/**
* {@code true} if the call to {@link #closeExpired()} is scheduled for future execution in the background
* cleaner thread. A value of {@code true} implies that this factory contains at least one active data access.
* However the reciprocal is not true: this field may be set to {@code false} while a DAO is currently in use
* because this field is set to {@code true} only when a worker factory is {@linkplain #release released}.
*
* <p>Note that we can not use {@code !availableDAOs.isEmpty()} as a replacement of {@code isCleanScheduled}
* because the queue is empty if all Data Access Objects are currently in use.</p>
*
* <p>Every access to this field must be performed in a block synchronized on {@link #availableDAOs}.</p>
*
* @see #isCleanScheduled()
*/
private boolean isCleanScheduled;
/**
* The delay of inactivity (in nanoseconds) before to close a Data Access Object.
* Every access to this field must be performed in a block synchronized on {@link #availableDAOs}.
*
* @see #getTimeout(TimeUnit)
*/
private long timeout = 60_000_000_000L; // 1 minute
/**
* The maximal difference between the scheduled time and the actual time in order to perform the factory disposal,
* in nanoseconds. This is used as a tolerance value for possible wait time inaccuracy.
*/
static final long TIMEOUT_RESOLUTION = 200_000_000L; // 0.2 second
/**
* Constructs an instance with a default number of threads and a default number of entries to keep
* by strong references. Note that those default values may change in any future SIS versions based
* on experience gained.
*
* @param dataAccessClass The class of Data Access Object (DAO) created by {@link #newDataAccess()}.
*/
protected ConcurrentAuthorityFactory(Class<DAO> dataAccessClass) {
this(dataAccessClass, 100, 8);
/*
* NOTE: if the default maximum number of Data Access Objects (currently 8) is augmented,
* make sure to augment the number of runner threads in the "StressTest" class to a greater amount.
*/
}
/**
* Constructs an instance with the specified number of entries to keep by strong references.
* If a number of object greater than {@code maxStrongReferences} are created, then the strong references
* for the eldest objects will be replaced by weak references.
*
* @param dataAccessClass the class of Data Access Object (DAO) created by {@link #newDataAccess()}.
* @param maxStrongReferences the maximum number of objects to keep by strong reference.
* @param maxConcurrentQueries the maximal amount of Data Access Objects to use concurrently.
* If more than this amount of threads are querying this {@code ConcurrentAuthorityFactory} concurrently,
* additional threads will be blocked until a Data Access Object become available.
*/
protected ConcurrentAuthorityFactory(final Class<DAO> dataAccessClass,
final int maxStrongReferences, final int maxConcurrentQueries)
{
ArgumentChecks.ensureNonNull("dataAccessClass", dataAccessClass);
ArgumentChecks.ensurePositive("maxStrongReferences", maxStrongReferences);
ArgumentChecks.ensureStrictlyPositive("maxConcurrentQueries", maxConcurrentQueries);
/*
* Detect which methods in the DAO have been overridden.
*/
for (final Method method : dataAccessClass.getMethods()) {
if (method.getDeclaringClass() == GeodeticAuthorityFactory.class && method.getName().startsWith("create")) {
final Class<?>[] p = method.getParameterTypes();
if (p.length == 1 && p[0] == String.class) {
inherited.put(method.getReturnType(), Boolean.TRUE);
}
}
}
/*
* Create a cache allowing key collisions.
* Key collision is usually an error. But in this case we allow them in order to enable recursivity.
* If during the creation of an object the program asks to this ConcurrentAuthorityFactory for the same
* object (using the same key), then the default Cache implementation considers that situation as an
* error unless the above property has been set to 'true'.
*/
remainingDAOs = maxConcurrentQueries;
cache = new Cache<>(20, maxStrongReferences, false);
cache.setKeyCollisionAllowed(true);
/*
* The shutdown hook serves two purposes:
*
* 1) Closes the Data Access Objects when the garbage collector determined
* that this ConcurrentAuthorityFactory is no longer in use.
*
* 2) Closes the Data Access Objects at JVM shutdown time if the application is standalone,
* or when the bundle is uninstalled if running inside an OSGi or Servlet container.
*/
Shutdown.register(new ShutdownHook<>(this));
}
/**
* Returns the number of Data Access Objects available for reuse. This count does not include the
* Data Access Objects that are currently in use. This method is used only for testing purpose.
*
* @see #isCleanScheduled()
*/
@Debug
final int countAvailableDataAccess() {
synchronized (availableDAOs) {
return availableDAOs.size();
}
}
/**
* Creates a factory which will perform the actual geodetic object creation work.
* This method is invoked the first time a {@code createFoo(String)} method is invoked.
* It may also be invoked again if additional factories are needed in different threads,
* or if all factories have been closed after the timeout.
*
* <div class="section">Multi-threading</div>
* This method (but not necessarily the returned factory) needs to be thread-safe;
* {@code ConcurrentAuthorityFactory} does not hold any lock when invoking this method.
* Subclasses are responsible to apply their own synchronization if needed,
* but are encouraged to avoid doing so if possible.
* In addition, implementations should not invoke other {@code ConcurrentAuthorityFactory}
* methods during this method execution in order to avoid never-ending loop.
*
* @return Data Access Object (DAO) to use in {@code createFoo(String)} methods.
* @throws UnavailableFactoryException if the Data Access Object is unavailable because an optional resource is missing.
* @throws FactoryException if the creation of Data Access Object failed for another reason.
*/
protected abstract DAO newDataAccess() throws UnavailableFactoryException, FactoryException;
/**
* Returns a Data Access Object. This method <strong>must</strong>
* be used together with {@link #release(String, Class, String)}
* in a {@code try ... finally} block.
*
* @return Data Access Object (DAO) to use in {@code createFoo(String)} methods.
* @throws FactoryException if the Data Access Object creation failed.
*/
@SuppressWarnings("null")
private DAO getDataAccess() throws FactoryException {
/*
* First checks if the current thread is already using a factory. If yes, we will
* avoid creating new factories on the assumption that factories are reentrant.
*/
DataAccessRef<DAO> usage = currentDAO.get();
if (usage == null) {
synchronized (availableDAOs) {
/*
* If we have reached the maximal amount of Data Access Objects allowed, wait for an instance
* to become available. In theory the 0.2 second timeout is not necessary, but we put it as a
* safety in case we fail to invoke a notify() matching this wait(), for example someone else
* is waiting on this monitor or because the release(…) method threw an exception.
*/
while (remainingDAOs == 0) {
try {
availableDAOs.wait(TIMEOUT_RESOLUTION);
} catch (InterruptedException e) {
// Someone does not want to let us sleep.
throw new FactoryException(e.getLocalizedMessage(), e);
}
}
/*
* Reuse the most recently used factory, if available. If there is no factory available for reuse,
* creates a new one. We do not add it to the queue now; it will be done by the release(…) method.
*/
usage = availableDAOs.pollLast();
remainingDAOs--; // Should be done last when we are sure to not fail.
}
/*
* If there is a need to create a new factory, do that outside the synchronized block because this
* creation may involve a lot of client code. This is better for reducing the dead-lock risk.
* Subclasses are responsible of synchronizing their newDataAccess() method if necessary.
*/
try {
if (usage == null) {
final DAO factory = newDataAccess();
if (factory == null) {
UnavailableFactoryException e = new UnavailableFactoryException(Errors.format(
Errors.Keys.FactoryNotFound_1, GeodeticAuthorityFactory.class));
e.setUnavailableFactory(this);
throw e;
}
usage = new DataAccessRef<>(factory);
}
assert usage.depth == 0 : usage;
usage.timestamp = System.nanoTime();
} catch (Throwable e) {
/*
* If any kind of error occurred, restore the 'remainingDAO' field as if no code were executed.
* This code would not have been needed if we were allowed to decrement 'remainingDAO' only as
* the very last step (when we know that everything else succeed).
* But it needed to be decremented inside the synchronized block.
*/
synchronized (availableDAOs) {
remainingDAOs++;
}
throw e;
}
currentDAO.set(usage);
}
/*
* Increment below is safe even if outside the synchronized block,
* because each thread own exclusively its DataAccessRef instance.
*/
usage.depth++;
return usage.factory;
}
/**
* Releases the Data Access Object previously obtained with {@link #getDataAccess()}.
* This method marks the factory as available for reuse by other threads.
*
* <p>All arguments given to this method are for logging purpose only.</p>
*
* @param caller the caller method, or {@code null} for {@code "create" + type.getSimpleName()}.
* @param type the type of the created object, or {@code null} for performing no logging.
* @param code the code of the created object, or {@code null} if none.
*/
private void release(String caller, final Class<?> type, final String code) {
final DataAccessRef<DAO> usage = currentDAO.get(); // A null value here would be an error in our algorithm.
if (--usage.depth == 0) {
currentDAO.remove();
long time = usage.timestamp;
synchronized (availableDAOs) {
remainingDAOs++; // Must be done first in case an exception happen after this point.
recycle(usage);
availableDAOs.notify(); // We released only one data access, so awake only one thread - not all of them.
time = usage.timestamp - time;
}
/*
* Log only events that take longer than the threshold (e.g. 10 milliseconds).
*/
if (time >= DURATION_FOR_LOGGING && type != null) {
if (caller == null) {
caller = "create".concat(type.getSimpleName());
}
final PerformanceLevel level = PerformanceLevel.forDuration(time, TimeUnit.NANOSECONDS);
final Double duration = time / (double) StandardDateFormat.NANOS_PER_SECOND;
final Messages resources = Messages.getResources(null);
final LogRecord record;
if (code != null) {
record = resources.getLogRecord(level, Messages.Keys.CreateDurationFromIdentifier_3, type, code, duration);
} else {
record = resources.getLogRecord(level, Messages.Keys.CreateDuration_2, type, duration);
}
record.setLoggerName(Loggers.CRS_FACTORY);
Logging.log(getClass(), caller, record);
}
}
assert usage.depth >= 0 : usage;
}
/**
* Pushes the given DAO in the list of objects available for reuse.
*/
private void recycle(final DataAccessRef<DAO> usage) {
usage.timestamp = System.nanoTime();
availableDAOs.addLast(usage);
/*
* If the Data Access Object we just released is the first one, awake the
* cleaner thread which was waiting for an indefinite amount of time.
*/
if (!isCleanScheduled) {
isCleanScheduled = true;
DelayedExecutor.schedule(new CloseTask(usage.timestamp + timeout));
}
}
/**
* {@code true} if the call to {@link #closeExpired()} is scheduled for future execution in the background
* cleaner thread. A value of {@code true} implies that this factory contains at least one active data access.
* However the reciprocal is not true: this field may be set to {@code false} while a DAO is currently in use
* because this field is set to {@code true} only when a worker factory is {@linkplain #release released}.
*
* <p>This method is used only for testing purpose.</p>
*
* @see #countAvailableDataAccess()
*/
@Debug
final boolean isCleanScheduled() {
synchronized (availableDAOs) {
return isCleanScheduled;
}
}
/**
* Confirms that the given factories can be closed. If any factory is still in use,
* it will be removed from that {@code factories} list and re-injected in the {@link #availableDAOs} queue.
*/
private void confirmClose(final List<DAO> factories) {
assert !Thread.holdsLock(availableDAOs);
for (final Iterator<DAO> it = factories.iterator(); it.hasNext();) {
final DAO factory = it.next();
try {
if (canClose(factory)) {
continue;
}
} catch (Exception e) {
unexpectedException("canClose", e);
continue; // Keep the factory on the list of factories to close.
}
// Cancel closing for that factory.
it.remove();
synchronized (availableDAOs) {
recycle(new DataAccessRef<>(factory));
}
}
}
/**
* A task for invoking {@link ConcurrentAuthorityFactory#closeExpired()} after a delay.
*/
private final class CloseTask extends DelayedRunnable {
/**
* Creates a new task to be executed at the given time,
* in nanoseconds relative to {@link System#nanoTime()}.
*/
CloseTask(final long timestamp) {
super(timestamp);
}
/** Invoked when the delay expired. */
@Override public void run() {
closeExpired();
}
}
/**
* Closes the expired Data Access Objects. This method should be invoked from a background task only.
* This method may reschedule the task again for an other execution if it appears that at least one
* Data Access Object was not ready for disposal.
*
* @see #close()
*/
final void closeExpired() {
final List<DAO> factories;
final boolean isEmpty;
synchronized (availableDAOs) {
factories = new ArrayList<>(availableDAOs.size());
final Iterator<DataAccessRef<DAO>> it = availableDAOs.iterator();
final long nanoTime = System.nanoTime();
while (it.hasNext()) {
final DataAccessRef<DAO> dao = it.next();
/*
* Computes how much time we need to wait again before we can close the factory.
* If this time is greater than some arbitrary amount, do not close the factory
* and wait again.
*/
final long nextTime = dao.timestamp + timeout;
if (nextTime - nanoTime > TIMEOUT_RESOLUTION) {
/*
* Found a factory which is not expired. Stop the search,
* since the iteration is expected to be ordered.
*/
DelayedExecutor.schedule(new CloseTask(nextTime));
break;
}
/*
* Found an expired factory. Adds it to the list of
* factories to close and search for other factories.
*/
factories.add(dao.factory);
it.remove();
}
/*
* The DAOs list is empty if all Data Access Objects in the queue have been closed.
* Note that some DAOs may still be in use outside the queue, because the DAOs are
* added to the queue only after completion of their work.
* In the later case, release() will reschedule a new task.
*/
isCleanScheduled = !(isEmpty = availableDAOs.isEmpty());
}
/*
* We must close the factories from outside the synchronized block.
*/
confirmClose(factories);
try {
close(factories);
} catch (Exception exception) {
unexpectedException("closeExpired", exception);
}
/*
* If the queue of Data Access Objects (DAO) become empty, this means that this ConcurrentAuthorityFactory
* has not created new object for a while (at least the amount of time given by the timeout), ignoring any
* request which may be under execution in another thread right now. Reduce the amount of objects retained
* in the cache of IdentifiedObjectFinder.find(…) results by removing all results containing more than one
* element, except for results of CoordinateReferenceSystem and CoordinateOperation lookups. The reason is
* that IdentifiedObjectFinder is almost always used for resolving CoordinateReferenceSystem objects, and
* all other kind of elements in the cache were dependencies searched as a side effect of the CRS search.
* Since we have the result of the CRS search, we often do not need anymore the result of dependency search.
*
* Touching 'findPool' also has the desired side-effect of letting WeakHashMap expunges stale entries.
*/
if (isEmpty) {
synchronized (findPool) {
final Iterator<FindEntry> it = findPool.values().iterator();
while (it.hasNext()) {
if (it.next().cleanup()) {
it.remove();
}
}
}
}
}
/**
* Invoked when an exception occurred while closing a factory and we can not propagate the exception to the user.
* This situation happen when the factories are closed in a background thread. There is not much we can do except
* logging the problem. {@code ConcurrentAuthorityFactory} should be able to continue its work normally since the
* factory that we failed to close will not be used anymore.
*
* @param method the name of the method to report as the source of the problem.
* @param exception the exception that occurred while closing a Data Access Object.
*/
static void unexpectedException(final String method, final Exception exception) {
Logging.unexpectedException(Logging.getLogger(Loggers.CRS_FACTORY),
ConcurrentAuthorityFactory.class, method, exception);
}
/**
* Returns {@code true} if the given Data Access Object (DAO) can be closed. This method is invoked automatically
* after the {@linkplain #getTimeout timeout} if the given DAO has been idle during all that time.
* Subclasses can override this method and return {@code false} if they want to prevent the DAO disposal
* under some circumstances.
*
* <p>The default implementation always returns {@code true}.</p>
*
* @param factory the Data Access Object which is about to be closed.
* @return {@code true} if the given Data Access Object can be closed.
*
* @see #close()
*/
protected boolean canClose(DAO factory) {
return true;
}
/**
* Returns the amount of time that {@code ConcurrentAuthorityFactory} will wait before to close a Data Access Object.
* This delay is measured from the last time the Data Access Object has been used by a {@code createFoo(String)} method.
*
* @param unit the desired unit of measurement for the timeout.
* @return the current timeout in the given unit of measurement.
*/
public long getTimeout(final TimeUnit unit) {
synchronized (availableDAOs) {
return unit.convert(timeout, TimeUnit.NANOSECONDS);
}
}
/**
* Sets a timer for closing the Data Access Object after the specified amount of time of inactivity.
* If a new Data Access Object is needed after the disposal of the last one, then the {@link #newDataAccess()}
* method will be invoked again.
*
* @param delay the delay of inactivity before to close a Data Access Object.
* @param unit the unit of measurement of the given delay.
*/
public void setTimeout(long delay, final TimeUnit unit) {
ArgumentChecks.ensureStrictlyPositive("delay", delay);
delay = unit.toNanos(delay);
synchronized (availableDAOs) {
timeout = delay; // Will be taken in account after the next factory to close.
}
}
/**
* Returns the database or specification that defines the codes recognized by this factory.
* The default implementation performs the following steps:
* <ul>
* <li>Returns the cached value if it exists.</li>
* <li>Otherwise:
* <ol>
* <li>get an instance of the Data Access Object,</li>
* <li>delegate to its {@link GeodeticAuthorityFactory#getAuthority()} method,</li>
* <li>release the Data Access Object,</li>
* <li>cache the result.</li>
* </ol>
* </li>
* </ul>
*
* If this method can not get a Data Access Object (for example because no database connection is available),
* then this method returns {@code null}.
*
* @return the organization responsible for definition of the database, or {@code null} if unavailable.
*/
@Override
public Citation getAuthority() {
Citation c = authority;
if (c == null || c == UNAVAILABLE) try {
final DAO factory = getDataAccess();
try {
/*
* Cache only in case of success. If we failed, we
* will try again next time this method is invoked.
*/
authority = c = factory.getAuthority();
} finally {
release("getAuthority", Citation.class, null);
}
} catch (FactoryException e) {
authority = UNAVAILABLE;
/*
* Use the warning level only on the first failure, then the fine level on all subsequent failures.
* Do not log the stack trace if we failed because of UnavailableFactoryException since it may be
* normal (the EPSG geodetic dataset is optional, even if strongly recommended).
*/
final LogRecord record = new LogRecord(c == null ? Level.WARNING : Level.FINE, e.getLocalizedMessage());
if (!(e instanceof UnavailableFactoryException)) {
record.setThrown(e);
}
record.setLoggerName(Loggers.CRS_FACTORY);
Logging.log(ConcurrentAuthorityFactory.class, "getAuthority", record);
c = null;
}
return c;
}
/**
* Returns the set of authority codes for objects of the given type.
* The default implementation performs the following steps:
* <ol>
* <li>get an instance of the Data Access Object,</li>
* <li>delegate to its {@link GeodeticAuthorityFactory#getAuthorityCodes(Class)} method,</li>
* <li>release the Data Access Object.</li>
* </ol>
*
* @param type the spatial reference objects type (e.g. {@code ProjectedCRS.class}).
* @return the set of authority codes for spatial reference objects of the given type.
* If this factory does not contains any object of the given type, then this method returns an empty set.
* @throws FactoryException if access to the underlying database failed.
*/
@Override
public Set<String> getAuthorityCodes(final Class<? extends IdentifiedObject> type) throws FactoryException {
final DAO factory = getDataAccess();
try {
return factory.getAuthorityCodes(type);
/*
* In the particular case of EPSG factory, the returned Set maintains a live connection to the database.
* But it still okay to release the factory anyway because our implementation will really close
* the connection only when the iteration is over or the iterator has been garbage-collected.
*/
} finally {
release("getAuthorityCodes", Set.class, null);
}
}
/**
* Gets a description of the object corresponding to a code.
* The default implementation performs the following steps:
* <ol>
* <li>get an instance of the Data Access Object,</li>
* <li>delegate to its {@link GeodeticAuthorityFactory#getDescriptionText(String)} method,</li>
* <li>release the Data Access Object.</li>
* </ol>
*
* @param code value allocated by authority.
* @return a description of the object, or {@code null} if the object
* corresponding to the specified {@code code} has no description.
* @throws NoSuchAuthorityCodeException if the specified {@code code} was not found.
* @throws FactoryException if the query failed for some other reason.
*/
@Override
public InternationalString getDescriptionText(final String code)
throws NoSuchAuthorityCodeException, FactoryException
{
final DAO factory = getDataAccess();
try {
return factory.getDescriptionText(code);
} finally {
release("getDescriptionText", InternationalString.class, code);
}
}
/**
* Returns {@code true} if the Data Access Object (DAO) does not provide a {@code create} method for the
* given type of object. The intent is to decide if the caller should delegate to the DAO or delegate to
* a more generic method of this class (e.g. {@code createCoordinateReferenceSystem(String)} instead of
* {@code createGeographicCRS(String)}) in order to give to {@code ConcurrentAuthorityFactory} a chance
* to reuse a value presents in the cache.
*/
private boolean isDefault(final Class<?> type) {
return inherited.containsKey(type);
}
/**
* Returns a code equivalent to the given code but with unnecessary elements eliminated.
* The normalized code is used as the key in the cache, and is also the code which will
* be passed to the {@linkplain #newDataAccess() Data Access Object} (DAO).
*
* <p>The default implementation performs the following steps:</p>
* <ol>
* <li>Removes the namespace if presents. For example if the {@linkplain #getCodeSpaces() codespace}
* is EPSG and the given code starts with the {@code "EPSG:"} prefix, then that prefix is removed.</li>
* <li>Removes leading and trailing spaces.</li>
* </ol>
*
* Subclasses can override this method for performing a different normalization work.
* It is okay to return internal codes completely different than the given codes,
* provided that the Data Access Objects will understand those internal codes.
*
* @param code the code to normalize.
* @return the normalized code.
* @throws FactoryException if an error occurred while normalizing the given code.
*/
protected String normalizeCode(String code) throws FactoryException {
return trimNamespace(code);
}
/**
* Returns an arbitrary object from a code.
* The default implementation performs the following steps:
* <ul>
* <li>Returns the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise:
* <ol>
* <li>get an instance of the Data Access Object,</li>
* <li>delegate to its {@link GeodeticAuthorityFactory#createObject(String)} method,</li>
* <li>release the Data Access Object,</li>
* <li>cache the result.</li>
* </ol>
* </li>
* </ul>
*
* @return the object for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public IdentifiedObject createObject(final String code) throws FactoryException {
return create(AuthorityFactoryProxy.OBJECT, code);
}
/**
* Returns an arbitrary coordinate reference system from a code.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCoordinateReferenceSystem(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCoordinateReferenceSystem(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CoordinateReferenceSystem createCoordinateReferenceSystem(final String code) throws FactoryException {
if (isDefault(CoordinateReferenceSystem.class)) {
return super.createCoordinateReferenceSystem(code);
}
return create(AuthorityFactoryProxy.CRS, code);
}
/**
* Returns a 2- or 3-dimensional coordinate reference system based on an ellipsoidal approximation of the geoid.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createGeographicCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createGeographicCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public GeographicCRS createGeographicCRS(final String code) throws FactoryException {
if (isDefault(GeographicCRS.class)) {
return super.createGeographicCRS(code);
}
return create(AuthorityFactoryProxy.GEOGRAPHIC_CRS, code);
}
/**
* Returns a 3-dimensional coordinate reference system with the origin at the approximate centre of mass of the earth.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createGeocentricCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createGeocentricCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public GeocentricCRS createGeocentricCRS(final String code) throws FactoryException {
if (isDefault(GeocentricCRS.class)) {
return super.createGeocentricCRS(code);
}
return create(AuthorityFactoryProxy.GEOCENTRIC_CRS, code);
}
/**
* Returns a 2-dimensional coordinate reference system used to approximate the shape of the earth on a planar surface.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createProjectedCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createProjectedCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public ProjectedCRS createProjectedCRS(final String code) throws FactoryException {
if (isDefault(ProjectedCRS.class)) {
return super.createProjectedCRS(code);
}
return create(AuthorityFactoryProxy.PROJECTED_CRS, code);
}
/**
* Returns a 1-dimensional coordinate reference system used for recording heights or depths.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createVerticalCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createVerticalCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public VerticalCRS createVerticalCRS(final String code) throws FactoryException {
if (isDefault(VerticalCRS.class)) {
return super.createVerticalCRS(code);
}
return create(AuthorityFactoryProxy.VERTICAL_CRS, code);
}
/**
* Returns a 1-dimensional coordinate reference system used for the recording of time.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createTemporalCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createTemporalCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public TemporalCRS createTemporalCRS(final String code) throws FactoryException {
if (isDefault(TemporalCRS.class)) {
return super.createTemporalCRS(code);
}
return create(AuthorityFactoryProxy.TEMPORAL_CRS, code);
}
/**
* Returns a CRS describing the position of points through two or more independent coordinate reference systems.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCompoundCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCompoundCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CompoundCRS createCompoundCRS(final String code) throws FactoryException {
if (isDefault(CompoundCRS.class)) {
return super.createCompoundCRS(code);
}
return create(AuthorityFactoryProxy.COMPOUND_CRS, code);
}
/**
* Returns a CRS that is defined by its coordinate conversion from another CRS (not by a datum).
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createDerivedCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createDerivedCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public DerivedCRS createDerivedCRS(final String code) throws FactoryException {
if (isDefault(DerivedCRS.class)) {
return super.createDerivedCRS(code);
}
return create(AuthorityFactoryProxy.DERIVED_CRS, code);
}
/**
* Returns a 1-, 2- or 3-dimensional contextually local coordinate reference system.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createEngineeringCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createEngineeringCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public EngineeringCRS createEngineeringCRS(final String code) throws FactoryException {
if (isDefault(EngineeringCRS.class)) {
return super.createEngineeringCRS(code);
}
return create(AuthorityFactoryProxy.ENGINEERING_CRS, code);
}
/**
* Returns a 2-dimensional engineering coordinate reference system applied to locations in images.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createImageCRS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createImageCRS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateReferenceSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate reference system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public ImageCRS createImageCRS(final String code) throws FactoryException {
if (isDefault(ImageCRS.class)) {
return super.createImageCRS(code);
}
return create(AuthorityFactoryProxy.IMAGE_CRS, code);
}
/**
* Returns an arbitrary datum from a code. The returned object will typically be an
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public Datum createDatum(final String code) throws FactoryException {
if (isDefault(Datum.class)) {
return super.createDatum(code);
}
return create(AuthorityFactoryProxy.DATUM, code);
}
/**
* Returns a datum defining the location and orientation of an ellipsoid that approximates the shape of the earth.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createGeodeticDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createGeodeticDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createDatum(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public GeodeticDatum createGeodeticDatum(final String code) throws FactoryException {
if (isDefault(GeodeticDatum.class)) {
return super.createGeodeticDatum(code);
}
return create(AuthorityFactoryProxy.GEODETIC_DATUM, code);
}
/**
* Returns a datum identifying a particular reference level surface used as a zero-height surface.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createVerticalDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createVerticalDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createDatum(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public VerticalDatum createVerticalDatum(final String code) throws FactoryException {
if (isDefault(VerticalDatum.class)) {
return super.createVerticalDatum(code);
}
return create(AuthorityFactoryProxy.VERTICAL_DATUM, code);
}
/**
* Returns a datum defining the origin of a temporal coordinate reference system.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createTemporalDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createTemporalDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createDatum(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public TemporalDatum createTemporalDatum(final String code) throws FactoryException {
if (isDefault(TemporalDatum.class)) {
return super.createTemporalDatum(code);
}
return create(AuthorityFactoryProxy.TEMPORAL_DATUM, code);
}
/**
* Returns a datum defining the origin of an engineering coordinate reference system.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createEngineeringDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createEngineeringDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createDatum(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public EngineeringDatum createEngineeringDatum(final String code) throws FactoryException {
if (isDefault(EngineeringDatum.class)) {
return super.createEngineeringDatum(code);
}
return create(AuthorityFactoryProxy.ENGINEERING_DATUM, code);
}
/**
* Returns a datum defining the origin of an image coordinate reference system.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createImageDatum(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createImageDatum(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createDatum(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the datum for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public ImageDatum createImageDatum(final String code) throws FactoryException {
if (isDefault(ImageDatum.class)) {
return super.createImageDatum(code);
}
return create(AuthorityFactoryProxy.IMAGE_DATUM, code);
}
/**
* Returns a geometric figure that can be used to describe the approximate shape of the earth.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createEllipsoid(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createEllipsoid(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the ellipsoid for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public Ellipsoid createEllipsoid(final String code) throws FactoryException {
if (isDefault(Ellipsoid.class)) {
return super.createEllipsoid(code);
}
return create(AuthorityFactoryProxy.ELLIPSOID, code);
}
/**
* Returns a prime meridian defining the origin from which longitude values are determined.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createPrimeMeridian(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createPrimeMeridian(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the prime meridian for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public PrimeMeridian createPrimeMeridian(final String code) throws FactoryException {
if (isDefault(PrimeMeridian.class)) {
return super.createPrimeMeridian(code);
}
return create(AuthorityFactoryProxy.PRIME_MERIDIAN, code);
}
/**
* Returns information about spatial, vertical, and temporal extent (usually a domain of validity) from a code.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createExtent(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createExtent(String)}
* method in the parent class.</li>
* </ul>
*
* @return the extent for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public Extent createExtent(final String code) throws FactoryException {
if (isDefault(Extent.class)) {
return super.createExtent(code);
}
return create(AuthorityFactoryProxy.EXTENT, code);
}
/**
* Returns an arbitrary coordinate system from a code.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCoordinateSystem(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCoordinateSystem(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CoordinateSystem createCoordinateSystem(final String code) throws FactoryException {
if (isDefault(CoordinateSystem.class)) {
return super.createCoordinateSystem(code);
}
return create(AuthorityFactoryProxy.COORDINATE_SYSTEM, code);
}
/**
* Returns a 2- or 3-dimensional coordinate system for geodetic latitude and longitude, sometime with ellipsoidal height.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createEllipsoidalCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createEllipsoidalCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public EllipsoidalCS createEllipsoidalCS(final String code) throws FactoryException {
if (isDefault(EllipsoidalCS.class)) {
return super.createEllipsoidalCS(code);
}
return create(AuthorityFactoryProxy.ELLIPSOIDAL_CS, code);
}
/**
* Returns a 1-dimensional coordinate system for heights or depths of points.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createVerticalCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createVerticalCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public VerticalCS createVerticalCS(final String code) throws FactoryException {
if (isDefault(VerticalCS.class)) {
return super.createVerticalCS(code);
}
return create(AuthorityFactoryProxy.VERTICAL_CS, code);
}
/**
* Returns a 1-dimensional coordinate system for heights or depths of points.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createTimeCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createTimeCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public TimeCS createTimeCS(final String code) throws FactoryException {
if (isDefault(TimeCS.class)) {
return super.createTimeCS(code);
}
return create(AuthorityFactoryProxy.TIME_CS, code);
}
/**
* Returns a 2- or 3-dimensional Cartesian coordinate system made of straight orthogonal axes.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCartesianCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCartesianCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CartesianCS createCartesianCS(final String code) throws FactoryException {
if (isDefault(CartesianCS.class)) {
return super.createCartesianCS(code);
}
return create(AuthorityFactoryProxy.CARTESIAN_CS, code);
}
/**
* Returns a 3-dimensional coordinate system with one distance measured from the origin and two angular coordinates.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createSphericalCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createSphericalCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public SphericalCS createSphericalCS(final String code) throws FactoryException {
if (isDefault(SphericalCS.class)) {
return super.createSphericalCS(code);
}
return create(AuthorityFactoryProxy.SPHERICAL_CS, code);
}
/**
* Returns a 3-dimensional coordinate system made of a polar coordinate system
* extended by a straight perpendicular axis.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCylindricalCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCylindricalCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CylindricalCS createCylindricalCS(final String code) throws FactoryException {
if (isDefault(CylindricalCS.class)) {
return super.createCylindricalCS(code);
}
return create(AuthorityFactoryProxy.CYLINDRICAL_CS, code);
}
/**
* Returns a 2-dimensional coordinate system for coordinates represented by a distance from the origin
* and an angle from a fixed direction.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createPolarCS(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createPolarCS(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createCoordinateSystem(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the coordinate system for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public PolarCS createPolarCS(final String code) throws FactoryException {
if (isDefault(PolarCS.class)) {
return super.createPolarCS(code);
}
return create(AuthorityFactoryProxy.POLAR_CS, code);
}
/**
* Returns a coordinate system axis with name, direction, unit and range of values.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCoordinateSystemAxis(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCoordinateSystemAxis(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the axis for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CoordinateSystemAxis createCoordinateSystemAxis(final String code) throws FactoryException {
if (isDefault(CoordinateSystemAxis.class)) {
return super.createCoordinateSystemAxis(code);
}
return create(AuthorityFactoryProxy.AXIS, code);
}
/**
* Returns an unit of measurement from a code.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createUnit(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createUnit(String)}
* method in the parent class.</li>
* </ul>
*
* @return the unit of measurement for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public Unit<?> createUnit(final String code) throws FactoryException {
if (isDefault(Unit.class)) {
return super.createUnit(code);
}
return create(AuthorityFactoryProxy.UNIT, code);
}
/**
* Returns a definition of a single parameter used by an operation method.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createParameterDescriptor(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createParameterDescriptor(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the parameter descriptor for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public ParameterDescriptor<?> createParameterDescriptor(final String code) throws FactoryException {
if (isDefault(ParameterDescriptor.class)) {
return super.createParameterDescriptor(code);
}
return create(AuthorityFactoryProxy.PARAMETER, code);
}
/**
* Returns a description of the algorithm and parameters used to perform a coordinate operation.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createOperationMethod(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createOperationMethod(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the operation method for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public OperationMethod createOperationMethod(final String code) throws FactoryException {
if (isDefault(OperationMethod.class)) {
return super.createOperationMethod(code);
}
return create(AuthorityFactoryProxy.METHOD, code);
}
/**
* Returns an operation for transforming coordinates in the source CRS to coordinates in the target CRS.
* The default implementation performs the following steps:
* <ul>
* <li>Return the cached instance for the given code if such instance already exists.</li>
* <li>Otherwise if the Data Access Object (DAO) overrides the {@code createCoordinateOperation(String)}
* method, invoke that method and cache the result for future use.</li>
* <li>Otherwise delegate to the {@link GeodeticAuthorityFactory#createCoordinateOperation(String)}
* method in the parent class. This allows to check if the more generic
* {@link #createObject(String)} method cached a value before to try that method.</li>
* </ul>
*
* @return the operation for the given code.
* @throws FactoryException if the object creation failed.
*/
@Override
public CoordinateOperation createCoordinateOperation(final String code) throws FactoryException {
if (isDefault(CoordinateOperation.class)) {
return super.createCoordinateOperation(code);
}
return create(AuthorityFactoryProxy.OPERATION, code);
}
/**
* The key objects to use in the {@link ConcurrentAuthorityFactory#cache}.
* This is one of the following pairs of values:
* <ul>
* <li>For all {@code ConcurrentAuthorityFactory.createFoo(String)} methods:
* <ol>
* <li>The {@code Foo} {@link Class} of the cached object.</li>
* <li>The authority code of the cached object.</li>
* </ol>
* </li>
* <li>For {@link ConcurrentAuthorityFactory#createFromCoordinateReferenceSystemCodes(String, String)}:
* <ol>
* <li>The authority code of source CRS (stored in the "type" field even if the name is not right).</li>
* <li>The authority code of target CRS (stored in the "code" field).</li>
* </ol>
* </li>
* </ul>
*/
private static final class Key {
/** The type of the cached object. */ final Object type;
/** The cached object authority code. */ final String code;
/** Creates a new key for the given type and code. */
Key(final Object type, final String code) {
this.type = type;
this.code = code;
}
/** Returns the hash code value for this key. */
@Override public int hashCode() {
return type.hashCode() ^ code.hashCode();
}
/** Compares this key with the given object for equality .*/
@Override public boolean equals(final Object other) {
if (other instanceof Key) {
final Key that = (Key) other;
return type.equals(that.type) && code.equals(that.code);
}
return false;
}
/** String representation used by {@link CacheRecord}. */
@Override public String toString() {
final StringBuilder buffer = new StringBuilder();
if (type instanceof Class<?>) {
buffer.append("Code[“").append(code);
if (buffer.length() > 15) { // Arbitrary limit in string length.
buffer.setLength(15);
buffer.append('…');
}
buffer.append("” : ").append(((Class<?>) type).getSimpleName());
} else {
buffer.append("CodePair[“").append(type).append("” → “").append(code).append('”');
}
return buffer.append(']').toString();
}
}
/**
* Returns an object from a code using the given proxy. This method first checks in the cache.
* If no object exists in the cache for the given code, then a lock is created and the object
* creation is delegated to the {@linkplain #getDataAccess() Data Access Object}.
* The result is then stored in the cache and returned.
*
* @param <T> the type of the object to be returned.
* @param proxy the proxy to use for creating the object.
* @param code the code of the object to create.
* @return the object extracted from the cache or created.
* @throws FactoryException if an error occurred while creating the object.
*/
private <T> T create(final AuthorityFactoryProxy<T> proxy, final String code) throws FactoryException {
ArgumentChecks.ensureNonNull("code", code);
final Class<T> type = proxy.type;
final Key key = new Key(type, normalizeCode(code));
Object value = cache.peek(key);
if (!type.isInstance(value)) {
final Cache.Handler<Object> handler = cache.lock(key);
try {
value = handler.peek();
if (!type.isInstance(value)) {
final T result;
final DAO factory = getDataAccess();
try {
result = proxy.create(factory, key.code);
} finally {
release(null, type, code);
}
if (isCacheable(code, result)) {
value = result; // For the finally block below.
}
return result;
}
} finally {
handler.putAndUnlock(value);
}
}
return type.cast(value);
}
/**
* Returns operations from source and target coordinate reference system codes.
* The default implementation performs the following steps:
* <ul>
* <li>Returns the cached collection for the given pair of codes if such collection already exists.</li>
* <li>Otherwise:
* <ol>
* <li>get an instance of the Data Access Object,</li>
* <li>delegate to its {@link GeodeticAuthorityFactory#createFromCoordinateReferenceSystemCodes(String, String)} method,</li>
* <li>release the Data Access Object — <em>this step assumes that the collection obtained at step 2
* is still valid after the Data Access Object has been released</em>,</li>
* <li>cache the result — <em>this step assumes that the collection obtained at step 2 is immutable</em>.</li>
* </ol>
* </li>
* </ul>
*
* @return the operations from {@code sourceCRS} to {@code targetCRS}.
* @throws FactoryException if the object creation failed.
*/
@Override
@SuppressWarnings("unchecked")
public Set<CoordinateOperation> createFromCoordinateReferenceSystemCodes(
final String sourceCRS, final String targetCRS) throws FactoryException
{
ArgumentChecks.ensureNonNull("sourceCRS", sourceCRS);
ArgumentChecks.ensureNonNull("targetCRS", targetCRS);
final Key key = new Key(normalizeCode(sourceCRS), normalizeCode(targetCRS));
Object value = cache.peek(key);
if (!(value instanceof Set<?>)) {
final Cache.Handler<Object> handler = cache.lock(key);
try {
value = handler.peek();
if (!(value instanceof Set<?>)) {
final DAO factory = getDataAccess();
try {
value = factory.createFromCoordinateReferenceSystemCodes(sourceCRS, targetCRS);
} finally {
release("createFromCoordinateReferenceSystemCodes", CoordinateOperation.class, null);
}
}
} finally {
handler.putAndUnlock(value);
}
}
return (Set<CoordinateOperation>) value;
}
/**
* Returns a finder which can be used for looking up unidentified objects.
* The default implementation delegates lookup to the underlying Data Access Object and caches the result.
*
* @return a finder to use for looking up unidentified objects.
* @throws FactoryException if the finder can not be created.
*/
@Override
public IdentifiedObjectFinder newIdentifiedObjectFinder() throws FactoryException {
return new Finder(this);
}
/**
* An implementation of {@link IdentifiedObjectFinder} which delegates
* the work to the underlying Data Access Object and caches the result.
*
* <div class="section">Synchronization note</div>
* our public API claims that {@link IdentifiedObjectFinder}s are not thread-safe.
* Nevertheless we synchronize this particular implementation for safety, because the consequence of misuse
* are more dangerous than other implementations. Furthermore this is also a way to assert that no code path
* go to the {@link #create(AuthorityFactoryProxy, String)} method from a non-overridden public method.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 0.7
* @since 0.7
* @module
*/
private static final class Finder extends IdentifiedObjectFinder {
/**
* The finder on which to delegate the work. This is acquired by {@link #acquire()}
* <strong>and must be released</strong> by call to {@link #release()} once finished.
*/
private transient IdentifiedObjectFinder finder;
/**
* Number of time that {@link #acquire()} has been invoked.
* When this count reaches zero, the {@linkplain #finder} is released.
*/
private transient int acquireCount;
/**
* The object in process of being searched, for information purpose only.
*/
private transient IdentifiedObject searching;
/**
* Creates a finder for the given type of objects.
*/
Finder(final ConcurrentAuthorityFactory<?> factory) {
super(factory);
}
/**
* Acquires a new {@linkplain #finder}.
* The {@link #release()} method must be invoked in a {@code finally} block after the call to {@code acquire}.
* The pattern must be as below (note that the call to {@code acquire()} is inside the {@code try} block):
*
* {@preformat java
* try {
* acquire();
* (finder or proxy).doSomeStuff();
* } finally {
* release();
* }
* }
*/
private void acquire() throws FactoryException {
assert Thread.holdsLock(this);
assert (acquireCount == 0) == (finder == null) : acquireCount;
if (acquireCount == 0) {
final GeodeticAuthorityFactory delegate = ((ConcurrentAuthorityFactory<?>) factory).getDataAccess();
/*
* Set 'acquireCount' only after we succeed in fetching the factory, and before any operation on it.
* The intent is to get ConcurrentAuthorityFactory.release() invoked if and only if the getDataAccess()
* method succeed, no matter what happen after this point.
*/
acquireCount = 1;
finder = delegate.newIdentifiedObjectFinder();
finder.setWrapper(this);
} else {
acquireCount++;
}
}
/**
* Releases the {@linkplain #finder}.
*/
private void release() {
assert Thread.holdsLock(this);
if (acquireCount == 0) {
// May happen only if a failure occurred during getDataAccess() execution.
return;
}
if (--acquireCount == 0) {
finder = null;
((ConcurrentAuthorityFactory<?>) factory).release(null, null, null);
}
}
/**
* Returns a set of authority codes that <strong>may</strong> identify the same
* object than the specified one. This method delegates to the data access object.
*/
@Override
protected synchronized Set<String> getCodeCandidates(final IdentifiedObject object) throws FactoryException {
try {
acquire();
return finder.getCodeCandidates(object);
} finally {
release();
}
}
/**
* Returns the cached value for the given object, or {@code null} if none.
*/
@Override
final Set<IdentifiedObject> getFromCache(final IdentifiedObject object) {
final Map<IdentifiedObject,FindEntry> findPool = ((ConcurrentAuthorityFactory<?>) factory).findPool;
synchronized (findPool) {
final FindEntry entry = findPool.get(object);
if (entry != null) {
// 'finder' may be null if this method is invoked directly by this Finder.
return entry.get((finder != null ? finder : this).isIgnoringAxes());
}
}
return null;
}
/**
* Stores the given result in the cache.
* This method shall be invoked only when {@link #getSearchDomain()} is not {@link Domain#DECLARATION}.
*/
@Override
final Set<IdentifiedObject> cache(final IdentifiedObject object, Set<IdentifiedObject> result) {
final Map<IdentifiedObject,FindEntry> findPool = ((ConcurrentAuthorityFactory<?>) factory).findPool;
result = CollectionsExt.unmodifiableOrCopy(result);
FindEntry entry = new FindEntry();
synchronized (findPool) {
final FindEntry c = findPool.putIfAbsent(object, entry);
if (c != null) {
entry = c; // May happen if the same set has been computed in another thread.
}
// 'finder' should never be null since this method is not invoked directly by this Finder.
result = entry.set(finder.isIgnoringAxes(), result, object == searching);
}
return result;
}
/**
* Looks up an object from this authority factory which is approximately equal to the specified object.
* The default implementation performs the same lookup than the Data Access Object and caches the result.
*/
@Override
public Set<IdentifiedObject> find(final IdentifiedObject object) throws FactoryException {
Set<IdentifiedObject> candidate = getFromCache(object);
if (candidate == null) {
/*
* Nothing has been found in the cache. Delegates the search to the Data Access Object.
* Note that the Data Access Object will itself callbacks our 'cache(…)' method, so there
* is no need that we cache the result here.
*/
synchronized (this) {
try {
acquire();
searching = object;
candidate = finder.find(object);
} finally {
searching = null;
release();
}
}
}
return candidate;
}
}
/**
* Cache for the result of {@link IdentifiedObjectFinder#find(IdentifiedObject)} operations.
* All access to this object must be done in a block synchronized on {@link #findPool}.
*/
private static final class FindEntry {
/** Result of the search with or without ignoring axes. */
private Set<IdentifiedObject> strict, lenient;
/** Whether the cache is the result of an explicit request instead than a dependency search. */
private boolean explicitStrict, explicitLenient;
/** Returns the cached instance. */
Set<IdentifiedObject> get(final boolean ignoreAxes) {
return ignoreAxes ? lenient : strict;
}
/** Cache an instance, or return previous instance if computed concurrently. */
@SuppressWarnings({"AssignmentToCollectionOrArrayFieldFromParameter", "ReturnOfCollectionOrArrayField"})
Set<IdentifiedObject> set(final boolean ignoreAxes, Set<IdentifiedObject> result, final boolean explicit) {
if (ignoreAxes) {
if (lenient != null) {
result = lenient;
} else {
lenient = result;
}
explicitLenient |= explicit;
} else {
if (strict != null) {
result = strict;
} else {
strict = result;
}
explicitStrict |= explicit;
}
return result;
}
/** Forgets the set that were not explicitly requested. */
boolean cleanup() {
if (!explicitStrict) strict = null;
if (!explicitLenient) lenient = null;
return (strict == null) && (lenient == null);
}
}
/**
* Returns whether the given object can be cached. This method is invoked after the
* {@linkplain #newDataAccess() Data Access Object} created a new object not previously in the cache.
* If this {@code isCacheable(…)} method returns {@code true}, then the newly created object will be cached so
* that next calls to the same {@code createFoo(String)} method with the same code may return the same object.
* If this method returns {@code false}, then the newly created object will not be cached and next call to
* the {@code createFoo(String)} method with the same code will return a new object.
*
* <p>The default implementation always returns {@code true}.
* Subclasses can override this method for filtering the objects to store in the cache.</p>
*
* @param code the authority code specified by the caller for creating an object.
* @param object the object created for the given authority code.
* @return whether the given object should be cached.
*
* @see #printCacheContent(PrintWriter)
*
* @since 0.8
*/
protected boolean isCacheable(String code, Object object) {
return true;
}
/**
* Prints the cache content to the given writer.
* Keys are sorted by numerical order if possible, or alphabetical order otherwise.
* This method is used for debugging purpose only.
*
* @param out the output printer, or {@code null} for the {@linkplain System#out standard output stream}.
*
* @see #isCacheable(String, Object)
*/
@Debug
public void printCacheContent(final PrintWriter out) {
CacheRecord.printCacheContent(cache, out);
}
/**
* A hook to be executed either when the {@link ConcurrentAuthorityFactory} is collected by the garbage collector,
* when the Java Virtual Machine is shutdown, or when the module is uninstalled by the OSGi or Servlet container.
*
* <p><strong>Do not keep reference to the enclosing factory</strong> - in particular,
* this class must not be static - otherwise the factory would never been garbage collected.</p>
*/
private static final class ShutdownHook<DAO extends GeodeticAuthorityFactory> // MUST be static!
extends PhantomReference<ConcurrentAuthorityFactory<DAO>> implements Disposable, Callable<Object>
{
/**
* The {@link ConcurrentAuthorityFactory#availableDAOs} queue.
*/
private final Deque<DataAccessRef<DAO>> availableDAOs;
/**
* Creates a new shutdown hook for the given factory.
*/
ShutdownHook(final ConcurrentAuthorityFactory<DAO> factory) {
super(factory, ReferenceQueueConsumer.QUEUE);
availableDAOs = factory.availableDAOs;
}
/**
* Invoked indirectly by the garbage collector when the {@link ConcurrentAuthorityFactory} is disposed.
*/
@Override
public void dispose() {
Shutdown.unregister(this);
try {
call();
} catch (Exception exception) {
/*
* Pretend that the exception is logged by ConcurrentAuthorityFactory.finalize().
* This is not true, but carries the idea that the error occurred while cleaning
* ConcurrentAuthorityFactory after garbage collection.
*/
unexpectedException("finalize", exception);
}
}
/**
* Invoked at JVM shutdown time, or when the container (OSGi or Servlet) uninstall the bundle containing SIS.
*/
@Override
public Object call() throws Exception {
final List<DAO> factories;
synchronized (availableDAOs) {
factories = ConcurrentAuthorityFactory.clear(availableDAOs);
}
// No call to confirmClose(List<DAO>) as we want to force close.
close(factories);
return null;
}
}
/**
* Clears the given queue and returns all DAO instances that it contained.
* The given queue shall be the {@link ConcurrentAuthorityFactory#availableDAOs} queue.
*
* @param availableDAOs the queue of factories to close.
*/
static <DAO extends GeodeticAuthorityFactory> List<DAO> clear(final Deque<DataAccessRef<DAO>> availableDAOs) {
assert Thread.holdsLock(availableDAOs);
final List<DAO> factories = new ArrayList<>(availableDAOs.size());
DataAccessRef<DAO> dao;
while ((dao = availableDAOs.pollFirst()) != null) {
factories.add(dao.factory);
}
return factories;
}
/**
* Invokes {@link AutoCloseable#close()} on all the given factories.
* Exceptions will be collected and rethrown only after all factories have been closed.
*
* @param factories the factories to close.
* @throws Exception the exception thrown by the first factory that failed to close.
*/
static <DAO extends GeodeticAuthorityFactory> void close(final List<DAO> factories) throws Exception {
Exception exception = null;
for (int i=factories.size(); --i>=0;) {
final DAO factory = factories.get(i);
if (factory instanceof AutoCloseable) try {
((AutoCloseable) factory).close();
} catch (Exception e) {
if (exception == null) {
exception = e;
} else {
exception.addSuppressed(e);
}
}
}
if (exception != null) {
throw exception;
}
}
/**
* Immediately closes all Data Access Objects that are closeable.
* This method does not clear the cache and does not disallow further usage of this factory:
* this {@code ConcurrentAuthorityFactory} can still be used as usual after it has been "closed".
* {@linkplain #newDataAccess() New Data Access Objects} will be created if needed for replacing
* the ones closed by this method.
*
* <p>The main purpose of this method is to force immediate release of JDBC connections or other kind of resources
* that Data Access Objects may hold. If this method is not invoked, Data Access Objects will be closed
* when this {@code ConcurrentAuthorityFactory} will be garbage collected or at JVM shutdown time,
* depending which event happen first.</p>
*
* @throws FactoryException if an error occurred while closing the Data Access Objects.
*
* @see #canClose(GeodeticAuthorityFactory)
*/
@Override
public void close() throws FactoryException {
try {
final List<DAO> factories;
synchronized (availableDAOs) {
factories = clear(availableDAOs);
}
confirmClose(factories);
close(factories); // Must be invoked outside the synchronized block.
} catch (Exception e) {
if (e instanceof FactoryException) {
throw (FactoryException) e;
} else {
throw new FactoryException(e);
}
}
}
/**
* Returns a string representation of this factory for debugging purpose only.
* The string returned by this method may change in any future SIS version.
*
* @return a string representation for debugging purpose.
*
* @see #printCacheContent(PrintWriter)
*/
@Override
public String toString() {
final String s = super.toString();
DataAccessRef<DAO> usage = currentDAO.get();
if (usage == null) {
synchronized (availableDAOs) {
usage = availableDAOs.peekLast();
}
if (usage == null) {
return s;
}
}
return s + System.lineSeparator() + usage;
}
/**
* Completes the string representation of this factory for debugging purpose only.
* The string formatted by this method may change in any future SIS version.
*/
@Debug
@Override
final void appendStringTo(final StringBuilder buffer) {
buffer.append(", cache=").append(cache.size()).append(", DAO=");
synchronized (availableDAOs) {
buffer.append(availableDAOs.size());
if (remainingDAOs <= 0) {
buffer.append(" (limit reached)");
}
}
}
}