/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.internal.io;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.prefs.Preferences;
import org.apache.sis.internal.system.OS;
import org.geotoolkit.lang.Workaround;
import org.apache.sis.util.logging.Logging;
/**
* Methods related to the Geotoolkit.org installation directory. This is provided for data that need
* to be saved in a user-specified directory. If the user didn't specified any directory, they
* will be saved in the temporary directory.
* <p>
* We try to keep the configuration options to a strict minimum, but we still need is some case
* to specify in which directory are stored the data, for example the NADCON data used for datum
* shift over United States.
*
* @author Martin Desruisseaux (Geomatys)
* @module
*/
public enum Installation {
/**
* The root directory of Geotoolkit.org installation.
*/
ROOT_DIRECTORY("org/geotoolkit", "Root directory", null),
/**
* The directory where to put configuration files for the tests.
* This is used only during Maven builds, for example in order to
* fetch connection parameters to a database.
* <p>
* This field is read by {@link org.geotoolkit.test.image.ImageTestBase} using Java reflection.
* This needs to be keep in mind in case of refactoring, since refactoring tools may not detect
* this case.
*
* @since 3.10
*/
TESTS(null, null, "Tests"),
/**
* Where to store the metadata database used by SIS. Used only if the user did not set the
* {@code SIS_DATA} environment variable or the {@code "derby.system.home"} property.
*/
SIS("org/geotoolkit/metadata", "SIS", "Databases"),
/**
* The parameters required for a connection to a distant EPSG database.
*/
EPSG("org/geotoolkit/referencing/factory", "EPSG", "EPSG"),
/**
* The directory of the properties file which contains the parameters required for a
* connection to a distant Coverages database.
*
* @since 3.11
*/
COVERAGES("org/geotoolkit/coverage/sql", "Database", "Coverages");
/**
* The user configuration file for a connection to a JDBC database.
*
* @since 3.11
*/
public static final String DATASOURCE_FILE = "DataSource.properties";
/**
* The preference node and key for storing the value of this configuration option.
* May be {@code null} if the value should not be stored in any preference node.
*/
private final String node, key;
/**
* The default subdirectory in the root directory, or
* {@code null} if this key is for the root directory.
*/
private final String directory;
/**
* The default root directory. Computed only once at class initialization time in
* order to make sure that the value stay consistent during all the JVM execution.
*/
private static final Path DEFAULT_ROOT = root();
/**
* Whatever we are allowed to check for system preferences. This can be set to {@code false}
* for avoiding the "<cite>can not flush system preferences in {@code /etc/.java/}</cite>"
* warning on Linux.
* <p>
* Note that this field applies only to read operations. If any write operations is requested,
* then those operations will be performed like usual regardless the value of this field.
*
* @since 3.10
*/
@Workaround(library="JDK", version="1.6")
public static volatile boolean allowSystemPreferences = true;
/**
* Creates a new configuration key.
*
* @param node The preference node where to store the configuration value.
* @param key The key where to store the value in the above node.
* @param directory The default subdirectory in the root directory.
*/
private Installation(final String node, final String key, final String directory) {
this.node = node;
this.key = key;
this.directory = directory;
}
/**
* Returns the preferences node.
*/
private Preferences preference(final boolean userSpecific) {
return (userSpecific ? Preferences.userRoot() : Preferences.systemRoot()).node(node);
}
/**
* Sets the preference to the given value. If the preference is set for the current user,
* then the system preference is left untouched. But if the preference is set for the system,
* we assume that it applies to all users including the current one, so the current user
* preference is removed.
*
* @param userSpecific {@code true} for user preference, or {@code false} for system preference.
* @param value The preference value, or {@code null} for removing it.
*/
public final void set(final boolean userSpecific, final String value) {
try {
final Preferences prefs = preference(userSpecific);
if (value != null) {
prefs.put(key, value);
} else {
prefs.remove(key);
}
if (!userSpecific) {
preference(true).remove(key);
}
} catch (SecurityException e) {
Logging.recoverableException(Logging.getLogger("org.geotoolkit"), Installation.class, "set", e);
}
}
/**
* Returns the preference, or {@code null} if none.
*
* @param userSpecific {@code true} for user preference, or {@code false} for system preference.
* @return The preference value, or {@code null} if none.
*/
public final String get(final boolean userSpecific) {
try {
if (key != null) {
if (userSpecific || allowSystemPreferences) {
return preference(userSpecific).get(key, null);
}
}
} catch (SecurityException e) {
Logging.recoverableException(Logging.getLogger("org.geotoolkit"), Installation.class, "set", e);
}
return null;
}
/**
* Returns the default root directory, ignoring user's preferences.
* This method is used only for the initialization of the {@link #DEFAULT_ROOT}
* static constant.
*
* @return The default installation root directory.
*/
private static Path root() {
try {
final OS system = OS.current();
if (system == OS.WINDOWS) {
final String app = System.getenv("APPDATA");
if (app != null) {
final Path file = Paths.get(app);
if (Files.isDirectory(file)) {
return file.resolve("Geotoolkit.org");
}
}
}
final String directory = System.getProperty("user.home");
if (directory != null) {
Path file = Paths.get(directory);
String name = ".geotoolkit.org";
switch (system) {
case WINDOWS: {
file = file.resolve("Application Data");
name = "Geotoolkit.org";
break;
}
case MAC_OS: {
file = file.resolve("Library");
name = "Geotoolkit.org";
break;
}
// For Linux and unknown OS, keep the directory selected above.
}
if (Files.isDirectory(file) && (!system.unix || Files.isWritable(file))) {
return file.resolve(name);
}
}
} catch (SecurityException e) {
Logging.getLogger("org.geotoolkit").warning(e.toString());
}
return Paths.get(System.getProperty("java.io.tmpdir"), "Geotoolkit.org");
}
/**
* If the preference is defined, returns its value as a {@link Path}. Otherwise returns a
* sub-directory of the <cite>root directory</cite> where the later is defined as the first
* of the following directories which is found suitable:
* <p>
* <ul>
* <li>{@link #ROOT_DIRECTORY} user preferences, if defined.</li>
* <li>{@link #ROOT_DIRECTORY} system preferences, if defined.</li>
* <li>{@code ".geotoolkit"} subdirectory in the user home directory,
* if the user home directory exists and is writable.</li>
* <li>{@code "Geotoolkit"} subdirectory in the temporary directory.</li>
* </ul>
*
* @param usePreferences Usually {@code true}. If {@code false}, the preferences
* are ignored and only the default directory is returned.
* @return The directory (never {@code null}).
*/
public Path directory(final boolean usePreferences) {
if (usePreferences) {
boolean user = true;
do {
final String candidate = get(user);
if (candidate != null) {
return Paths.get(candidate);
}
} while ((user = !user) == false);
}
if (directory != null) {
return ROOT_DIRECTORY.directory(true).resolve(directory);
} else {
return DEFAULT_ROOT;
}
}
/**
* Same as {@link #directory}, but creates the directory if it doesn't already exist.
*
* @param usePreferences Usually {@code true}. If {@code false}, the preferences
* are ignored and only the default directory is returned.
* @return The default directory.
* @throws IOException If the subdirectory can't be created.
*/
public Path validDirectory(final boolean usePreferences) throws IOException {
final Path directory = directory(usePreferences);
if (!Files.isDirectory(directory)) {
Files.createDirectories(directory);
}
return directory;
}
/**
* Returns the content of the {@value #DATASOURCE_FILE} file as a properties map,
* or {@code null} if the file does not exist.
*
* @return The content of the {@value #DATASOURCE_FILE} file, or {@code null}.
* @throws IOException If an error occurred while reading the file.
*
* @since 3.11
*/
public Properties getDataSource() throws IOException {
final Path file = directory(true).resolve(DATASOURCE_FILE);
if (Files.isRegularFile(file)) {
final Properties properties;
try (InputStream in = Files.newInputStream(file)) {
properties = new Properties();
properties.load(in);
}
return properties;
}
return null;
}
}