blob: f412dc880c18f225898540e3b1cb2a83774fea2b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.referencing;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.sql.SQLTransientException;
import org.opengis.util.FactoryException;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.datum.DatumAuthorityFactory;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
import org.apache.sis.internal.referencing.LazySet;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.system.Modules;
import org.apache.sis.internal.system.SystemListener;
import org.apache.sis.internal.referencing.EPSGFactoryProxy;
import org.apache.sis.referencing.factory.MultiAuthoritiesFactory;
import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
import org.apache.sis.referencing.factory.UnavailableFactoryException;
import org.apache.sis.referencing.factory.sql.EPSGFactory;
import org.apache.sis.util.logging.Logging;
/**
* Provides the CRS, CS, datum and coordinate operation authority factories.
* Provides also the system-wide {@link MultiAuthoritiesFactory} instance used by {@link CRS#forCode(String)}.
* Current version handles the EPSG factory in a special way, but we may try to avoid doing special cases in a
* future SIS version (this may require more help from {@link ServiceLoader}).
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.8
* @since 0.7
* @module
*/
final class AuthorityFactories<T extends AuthorityFactory> extends LazySet<T> {
/**
* An array containing only the EPSG factory. Content of this array is initially null.
* The EPSG factory will be created when first needed by {@link #initialValues()}.
* This array is returned directly (not cloned) by {@link #initialValues()}.
*/
private static final GeodeticAuthorityFactory[] EPSG = new GeodeticAuthorityFactory[1];
/**
* The unique system-wide authority factory instance that contains all factories found on the classpath,
* plus the EPSG factory. The {@link EPSGFactoryProxy} most be excluded from this list, since the EPSG
* factory is handled in a special way.
*/
static final MultiAuthoritiesFactory ALL = new MultiAuthoritiesFactory(
new AuthorityFactories<>(CRSAuthorityFactory.class),
new AuthorityFactories<>(CSAuthorityFactory.class),
new AuthorityFactories<>(DatumAuthorityFactory.class),
new AuthorityFactories<>(CoordinateOperationAuthorityFactory.class))
{
/** Anonymous constructor */ {
setLenient(true);
}
@Override
public void reload() {
EPSG(null);
super.reload();
}
};
/**
* Registers a hook for forcing {@code ALL} to reload all CRS, CS, datum and coordinate operation factories
* when the classpath changed.
*/
static {
SystemListener.add(new SystemListener(Modules.REFERENCING) {
@Override protected void classpathChanged() {ALL.reload();}
});
}
/**
* Creates a new provider for factories of the given type.
*/
private AuthorityFactories(final Class<T> type) {
super(type);
}
/**
* Sets the EPSG factory to the given value.
*/
static void EPSG(final GeodeticAuthorityFactory factory) {
synchronized (EPSG) {
EPSG[0] = factory;
}
}
/**
* Returns the factory connected to the EPSG geodetic dataset if possible, or the EPSG fallback otherwise.
* If an EPSG data source has been found, then this method returns an instance of {@link EPSGFactory} but
* there is no guarantee that attempts to use that factory will succeed; for example maybe the EPSG schema
* does not exist. Callers should be prepared to either receive an {@link EPSGFactoryFallback} directly if
* the EPSG data source does not exist, or replace the {@code EPSGFactory} by a {@code EPSGFactoryFallback}
* later if attempt to use the returned factory fails.
*/
static GeodeticAuthorityFactory EPSG() {
synchronized (EPSG) {
GeodeticAuthorityFactory factory = EPSG[0];
if (factory == null) {
try {
factory = new EPSGFactory(null);
} catch (FactoryException e) {
log(e, false);
factory = EPSGFactoryFallback.INSTANCE;
}
EPSG[0] = factory;
}
return factory;
}
}
/**
* Returns the fallback to use if the authority factory is not available. Unless the problem may be temporary,
* this method replaces the {@link EPSGFactory} instance by {@link EPSGFactoryFallback} in order to prevent
* the same exception to be thrown and logged on every calls to {@link CRS#forCode(String)}.
*/
static GeodeticAuthorityFactory fallback(final UnavailableFactoryException e) throws UnavailableFactoryException {
final boolean isTransient = (e.getCause() instanceof SQLTransientException);
final AuthorityFactory unavailable = e.getUnavailableFactory();
GeodeticAuthorityFactory factory;
final boolean alreadyDone;
synchronized (EPSG) {
factory = EPSG[0];
alreadyDone = (factory == EPSGFactoryFallback.INSTANCE);
if (!alreadyDone) { // May have been set in another thread (race condition).
if (unavailable != factory) {
throw e; // Exception did not come from a factory that we control.
}
factory = EPSGFactoryFallback.INSTANCE;
if (!isTransient) {
ALL.reload();
EPSG[0] = factory;
}
}
}
if (!alreadyDone) {
log(e, true);
}
return factory;
}
/**
* Notifies that a factory is unavailable, but without giving a fallback and without logging.
* The caller is responsible for throwing an exception, or for logging a warning and provide its own fallback.
*
* @return {@code false} if the caller can try again, or {@code true} if the failure can be considered final.
*/
static boolean failure(final UnavailableFactoryException e) {
if (!(e.getCause() instanceof SQLTransientException)) {
final AuthorityFactory unavailable = e.getUnavailableFactory();
synchronized (EPSG) {
final GeodeticAuthorityFactory factory = EPSG[0];
if (factory == EPSGFactoryFallback.INSTANCE) { // May have been set in another thread.
return false;
}
if (unavailable == factory) {
ALL.reload();
EPSG[0] = EPSGFactoryFallback.INSTANCE;
return false;
}
}
}
return true;
}
/**
* Logs the given exception at the given level. This method pretends that the logging come from
* {@link CRS#getAuthorityFactory(String)}, which is the public facade for {@link #EPSG()}.
*/
private static void log(final Exception e, final boolean isWarning) {
String message = e.getMessage(); // Prefer the locale of system administrator.
if (message == null) {
message = e.toString();
}
final LogRecord record = new LogRecord(isWarning ? Level.WARNING : Level.CONFIG, message);
if (isWarning && !(e instanceof UnavailableFactoryException)) {
record.setThrown(e);
}
record.setLoggerName(Loggers.CRS_FACTORY);
Logging.log(CRS.class, "getAuthorityFactory", record);
}
/**
* Invoked by {@link LazySet} for adding the EPSG factory before any other factory fetched by {@code ServiceLoader}.
* We put the EPSG factory first because it is often used anyway even for {@code CRS} and {@code AUTO} namespaces.
*
* <p>This method tries to instantiate an {@link EPSGFactory} if possible,
* or an {@link EPSGFactoryFallback} otherwise.</p>
*
* @return the EPSG factory in an array. Callers shall not modify the returned array.
*/
@Override
@SuppressWarnings("unchecked")
protected T[] initialValues() {
EPSG(); // Force EPSGFactory instantiation if not already done.
return (T[]) EPSG;
}
/**
* Invoked by {@link LazySet} for fetching the next element from the given iterator.
* Skips the {@link EPSGFactoryProxy} if possible, or returns {@code null} otherwise.
* Note that {@link MultiAuthoritiesFactory} is safe to null values.
*/
@Override
protected T next(final Iterator<? extends T> it) {
T e = it.next();
if (e instanceof EPSGFactoryProxy) {
e = it.hasNext() ? it.next() : null;
}
return e;
}
}