| /* |
| * 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)"); |
| } |
| } |
| } |
| } |