/*
* 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.
*/
package org.geotools.referencing.factory.epsg;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;
import java.util.Map;
import java.util.TreeSet;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.net.MalformedURLException;
import java.net.URL;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.geotools.factory.Hints;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.factory.AbstractAuthorityFactory;
import org.geotools.referencing.factory.DeferredAuthorityFactory;
import org.geotools.referencing.factory.FactoryNotFoundException;
import org.geotools.referencing.factory.PropertyAuthorityFactory;
import org.geotools.referencing.factory.ReferencingFactoryContainer;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.metadata.iso.citation.CitationImpl;
import org.geotools.io.TableWriter;
import org.geotools.io.IndentedLineWriter;
import org.geotools.resources.Arguments;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.util.logging.Logging;
/**
* Authority factory for {@linkplain CoordinateReferenceSystem Coordinate Reference Systems}
* beyong the one defined in the EPSG database. This factory is used as a fallback when a
* requested code is not found in the EPSG database, or when there is no connection at all
* to the EPSG database. The additional CRS are defined as <cite>Well Known Text</cite> in
* a property file located by default in the {@code org.geotools.referencing.factory.epsg}
* package, and whose name should be {@value #FILENAME}. If no property file is found, the
* factory won't be activated. The property file can also be located in a custom directory;
* See {@link #getDefinitionsURL()} for more details.
* <p>
* This factory can also be used to provide custom extensions or overrides to a main EPSG factory.
* In order to provide a custom extension file, override the {@link #getDefinitionsURL()} method.
* In order to make the factory be an override, change the default priority by using the
* two arguments constructor (this factory defaults to {@link ThreadedEpsgFactory#PRIORITY} - 10,
* so it's used as an extension).
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
* @author Jody Garnett
* @author Rueben Schulz
* @author Andrea Aime
*/
public class FactoryUsingWKT extends DeferredAuthorityFactory implements CRSAuthorityFactory {
/**
* The authority. Will be created only when first needed.
*
* @see #getAuthority
*/
private Citation authority;
/**
* The default filename to read. The default {@code FactoryUsingWKT} implementation will
* search for the first occurence of this file in the following places:
* <p>
* <ul>
* <li>In the directory specified by the
* {@value org.geotools.factory.GeoTools#CRS_DIRECTORY_KEY} system property.</li>
* <li>In every {@code org/geotools/referencing/factory/espg} directories found on the
* classpath.</li>
* </ul>
* <p>
* The filename part before the extension ({@code "epsg"}) denotes the authority namespace
* where to register the content of this file. The user-directory given by the system property
* may contains other property files for other authorities, like {@code "esri.properties"},
* but those additional authorities are not handled by the default {@code FactoryUsingWKT}
* class.
*
* @see #getDefinitionsURL
*/
public static final String FILENAME = "epsg.properties";
/**
* The factories to be given to the backing store.
*/
private final ReferencingFactoryContainer factories;
/**
* Default priority for this factory.
*
* @since 2.4
* @deprecated We will try to replace the priority mechanism by a better
* one in a future Geotools version.
*/
protected static final int DEFAULT_PRIORITY = ThreadedEpsgFactory.PRIORITY - 10;
/**
* Directory scanned for extra definitions.
*/
private final File directory;
/**
* Constructs an authority factory using the default set of factories.
*/
public FactoryUsingWKT() {
this(null);
}
/**
* Constructs an authority factory using a set of factories created from the specified hints.
* This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
* {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
* {@code FACTORY} hints.
*/
public FactoryUsingWKT(final Hints userHints) {
this(userHints, DEFAULT_PRIORITY);
}
/**
* Constructs an authority factory using the specified hints and priority.
*/
protected FactoryUsingWKT(final Hints userHints, final int priority) {
super(userHints, priority);
factories = ReferencingFactoryContainer.instance(userHints);
Object hint = null;
if (userHints != null) {
hint = userHints.get(Hints.CRS_AUTHORITY_EXTRA_DIRECTORY);
}
if (hint instanceof File) {
directory = (File) hint;
} else if (hint instanceof String) {
directory = new File((String) hint);
} else {
directory = null;
}
hints.put(Hints.CRS_AUTHORITY_EXTRA_DIRECTORY, directory);
// Disposes the cached property file after at least 15 minutes of inactivity.
setTimeout(15 * 60 * 1000L);
}
/**
* Returns the authority. The default implementation returns the first citation returned
* by {@link #getAuthorities()}, with the addition of identifiers from all additional
* authorities returned by the above method.
*
* @see #getAuthorities
*/
@Override
public synchronized Citation getAuthority() {
// No need to synchronize; this is not a big deal if we create this object twice.
if (authority == null) {
final Citation[] authorities = getAuthorities();
switch (authorities.length) {
case 0: authority = Citations.EPSG; break;
case 1: authority = authorities[0]; break;
default: {
final CitationImpl c = new CitationImpl(authorities[0]);
final Collection<Identifier> identifiers = c.getIdentifiers();
for (int i=1; i<authorities.length; i++) {
identifiers.addAll(authorities[i].getIdentifiers());
}
c.freeze();
authority = c;
break;
}
}
}
return authority;
}
/**
* Returns the set of authorities to use as {@linkplain CoordinateReferenceSystem#getIdentifiers
* identifiers} for the CRS to be created. This set is given to the
* {@linkplain PropertyAuthorityFactory#PropertyAuthorityFactory(ReferencingFactoryContainer,
* Citation[], URL) properties-backed factory constructor}.
* <p>
* The default implementation returns a singleton containing only {@linkplain Citations#EPSG
* EPSG}. Subclasses should override this method in order to enumerate all relevant authorities,
* with {@linkplain Citations#EPSG EPSG} in last position. For example {@link EsriExtension}
* returns {{@linkplain Citations#ESRI ESRI}, {@linkplain Citations#EPSG EPSG}}.
*
* @since 2.4
*/
protected Citation[] getAuthorities() {
return new Citation[] {
Citations.EPSG
};
}
/**
* Returns the URL to the property file that contains CRS definitions.
* The default implementation performs the following search path:
* <ul>
* <li>If a value is set for the {@value #CRS_DIRECTORY_KEY} system property key,
* then the {@value #FILENAME} file will be searched in this directory.</li>
* <li>If no value is set for the above-cited system property, or if no {@value #FILENAME}
* file was found in that directory, then the first {@value #FILENAME} file found in
* any {@code org/geotools/referencing/factory/epsg} directory on the classpath will
* be used.</li>
* <li>If no file was found on the classpath neither, then this factory will be disabled.</li>
* </ul>
*
* @return The URL, or {@code null} if none.
*/
protected URL getDefinitionsURL() {
try {
if (directory != null) {
final File file = new File(directory, FILENAME);
if (file.isFile()) {
return file.toURI().toURL();
}
}
} catch (SecurityException exception) {
Logging.unexpectedException(LOGGER, exception);
} catch (MalformedURLException exception) {
Logging.unexpectedException(LOGGER, exception);
}
return FactoryUsingWKT.class.getResource(FILENAME);
}
/**
* Creates the backing store authority factory.
*
* @return The backing store to uses in {@code createXXX(...)} methods.
* @throws FactoryNotFoundException if the no {@code epsg.properties} file has been found.
* @throws FactoryException if the constructor failed to find or read the file.
* This exception usually has an {@link IOException} as its cause.
*/
protected AbstractAuthorityFactory createBackingStore() throws FactoryException {
try {
URL url = getDefinitionsURL();
if (url == null) {
throw new FactoryNotFoundException(Errors.format(
ErrorKeys.FILE_DOES_NOT_EXIST_$1, FILENAME));
}
final Iterator<? extends Identifier> ids = getAuthority().getIdentifiers().iterator();
final String authority = ids.hasNext() ? ids.next().getCode() : "EPSG";
final LogRecord record = Loggings.format(Level.CONFIG,
LoggingKeys.USING_FILE_AS_FACTORY_$2, url.getPath(), authority);
record.setLoggerName(LOGGER.getName());
LOGGER.log(record);
return new PropertyAuthorityFactory(factories, getAuthorities(), url);
} catch (IOException exception) {
throw new FactoryException(Errors.format(ErrorKeys.CANT_READ_$1, FILENAME), exception);
}
}
/**
* Returns a factory of the given type.
*/
private static final <T extends AbstractAuthorityFactory> T getFactory(final Class<T> type) {
return type.cast(ReferencingFactoryFinder.getCRSAuthorityFactory("EPSG",
new Hints(Hints.CRS_AUTHORITY_FACTORY, type)));
}
/**
* Prints a list of codes that duplicate the ones provided by {@link ThreadedEpsgFactory}.
* This is used for implementation of {@linkplain #main main method} in order to check
* the content of the {@value #FILENAME} file (or whatever property file used as backing
* store for this factory) from the command line.
*
* @param out The writer where to print the report.
* @return The set of duplicated codes.
* @throws FactoryException if an error occured.
*
* @since 2.4
*/
protected Set reportDuplicatedCodes(final PrintWriter out) throws FactoryException {
final AbstractAuthorityFactory sqlFactory = getFactory(ThreadedEpsgFactory.class);
final Vocabulary resources = Vocabulary.getResources(null);
out.println(resources.getLabel(VocabularyKeys.COMPARE_WITH));
try {
final IndentedLineWriter w = new IndentedLineWriter(out);
w.setIndentation(4);
w.write(sqlFactory.getBackingStoreDescription());
w.flush();
} catch (IOException e) {
// Should never happen, since we are writting to a PrintWriter.
throw new AssertionError(e);
}
out.println();
final Set<String> wktCodes = this. getAuthorityCodes(IdentifiedObject.class);
final Set<String> sqlCodes = sqlFactory.getAuthorityCodes(IdentifiedObject.class);
final Set<String> duplicated = new TreeSet<String>();
for (String code : wktCodes) {
code = code.trim();
if (sqlCodes.contains(code)) {
duplicated.add(code);
/*
* Note: we don't use wktCodes.retainsAll(sqlCode) because the Set implementations
* are usually not the standard ones, but rather some implementations backed
* by a connection to the resources of the underlying factory. We also close
* the connection after this loop for the same reason. In addition, we take
* this opportunity for sorting the codes.
*/
}
}
if (duplicated.isEmpty()) {
out.println(resources.getString(VocabularyKeys.NO_DUPLICATION_FOUND));
} else {
for (final String code : duplicated) {
out.print(resources.getLabel(VocabularyKeys.DUPLICATED_VALUE));
out.println(code);
}
}
return duplicated;
}
/**
* Prints a list of CRS that can't be instantiated. This is used for implementation of
* {@linkplain #main main method} in order to check the content of the {@value #FILENAME}
* file (or whatever property file used as backing store for this factory) from the command
* line.
*
* @param out The writer where to print the report.
* @return The set of codes that can't be instantiated.
* @throws FactoryException if an error occured while
* {@linkplain #getAuthorityCodes fetching authority codes}.
*
* @since 2.4
*/
protected Set reportInstantiationFailures(final PrintWriter out) throws FactoryException {
final Set<String> codes = getAuthorityCodes(CoordinateReferenceSystem.class);
final Map<String,String> failures = new TreeMap<String,String>();
for (final String code : codes) {
try {
createCoordinateReferenceSystem(code);
} catch (FactoryException exception) {
failures.put(code, exception.getLocalizedMessage());
}
}
if (!failures.isEmpty()) {
final TableWriter writer = new TableWriter(out, " ");
for (final Map.Entry<String,String> entry : failures.entrySet()) {
writer.write(entry.getKey());
writer.write(':');
writer.nextColumn();
writer.write(entry.getValue());
writer.nextLine();
}
try {
writer.flush();
} catch (IOException e) {
// Should not happen, since we are writting to a PrintWriter
throw new AssertionError(e);
}
}
return failures.keySet();
}
/**
* Prints a list of codes that duplicate the ones provided in the {@link ThreadedEpsgFactory}.
* The factory tested is the one registered in {@link ReferencingFactoryFinder}. By default,
* this is this {@code FactoryUsingWKT} class backed by the {@value #FILENAME} property file.
* This method can be invoked from the command line in order to check the content of the
* property file. Valid arguments are:
* <p>
* <table>
* <tr><td>{@code -test}</td><td>Try to instantiate all CRS and reports any failure
* to do so.</td></tr>
* <tr><td>{@code -duplicated}</td><td>List all codes from the WKT factory that are
* duplicating a code from the SQL factory.</td></tr>
* </table>
*
* @param args Command line arguments.
* @throws FactoryException if an error occured.
*
* @since 2.4
*/
public static void main(final String[] args) throws FactoryException {
main(args, FactoryUsingWKT.class);
}
/**
* Implementation of the {@link #main} method, shared by subclasses.
*/
static void main(String[] args, final Class<? extends FactoryUsingWKT> type)
throws FactoryException
{
final Arguments arguments = new Arguments(args);
Locale.setDefault(arguments.locale);
final boolean duplicated = arguments.getFlag("-duplicated");
final boolean instantiate = arguments.getFlag("-test");
args = arguments.getRemainingArguments(0);
final FactoryUsingWKT factory = getFactory(type);
if (duplicated) {
factory.reportDuplicatedCodes(arguments.out);
}
if (instantiate) {
factory.reportInstantiationFailures(arguments.out);
}
factory.dispose();
}
}