/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.referencing.factory;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.opengis.referencing.FactoryException;
import org.geotools.factory.Hints;
import org.geotools.factory.OptionalFactory;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
/**
* A buffered authority factory which will defer the {@linkplain #createBackingStore creation
* of a backing store} until when first needed. This approach allow to etablish a connection to
* a database (for example) only when first needed. In addition, the backing store can be
* automatically disposed after a timeout and recreated when needed again.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @todo Extends {@link BufferedAuthorityFactory} for now in order to improve the trunk stability
* during GEOT-1286 development, but we may revisit that after GEOT-1286 completion.
*/
public abstract class DeferredAuthorityFactory extends BufferedAuthorityFactory
implements OptionalFactory
{
/**
* The timer for {@linkplain AbstractAuthorityFactory#dispose disposing} backing stores.
*
* @todo Give a name to this timer when we will be allowed to compile for J2SE 1.5.
*/
private static final Timer TIMER = new Timer(true);
/**
* The task for disposing the backing store, or {@code null} if none.
* This task will be scheduled for repeated execution by {@link #setTimeout}.
*/
private TimerTask disposer;
/**
* {@code true} if the backing store was used since the last time the timer task was run.
* A value of {@code true} means that the task must wait again. A value of {@code false}
* means that it can dispose the backing store.
*/
private boolean used;
/**
* Constructs an instance without initial backing store. Subclasses are responsible for
* creating an appropriate backing store when the {@link #createBackingStore} method is
* invoked.
*
* @param userHints An optional set of hints, or {@code null} if none.
* @param priority The priority for this factory, as a number between
* {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
* {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
*
* @see #createBackingStore
*
* @since 2.2
*/
protected DeferredAuthorityFactory(final Hints userHints, final int priority) {
super(priority, DEFAULT_MAX);
}
/**
* Constructs an instance without initial backing store. Subclasses are responsible for
* creating an appropriate backing store when the {@link #createBackingStore} method is
* invoked.
*
* @param userHints An optional set of hints, or {@code null} if none.
* @param priority The priority for this factory, as a number between
* {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
* {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
* @param maxStrongReferences The maximum number of objects to keep by strong reference.
*
* @see #createBackingStore
*
* @since 2.2
*/
protected DeferredAuthorityFactory(final Hints userHints,
final int priority,
final int maxStrongReferences)
{
super(priority, maxStrongReferences);
}
/**
* Returns {@code true} if this factory is available. The default implementation returns
* {@code false} if {@link #createBackingStore} throws an exception.
*/
@Override
public boolean isAvailable() {
return super.isAvailable();
}
/**
* Returns the backing store authority factory.
*
* @return The backing store to uses in {@code createXXX(...)} methods.
* @throws FactoryException if the creation of backing store failed.
*/
@Override
final AbstractAuthorityFactory getBackingStore() throws FactoryException {
assert Thread.holdsLock(this);
if (backingStore == null) {
backingStore = createBackingStore();
if (backingStore == null) {
throw new FactoryNotFoundException(Errors.format(ErrorKeys.NO_DATA_SOURCE));
}
completeHints();
}
used = true; // Tell to the disposer to wait again.
return backingStore;
}
/**
* Creates the backing store authority factory. This method is invoked the first time a
* {@code createXXX(...)} method is invoked.
*
* @return The backing store to uses in {@code createXXX(...)} methods.
* @throws FactoryNotFoundException if the backing store has not been found.
* @throws FactoryException if the creation of backing store failed for an other reason.
*/
protected abstract AbstractAuthorityFactory createBackingStore() throws FactoryException;
/**
* Returns {@code true} if this deferred factory is connected to its backing store.
* This method returns {@code false} if no {@code createFoo} method has been invoked,
* if the backing store has been automatically disposed after the {@linkplain #setTimeout
* timeout} or if this factoy has been {@linkplain #dispose disposed}.
*/
public synchronized boolean isConnected() {
return backingStore != null;
}
/**
* Set a timer for disposing the backing store after the specified amount of milliseconds of
* inactivity. The {@link #createBackingStore} method will be responsible for creating a new
* backing store when needed. Note that the backing store disposal can be vetoed if
* {@link #canDisposeBackingStore} returns {@code false}.
*
* @param delay The minimal delay before to close the backing store. This delay is very
* approximative. The backing store will not be closed before, but may take as
* much as twice that time before to be closed.
*/
public synchronized void setTimeout(final long delay) {
if (disposer != null) {
disposer.cancel();
}
disposer = new Disposer();
TIMER.schedule(disposer, delay, delay);
}
/**
* Returns {@code true} if the backing store can be disposed now. This method is invoked
* automatically after the amount of time specified by {@link #setTimeout} if the factory
* were not used during that time. The default implementation always returns {@code true}.
* Subclasses should override this method and returns {@code false} if they want to prevent
* the backing store disposal under some circonstances.
*
* @param backingStore The backing store in process of being disposed.
*/
protected boolean canDisposeBackingStore(final AbstractAuthorityFactory backingStore) {
return true;
}
/**
* Releases resources immediately instead of waiting for the garbage collector. This
* method disposes the backing store regardeless of {@link #canDisposeBackingStore} value.
*/
@Override
public synchronized void dispose() throws FactoryException {
if (disposer != null) {
disposer.cancel();
disposer = null;
}
super.dispose();
}
/**
* The task for closing the backing store after the timeout.
*/
private final class Disposer extends TimerTask {
public void run() {
synchronized (DeferredAuthorityFactory.this) {
if (used || !canDisposeBackingStore(backingStore)) {
used = false;
return;
}
if (cancel()) {
disposer = null;
if (backingStore != null) try {
backingStore.dispose();
backingStore = null;
} catch (FactoryException exception) {
backingStore = null;
final LogRecord record = Loggings.format(Level.WARNING,
LoggingKeys.CANT_DISPOSE_BACKING_STORE);
record.setSourceMethodName("run");
record.setSourceClassName(Disposer.class.getName());
record.setThrown(exception);
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
}
// Needed in order to lets GC do its job.
hints.remove(Hints.DATUM_AUTHORITY_FACTORY);
hints.remove(Hints.CS_AUTHORITY_FACTORY);
hints.remove(Hints.CRS_AUTHORITY_FACTORY);
hints.remove(Hints.COORDINATE_OPERATION_AUTHORITY_FACTORY);
}
}
}
}
}