diff --git a/build/release.sh b/build/release.sh new file mode 100644 index 0000000..669ac0a --- /dev/null +++ b/build/release.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +# +# Create release of the project, which means a tag on a separate branch, with a fixed version. +# NOTE : we expect this script to be in the following directory : ROOT_POM_DIR/build/ +# + +# Release version for the tag to create +releaseVersion= + +# change SNAPSHOT version after release. +newSnapshot= + +# A prefix for the temporary release branch +releaseBranchPrefix="release-" + +# Keep original branch to come back to it after release. +sourceBranch= + +# Original code from 'http://stackoverflow.com/questions/242538/unix-shell-script-find-out-which-directory-the-script-file-resides' +# Absolute path to this script, e.g. /home/user/bin/foo.sh +SCRIPT=$(readlink -f "$BASH_SOURCE") +# Absolute path this script is in, thus /home/user/bin +SCRIPT_PATH=$(dirname "$SCRIPT") + +###################### FUNCTIONS ############################ + +# Print help on standard output. +function printHelp { + echo " + Create a release tag for the current project. Release number will be something like X.x where X is major release version, and x is minor. + " + echo " WARNING : No check is done to ensure the project is well-formed (no compilation, test, syntax check, etc.) + " + echo " MANDATORY ARGUMENT : " + echo " -r --release-version : Version to set on new release." + echo " OPTIONAL ARGUMENT : " + echo " -s --snapshot-version : If set, and once the tag is done, the version on original branch is changed according to this argument value." + echo " -- help, -h : Print this help." +} + +function printError() { + echo " + " + tput setaf 1; tput setab 7; tput bold; + echo "$*" + tput sgr0; + echo " + " +} + +# Print the given parameters to standard error output, then exit the script in failure mode. +function error() { + printError "$*" + git checkout $sourceBranch # come back on the initial branch before exit + exit 1 +} + +# do the job +function execute { + # We place ourself in the script directory. It MUST BE into the git project directory. + cd $SCRIPT_PATH/.. + + # Save original branch name to go back to it at the end of the release. + sourceBranch="$(git symbolic-ref --short HEAD)" + echo "Original branch is $sourceBranch" + createTag + updateSnapshot +} + +# Create the tag itself +function createTag { + echo "Create temporary branch for release $releaseVersion" + tmpReleaseBranch="$releaseBranchPrefix$releaseVersion" + git branch $tmpReleaseBranch || error "Cannot create a temporary branch for release creation." + git checkout $tmpReleaseBranch || error "Cannot access created branch $tmpReleaseBranch" + + echo "Perform version update for release $releaseVersion" + mvn versions:set -DnewVersion=$releaseVersion || error "Cannot update project version to $releaseVersion" + + echo "Commit release versions" + git commit -m "Project release $releaseVersion" -a || error "Cannot commit updated poms to the temporary release branch" + + echo "Create tag named $releaseVersion" + git tag $releaseVersion $tmpReleaseBranch || error "Cannot create the tag $releaseVersion" + + echo "Remove temporary release branch" + git checkout $sourceBranch || error "Cannot come back to the source branch $sourceBranch" + git branch -D $tmpReleaseBranch || printError "WARNING : Cannot delete temporary branch $tmpReleaseBranch. You will have to do it yourself ! " +} + +# Update project version on initial branch +function updateSnapshot { + if [ ! -z "$releaseVersion" ] + then + echo "Update snapshot version $newSnapshot" + mvn versions:set -DnewVersion=$newSnapshot || error "Cannot update project version to $newSnapshot" + + echo "Commit updated snapshot" + git commit -m "Snapshot update $newSnapshot" -a || error "Cannot commit updated poms to the source branch" + fi +} + +########################### MAIN ############################# + +# ARGUMENT CHECK +while [ "$1" != "" ]; do + case $1 in + -r | --release-version ) shift + releaseVersion=$1 + ;; + -s | --snapshot-version ) shift + newSnapshot=$1 + ;; + # etc. + -h | --help ) printHelp + exit + ;; + * ) printHelp + exit 1 + ;; + esac + shift +done + + +if [ -z "$releaseVersion" ] + then echo "ERROR: MINOR RELEASE VERSION IS NOT AN INTEGER. FOUND VALUE --> $releaseMinor" >&2; exit 1 +fi + +# We must ensure that no tag already exists with the same name. +tagDoublon="$(git tag -l|grep $releaseVersion)" + +if [ ! -z "$tagDoublon" ] + then echo "ERROR : A tag already exists for the following version : $releaseVersion" >&2; exit 1 +fi + +echo "Execute for version $releaseVersion" + +# Make release +execute \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml new file mode 100644 index 0000000..4834088 --- /dev/null +++ b/core/pom.xml @@ -0,0 +1,167 @@ + + + 4.0.0 + + + fr.cenra.rhomeo + rhomeo + 1.2-SNAPSHOT + + + fr.cenra.rhomeo + core + jar + Core + + + + javax.validation + validation-api + + + + org.hibernate + hibernate-validator + + + + javax.el + javax.el-api + + + + org.glassfish.web + javax.el + + + + org.springframework + spring-context + + + + com.fasterxml.jackson.core + jackson-databind + + + + + commons-net + commons-net + 3.4 + + + commons-codec + commons-codec + + + + + org.geotoolkit + geotk-feature-shapefile + ${geotoolkit.version} + + + org.geotoolkit + geotk-feature-kml + ${geotoolkit.version} + + + org.geotoolkit + geotk-feature-geojson + ${geotoolkit.version} + + + org.codehaus.groovy + groovy-all + + + + + + org.geotoolkit + geotk-feature-store + ${geotoolkit.version} + + + org.codehaus.groovy + groovy-all + + + + + + org.geotoolkit + geotk-feature-csv + ${geotoolkit.version} + + + + org.geotoolkit + geotk-client-wfs + + + org.geotoolkit + geotk-jaxp-xsd + + + + org.apache.derby + derby + + + + + javax.media + jai_core + + + javax.media + jai_codec + + + javax.media + jai_imageio + + + + + junit + junit + test + + + + org.apache.sis.core + sis-utility + test-jar + test + + + + org.springframework + spring-test + test + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + test-jar + + + + + + + + diff --git a/core/src/main/java/fr/cenra/rhomeo/api/EditableIdentifiedObject.java b/core/src/main/java/fr/cenra/rhomeo/api/EditableIdentifiedObject.java new file mode 100644 index 0000000..ed0beaf --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/EditableIdentifiedObject.java @@ -0,0 +1,93 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class EditableIdentifiedObject implements IdentifiedObject { + + protected final SimpleStringProperty nameProperty = new SimpleStringProperty(); + protected final SimpleStringProperty remarksProperty = new SimpleStringProperty(); + protected ObservableList alias; + + @Override + public String getName() { + return nameProperty.get(); + } + + public void setName(final String newName) { + nameProperty.set(newName); + } + + public StringProperty nameProperty() { + return nameProperty; + } + + @Override + public ObservableList getAlias() { + if (alias == null) { + alias = FXCollections.observableArrayList(); + } + return alias; + } + + public void setAlias(final ObservableList alias) { + this.alias = alias; + } + + @Override + public String getRemarks() { + return remarksProperty.get(); + } + + public void setRemarks(final String remarks) { + remarksProperty.set(remarks); + } + + public StringProperty remarksProperty() { + return remarksProperty; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/IdentifiedObject.java b/core/src/main/java/fr/cenra/rhomeo/api/IdentifiedObject.java new file mode 100644 index 0000000..0ae14e4 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/IdentifiedObject.java @@ -0,0 +1,88 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api; + +import java.util.Collection; +import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.NotBlank; + +/** + * A simplified version of {@link org.opengis.referencing.IdentifiedObject} from + * GeoAPI. The aim is to ease manipulation by working only with Strings. + * + * Note : The {@link #getName() } is the identifier of the object. + * The {@link #getAlias() } is a list of alternative names to use as proper title. + * By default, the {@link #getTitle() } method returns the first alias available, + * or the name of the object if none is available. + * + * @author Alexis Manin (Geomatys) + */ +public interface IdentifiedObject { + + /** + * + * @return Identifier of the object. + */ + @NotBlank + String getName(); + + /** + * + * @return Available titles / alternative names. Can be empty, but should never be null. + */ + @NotNull + Collection getAlias(); + + /** + * + * @return A short description of the object. + */ + String getRemarks(); + + /** + * + * @return The most appropriate title from available aliases. + */ + default String getTitle() { + if (getAlias().isEmpty()) { + return getName(); + } + return getAlias().iterator().next(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/InternationalDescription.java b/core/src/main/java/fr/cenra/rhomeo/api/InternationalDescription.java new file mode 100644 index 0000000..66a0c75 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/InternationalDescription.java @@ -0,0 +1,123 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.logging.Level; + +/** + * An object able to provide some title and description for given (or default) locales. + * + * This interface provides a default implementation based on the class {@link ResourceBundle}. + * + * According to the default implementation, the title and the description are the values + * of the keys {@link InternationalDescription#LABEL_KEY} and {@link InternationalDescription#DESCRIPTION_KEY} respectively. + * + * @author Samuel Andrés (Geomatys) + */ +public interface InternationalDescription { + + /** + * The bundle key of the title value provided by the default implementation. + */ + String LABEL_KEY = "_label"; + + /** + * The bundle key of the description value provided by the default implementation. + */ + String DESCRIPTION_KEY = "_description"; + + /** + * Gets a title for the object. + * + * Default implementation returns the value of the {@link InternationalDescription#LABEL_KEY} + * key stored into the class {@link ResourceBundle} for the given {@link Locale}. + * + * @param locale + * @return + */ + default String getLabel(final Locale locale) { + try { + final ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName(), locale); + return bundle.getString(LABEL_KEY); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot find any title for this object.", e); + return "No title available"; + } + } + + /** + * Gets a description for the object. + * + * Default implementation returns the value of the {@link InternationalDescription#DESCRIPTION_KEY} + * key stored into the class {@link ResourceBundle} for the given {@link Locale}. + * + * @param locale + * @return + */ + default String getDescription(final Locale locale){ + try { + final ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName(), locale); + return bundle.getString(DESCRIPTION_KEY); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot find any title for this object.", e); + return "No title available"; + } + } + + /** + * Shortcut method to avoid to specify locale parameter. + * + * Default implementation maps the {@link Locale#getDefault() } {@link Locale}. + * + * @return + */ + default String getLabel(){return getLabel(Locale.getDefault());} + + /** + * Shortcut method to avoid to specify locale parameter. + * + * Default implementation maps the {@link Locale#getDefault() } {@link Locale}. + * + * @return + */ + default String getDescription(){return getDescription(Locale.getDefault());} +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/InternationalResource.java b/core/src/main/java/fr/cenra/rhomeo/api/InternationalResource.java new file mode 100644 index 0000000..0452e7b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/InternationalResource.java @@ -0,0 +1,249 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.StringJoiner; +import java.util.logging.Level; +import org.apache.sis.util.ArgumentChecks; + +/** + * An object able to provide some resource strings for given (or default) locales. + * + * This interface provides a default implementation based on the class + * {@link ResourceBundle}. So, if you do not redefine its methods, make sure + * the {@link ResourceBundle} associated to the class exists. + * + * @author Samuel Andrés (Geomatys) + */ +public interface InternationalResource { + + /** + * Default {@link InternationalResource} behaviour for a given class and a + * given locale, retrieving the associated {@link ResourceBundle}. + * + * The resource can be associated to a simple key or a complex one. + * + * The default implementation assumes the key parts are separated by a dot. + * + * For instance, let's assume the class my.package.MyClass implementq + * {@link InternationalResource} without overriding methods and is + * associated to the following resource bundle : /my/package/MyClass.properties + * which content is below : + * + * keyPart1.keyPart2=resource value + * + * The resource string "resource value" can be retrived from a my.package.MyClass + * instance, calling this method either specifying only one key part + * "keyPart1.keyPart2", or two key parts "keyPart1" and "keyPart2". + * + * @param c + * @param locale + * @param keyParts + * @return + * + * @throws IllegalArgumentException if the class parameter or the locale is null or no key is provided + * @throws MissingResourceException if no resource bundle for the specified class can be found or no object for the key can be found + */ + static String getResourceString(final Class c, final Locale locale, final String... keyParts){ + ArgumentChecks.ensureNonNull("class", c); + ArgumentChecks.ensureNonNull("locale", locale); + ArgumentChecks.ensureNonNull("keyParts", keyParts); + + final ResourceBundle bundle; + try{ + bundle = ResourceBundle.getBundle(c.getName(), locale); + } + catch(MissingResourceException e){ + // Warns about InternationalResource default behaviour is not fulfilled. + RhomeoCore.LOGGER.log(Level.WARNING, "No resource found for the class {0}. Default {1} behaviour use resource bundle.", + new Object[]{c.getCanonicalName(), InternationalResource.class.getSimpleName()}); + // Then, re-throw the exception… + throw e; + } + + final StringJoiner joiner = new StringJoiner("."); + for(final String keyPart : keyParts){ + joiner.add(keyPart); + } + return bundle.getString(joiner.toString()); + } + + /** + * + * @param c + * @param keyParts + * @return + * + * @see InternationalResource#getResourceString(java.lang.Class, java.util.Locale, java.lang.String...) , with default locale. + * + * @throws IllegalArgumentException if the class parameter is null or no key is provided + * @throws MissingResourceException if no resource bundle for the specified class can be found or no object for the key can be found + */ + static String getResourceString(final Class c, final String... keyParts){ + return getResourceString(c, Locale.getDefault(), keyParts); + } + + /** + * Use the default behaviour to retrieve the property mapping the given key for + * the given class before formating the result using the given arguments. + * + * Note the key cannot be split into dot-separated key parts because of the + * arguments vararg parameter. + * + * @param c + * @param locale + * @param key + * @param arguments + * @return + * + * @see InternationalResource#getResourceString(java.lang.Class, java.util.Locale, java.lang.String...) , with default locale. + * + * @throws IllegalArgumentException if the class parameter or the locale are null or no key is provided + * @throws MissingResourceException if no resource bundle for the specified class can be found or no object for the key can be found + */ + static String getFormatedResourceString(final Class c, final Locale locale, final String key, final Object... arguments){ + return MessageFormat.format(getResourceString(c, locale, key), arguments); + } + + /** + * + * Same behaviour as {@link InternationalResource#getFormatedResourceString(java.lang.Class, java.util.Locale, java.lang.String, java.lang.Object...) }, + * with default locale. + * + * @param c + * @param key + * @param arguments + * @return + * + * @see InternationalResource#getResourceString(java.lang.Class, java.lang.String...). + * + * @throws IllegalArgumentException if no key is provided + * @throws MissingResourceException if no object for the key can be found + */ + static String getFormatedResourceString(final Class c, final String key, final Object... arguments){ + return MessageFormat.format(getResourceString(c, key), arguments); + } + + /** + * Retrieves a string resource for the given locale. + * + * The resource can be associated to a simple key or a complex one. + * + * The default implementation assumes the key parts are separated by a dot. + * + * For instance, let's assume the class my.package.MyClass implementq + * {@link InternationalResource} without overriding methods and is + * associated to the following resource bundle : /my/package/MyClass.properties + * which content is below : + * + * keyPart1.keyPart2=resource value + * + * The resource string "resource value" can be retrived from a my.package.MyClass + * instance, calling this method either specifying only one key part + * "keyPart1.keyPart2", or two key parts "keyPart1" and "keyPart2". + * + * @param keyParts + * @param locale + * @return + * + * @throws IllegalArgumentException if the locale is null or no key is provided + * @throws MissingResourceException if no resource bundle can be found for the class of the current object or no object for the key can be found + */ + default String getResourceString(final Locale locale, final String... keyParts) { + return getResourceString(getClass(), locale, keyParts); + } + + /** + * Shortcut method to avoid to specify locale parameter. + * + * Default implementation maps the {@link Locale#getDefault() } {@link Locale}. + * + * @param keyParts + * @return + * + * @see InternationalResource#getResourceString(java.util.Locale, java.lang.String...) ...). + * + * @throws IllegalArgumentException if no key is provided + * @throws MissingResourceException if no resource bundle can be found for the class of the current object or no object for the key can be found + */ + default String getResourceString(final String... keyParts) { + return getResourceString(Locale.getDefault(), keyParts); + } + + /** + * Retrieves a string resource for the given locale and formats the result + * using the given arguments. + * + * @param locale + * @param key + * @param arguments + * @return + * + * @see InternationalResource#getFormatedResourceString(java.lang.Class, java.util.Locale, java.lang.String, java.lang.Object...) + * + * @throws IllegalArgumentException if the locale is null or no key is provided + * @throws MissingResourceException if no resource bundle for the class can be found for the class of the current object or no object for the key can be found + */ + default String getFormatedResourceString(final Locale locale, final String key, final Object... arguments) { + return getFormatedResourceString(getClass(), locale, key, arguments); + } + + /** + * Shortcut method to avoid to specify locale parameter. + * + * Default implementation maps the {@link Locale#getDefault() } {@link Locale}. + * + * @param key + * @param arguments + * @return + * + * @see InternationalResource#getFormatedResourceString(java.util.Locale, java.lang.String, java.lang.Object...) ...). + * + * @throws IllegalArgumentException if no key is provided + * @throws MissingResourceException if no resource bundle for the class can be found for the class of the current object or no object for the key can be found + */ + default String getFormatedResourceString(final String key, final Object... arguments) { + return getFormatedResourceString(Locale.getDefault(), key, arguments); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/Version.java b/core/src/main/java/fr/cenra/rhomeo/api/Version.java new file mode 100644 index 0000000..d6e4886 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/Version.java @@ -0,0 +1,138 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api; + +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.apache.sis.util.logging.Logging; + +/** + * A comparable to hold version values and allow for comparison between them. + * This object will try to split an input string to extract a suite of digits, + * which will compose the version. The weight of each digit in the suit is + * determined by its encounter order in the string. First encountered are priorized + * over the last ones. + * + * Ex : the String "1.2.3" will result in the following digit suite : 1 2 3, + * where 1 is the major version, 2 the minor version, and 3 the update version. + * + * @author Alexis Manin (Geomatys) + */ +public class Version implements Comparable { + + private static final Logger LOGGER = Logging.getLogger("fr.cenra.core"); + + final String stringVersion; + final int[] version; + + public Version(String inputVersion) { + stringVersion = inputVersion == null? "" : inputVersion; + + String[] splitted = stringVersion.split("[^\\d]+"); + if (splitted.length < 1) { + version = new int[0]; + } else { + int[] tmpVersion = new int[splitted.length]; + try { + for (int i = 0; i < splitted.length; i++) { + tmpVersion[i] = Integer.parseInt(splitted[i]); + } + } catch (NumberFormatException e) { + LOGGER.log(Level.WARNING, "Input version cannot be split as a suite of numbers : "+ inputVersion, e); + tmpVersion = new int[0]; + } + + version = tmpVersion; + } + } + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Version o) { + if (o == null) { + return -1; + } else if (version.length < 1 || o.version.length < 1) { + return stringVersion.compareTo(o.stringVersion); + } else { + final int maxIndex = Math.min(version.length, o.version.length); + int comparison; + for (int i = 0 ; i < maxIndex ; i++) { + comparison = version[i] - o.version[i]; + if (comparison != 0) { + return comparison; + } + } + + // if we arrived here, common version parts are equals. The longest should be the last version. + return version.length - o.version.length; + } + } + + /** + * A string representing current application version. + * + * @return current application version. + */ + @Override + public String toString() { + return stringVersion; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Version) { + return compareTo((Version) obj) == 0; + } + return false; + } + + @Override + public int hashCode() { + if (version.length < 1) { + return Arrays.hashCode(version); + } else { + return stringVersion.hashCode(); + } + } + + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/annotations/RefersTo.java b/core/src/main/java/fr/cenra/rhomeo/api/annotations/RefersTo.java new file mode 100644 index 0000000..5c9ad3c --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/annotations/RefersTo.java @@ -0,0 +1,98 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.annotations; + +import fr.cenra.rhomeo.core.validation.ReferenceValidator; +import java.beans.Introspector; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.Predicate; +import javax.validation.Constraint; +import javax.validation.Payload; + +/** + * Describe a link between the source (annotated) property, and another one, + * located in the specified class. This annotation should be positioned on a + * property getter. + * + * IMPORTANT : we talk about properties in java.beans term, which means a data + * accessible via public getter. The property name is defined by the terms used + * to define the getter name. For more information, see {@link Introspector} + * documentation. + * + * IMPORTANT : This annotation is also a validation flag, to ensure the property + * value is referring to an existing object. However, it tests NEITHER nullity + * NOR blankness. + * + * @author Alexis Manin (Geomatys) + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Constraint(validatedBy = ReferenceValidator.class) +@Documented +public @interface RefersTo { + String message() default "Aucune correspondance trouvée"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + /** + * + * @return The class in which is located the property we're refering to. + */ + Class type(); + + /** + * + * @return The name of the property refered in {@link #type() }. + */ + String property(); + + /** + * Provides a predicate to keep only objects which respects it. + * @return A class (should not be an interface nor abstract one) from which + * a {@link Predicate} can be created using {@link Class#newInstance() }. + * + */ + Class filterClass() default Predicate.class; +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/AbstractProtocol.java b/core/src/main/java/fr/cenra/rhomeo/api/data/AbstractProtocol.java new file mode 100644 index 0000000..d1b5ad1 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/AbstractProtocol.java @@ -0,0 +1,133 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.process.Indicator; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Abstract implementation of {@link Protocol}. + * + * @author Samuel Andrés (Geomatys) + */ +public abstract class AbstractProtocol implements Protocol { + + private final String name; + private final String remarks; + protected final Class dataType; + protected final Set referenceTypes = new HashSet<>(); + + @Autowired // Need all subclasses to be Spring components. + protected List indicators; + + public AbstractProtocol(final String name, final String remarks, final Class dataType, final ReferenceDescription... referenceTypes){ + this.name = name; + this.remarks = remarks; + this.dataType = dataType; + if (referenceTypes != null && referenceTypes.length > 0) { + for (final ReferenceDescription referenceType : referenceTypes) { + if (referenceType != null) { + this.referenceTypes.add(referenceType); + } + } + } + } + + @Override + public Class getDataType() { + return dataType; + } + + @Override + public Set getReferenceTypes() { + return referenceTypes; + } + + @Override + public String getName() { + return name; + } + + @Override + public Collection getAlias() { + return Collections.singleton(name); + } + + @Override + public String getRemarks() { + return remarks; + } + + /** + * Default implementation checking at least one {@link Indicator} exists + * referencing this {@link Protocol} instance and compatible with the given + * {@link Site} zone type code. + * + * @param site + * @return + */ + @Override + public boolean isCompatible(Site site) { + if (site == null) { + return false; + } + + for(final Indicator indicator : indicators){ + if(indicator.getProtocol()==this && indicator.getZoneTypeCodes() != null + && indicator.getZoneTypeCodes().contains(site.getZoneType())) return true; + } + return false; + } + + @Override + public int compareTo(final Protocol p){ + return this.getName().compareTo(p.getName()); + } + + @Override + public String toString() { + return "AbstractProtocol{" + "name=" + name + ", remarks=" + remarks + '}'; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/DataContext.java b/core/src/main/java/fr/cenra/rhomeo/api/data/DataContext.java new file mode 100644 index 0000000..984d52d --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/DataContext.java @@ -0,0 +1,132 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.Version; +import java.time.ZonedDateTime; +import java.util.HashMap; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import javax.validation.constraints.NotNull; +import org.apache.sis.util.ArgumentChecks; + +/** + * Contains all data from the seizure context : + * - Target Site + * - Input protocol + * - References versions + * - Additional user data + * + * @author Alexis Manin (Geomatys) + */ +public class DataContext { + + private final Site site; + private final Protocol protocol; + + private final SimpleObjectProperty dateProperty; + + private final ObservableMap, Version> references; + + private final ObservableMap userData; + + public DataContext(Site site, Protocol protocol) { + ArgumentChecks.ensureNonNull("Input site", site); + ArgumentChecks.ensureNonNull("Source protocol", protocol); + this.site = site; + this.protocol = protocol; + dateProperty = new SimpleObjectProperty<>(ZonedDateTime.now()); + references = FXCollections.observableMap(new HashMap<>()); + userData = FXCollections.observableMap(new HashMap<>()); + } + + /** + * + * @return Protocol currently used for seizure. + */ + @NotNull + public Protocol getProtocol() { + return protocol; + } + + /** + * + * @return Site chosen for current session. + */ + @NotNull + public Site getSite() { + return site; + } + + /** + * + * @return References used for this seizure, along with the chosen version for + * each of them. + */ + @NotNull + public ObservableMap, Version> getReferences() { + return references; + } + + /** + * + * @return References used for this seizure, along with the chosen version for + * each of them. + */ + @NotNull + public ObservableMap getUserData() { + return userData; + } + /** + * + * @return Creation date of this dataset. + */ + public ZonedDateTime getDate() { + return dateProperty.get(); + } + + /** + * Set given date as creation date of this dataset. + * @param date The date to use as new creation date. + */ + public void setDate(final ZonedDateTime date) { + dateProperty.set(date); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Dataset.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Dataset.java new file mode 100644 index 0000000..c17aaf7 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Dataset.java @@ -0,0 +1,589 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.lang.reflect.Method; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.logging.Level; +import javafx.beans.Observable; +import javafx.beans.property.Property; +import javafx.beans.value.ChangeListener; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; +import javafx.collections.transformation.SortedList; +import javafx.util.Callback; +import javax.validation.Valid; +import javax.validation.constraints.NotNull; +import org.apache.sis.util.ArgumentChecks; + +/** + * Data filled or imported in application. + * + * IMPORTANT : Avoid modifying {@link #getItems() } list outside of FX thread, + * because UI could use filtered / sorted lists based on it. + * + * @author Alexis Manin (Geomatys) + * @param Type of statements pointed by this dataset. + */ +public class Dataset { + + private final Protocol protocol; + + /** + * Keep a set of computed tracking points to speed up research and updates. + * + * TODO : replace with a map whose key is the tracking point, and value is the count of attached statements. + */ + private ObservableSet internalPoints; + /** + * List of tracking points exposed to user. + */ + private ObservableList points; + /** + * Seized statements. + */ + private final ObservableList items; + + /** + * When an item date change, this listener update available tracking point list. + */ + private final ChangeListener dateListener; + /** + * When an item name change, this listener update available tracking point list. + */ + private final ChangeListener nameListener; + /** + * On a change in {@link #getItems() }, this listener check added/removed + * objects to update tracking point list. + */ + private final TrackingPointListener tPointListener; + private SetToListListener mirroring; + + public Dataset(final Protocol target) { + ArgumentChecks.ensureNonNull("Target protocol", target); + protocol = target; + + final Callback extractor = createCallback(); + if (extractor == null) { + items = FXCollections.observableArrayList(); + } else { + items = FXCollections.observableArrayList(extractor); + } + + dateListener = (obs, oldDate, newDate) -> { + if (obs instanceof Property) { + Object bean = ((Property)obs).getBean(); + if (bean instanceof Statement) { + final String name = ((Statement)bean).getTrackingPoint(); + if (name != null) { + if (oldDate != null) { + final TrackingPoint p = protocol.createTrackingPoint(name, oldDate); + if (getSubSet(p).isEmpty()) { + internalPoints.remove(p); + } + } + if (newDate != null) { + internalPoints.add(protocol.createTrackingPoint(name, newDate)); + } + } + } + } + }; + + nameListener = (obs, oldName, newName) -> { + if (obs instanceof Property) { + Object bean = ((Property) obs).getBean(); + if (bean instanceof Statement) { + final LocalDate date = ((Statement) bean).getDate(); + if (date != null) { + if (oldName != null) { + final TrackingPoint p = protocol.createTrackingPoint(oldName, date); + if (getSubSet(p).isEmpty()) { + internalPoints.remove(p); + } + } + if (newName != null) { + internalPoints.add(protocol.createTrackingPoint(newName, date)); + } + } + } + } + }; + + tPointListener = new TrackingPointListener(); + } + + /** + * + * @return List of associated data. Can be empty, but never null. + */ + @NotNull + @Valid + public synchronized ObservableList getItems() { + return items; + } + + @Valid + public Protocol getProtocol() { + return protocol; + } + + private Callback createCallback() { + final ArrayList extractors = new ArrayList<>(); + try { + final BeanInfo info = Introspector.getBeanInfo(protocol.getDataType(), Object.class); + MethodDescriptor[] descs = info.getMethodDescriptors(); + for (final MethodDescriptor desc : descs) { + final Method method = desc.getMethod(); + if (method.getParameterCount() < 1 && Observable.class.isAssignableFrom(method.getReturnType())) { + method.setAccessible(true); + extractors.add(method); + } + } + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot analyze datatype for protocol ".concat(protocol.getName()), e); + } + + if (extractors.isEmpty()) { + return null; + } + + return input -> { + final ArrayList result = new ArrayList<>(extractors.size()); + for (int i = 0 ; i < extractors.size() ; i++) { + try { + result.add((Observable) extractors.get(i).invoke(input)); + } catch (ReflectiveOperationException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot extract a property !", e); + } + } + + return result.toArray(new Observable[result.size()]); + }; + } + + @NotNull + public synchronized ObservableList getTrackingPoints() { + if(points==null) { + final HashSet tmpSet = new HashSet<>(); + if (items != null) { + String name; + LocalDate date; + for (final Statement statement : items) { + name = statement.getTrackingPoint(); + date = statement.getDate(); + if (name != null && statement.getDate() != null) { + final TrackingPoint p = protocol.createTrackingPoint(name, date); + p.setRemarks(statement.getRemarks()); + tmpSet.add(p); + } + } + } + + internalPoints = FXCollections.synchronizedObservableSet(FXCollections.observableSet(tmpSet)); + points = FXCollections.observableArrayList(tmpSet); + removeListeners(); + addListeners(); + } + return FXCollections.unmodifiableObservableList(points); + } + + /** + * Query an unmodifiable view of the items contained in this dataset matching + * the given tracking point. All modifications in {@link #getItems() } will + * be mirrored in the returned view. + * + * If the given point is null, a list of all statements which are not bound + * to any point is returned. + * + * @param filter The tracking point to get data for. + * @return items matching a given tracking point (contained in it). + */ + @NotNull + public synchronized ObservableList getSubSet(final TrackingPoint... filter) { + return getSubSet(filter == null || (filter.length == 1 && filter[0] == null)? null : Arrays.asList(filter)); + } + + public synchronized ObservableList getSubSet(Collection filter) { + final Predicate predicate; + if (filter == null || filter.isEmpty()) { + predicate = input -> { + return input.getTrackingPoint() == null || input.getDate() == null; + }; + } else if (filter.size() == 1) { + final TrackingPoint tp = filter.iterator().next(); + final String filterName = tp.getName(); + final LocalDate filterStart = tp.getYearStart().minusDays(1); + final LocalDate filterEnd = tp.getYearEnd().plusDays(1); + predicate = input -> { + return filterName.equals(input.getTrackingPoint()) + && input.getDate() != null + && input.getDate().isAfter(filterStart) + && input.getDate().isBefore(filterEnd); + }; + } else { + // Remove doublons + if (!(filter instanceof Set)) { + filter = new HashSet(filter); + } + + // Multiple tracking point filter. For each name, we build a set of + // date range, for each tracking with the same name but different year range. + final HashMap> filters = new HashMap<>(filter.size()); + for (final TrackingPoint tp : filter) { + filters.computeIfAbsent(tp.getName(), str -> new ArrayList()) + .add(new LocalDate[]{tp.getYearStart().minusDays(1), tp.getYearEnd().plusDays(1)}); + } + + predicate = input -> { + if (input.getTrackingPoint() == null || input.getDate() == null) + return false; + + final List dates = filters.get(input.getTrackingPoint()); + if (dates == null) + return false; + + for (final LocalDate[] range : dates) { + if (input.getDate().isAfter(range[0]) && + input.getDate().isBefore(range[1])) + return true; + } + + return false; + }; + } + return getItems().filtered(predicate); + } + + public boolean deletePoints(final Collection points) { + final ArrayList defCopy = new ArrayList<>(points); + return items.removeIf(item -> { + for (final TrackingPoint tp : defCopy) { + if (tp.getName().equals(item.getTrackingPoint()) && + item.getDate() != null && + (item.getDate().isEqual(tp.getYearStart()) || item.getDate().isAfter(tp.getYearStart())) && + (item.getDate().isEqual(tp.getYearEnd()) || item.getDate().isBefore(tp.getYearEnd()))) { + return true; + } + } + return false; + }); + } + + /** + * Remove listeners in charge of updating tracking point list. + */ + private void removeListeners() { + if (mirroring != null && internalPoints != null) { + internalPoints.removeListener(mirroring); + } + + if (items != null) { + items.removeListener(tPointListener); + items.forEach(item -> { + item.dateProperty().removeListener(dateListener); + item.trackingPointProperty().removeListener(nameListener); + }); + } + } + + /** + * Add listeners on items and tracking points. The aim is to check statement + * changes to update tracking point list accordingly. + */ + private void addListeners() { + if (items != null && points != null) { + mirroring = new SetToListListener(points); + internalPoints.addListener(mirroring); + items.forEach(item -> { + item.dateProperty().addListener(dateListener); + item.trackingPointProperty().addListener(nameListener); + }); + items.addListener(tPointListener); + } + } + + /** + * Check changes on statement list, to update available tracking points + * according to dataset content. + */ + private class TrackingPointListener implements ListChangeListener { + + @Override + public void onChanged(Change c) { + final HashSet toRemove = new HashSet<>(); + final HashSet toAdd = new HashSet<>(); + + TrackingPoint point; + while (c.next()) { + if (c.wasRemoved()) { + for (Statement remitem : c.getRemoved()) { + remitem.dateProperty().removeListener(dateListener); + remitem.trackingPointProperty().removeListener(nameListener); + + point = createTrackingPoint(remitem); + if (point != null) { + toRemove.add(point); + } + } + } + + if (c.wasAdded()) { + for (Statement additem : c.getAddedSubList()) { + additem.dateProperty().addListener(dateListener); + additem.trackingPointProperty().addListener(nameListener); + + point = createTrackingPoint(additem); + if (point != null) { + toRemove.remove(point); + toAdd.add(point); + } + } + } + } + + // If there's still statements referencing removed statement tracking points, we keep them. + final Iterator it = toRemove.iterator(); + while (it.hasNext()) { + point = it.next(); + if (!getSubSet(point).isEmpty()) + it.remove(); + } + + internalPoints.removeAll(toRemove); + internalPoints.addAll(toAdd); + } + + private TrackingPoint createTrackingPoint(final Statement statement) { + final String name = statement.getTrackingPoint(); + final LocalDate date = statement.getDate(); + if (name != null && date != null) { + final TrackingPoint p = protocol.createTrackingPoint(name, date); + p.setRemarks(statement.getRemarks()); + return p; + } else { + return null; + } + } + } + + /** + * Mirrors changes happening in a set to the parameterised list. + */ + private class SetToListListener implements SetChangeListener { + + private final List destination; + + public SetToListListener(final List destination) { + this.destination = destination; + } + + @Override + public void onChanged(Change c) { + if (c.wasRemoved()) { + destination.remove(c.getElementRemoved()); + } else if (c.wasAdded()) { + destination.add(c.getElementAdded()); + } + } + } + + /** + * Search for the tracking point attached to given statement. + * @param data The statement to find parent tracking point for. + * @return A tracking point matching given parameter, or nothing. + */ + public Optional findPoint(final Statement data) { + if (data == null) + return Optional.empty(); + return findPoint(data.getTrackingPoint(), data.getDate()); + } + + /** + * Search for a tracking point whose name is equal to the given one, and validity + * period contains given date. + * @param name The name to get a tracking point for. + * @param date The date to get a tracking point for. + * @return A tracking point matching given parameters, or nothing. + */ + public Optional findPoint(String name, final LocalDate date) { + if (!items.isEmpty() && date != null && name != null && !(name = name.trim()).isEmpty()) { + final ObservableList tps = getTrackingPoints(); + if (!tps.isEmpty()) { + for (final TrackingPoint tp : tps) { + if (tp.getName().equals(name) && + (date.isAfter(tp.getYearStart()) || date.isEqual(tp.getYearStart())) && + (date.isBefore(tp.getYearEnd()) || date.isEqual(tp.getYearEnd()))) { + return Optional.of(tp); + } + } + } + } + + return Optional.empty(); + } + + public synchronized Map> groupByPoint() { + final List trackingPoints = getTrackingPoints().sorted((TrackingPoint o1, TrackingPoint o2) -> { + if (o1 == null) { + return o2 == null ? 0 : 1; + } else if (o2 == null) { + return -1; + } + + final int nameOrder = o1.getName().compareTo(o2.getName()); + if (nameOrder != 0) { + return nameOrder; + } + + return o1.getYear() - o2.getYear(); + }); + + final Map> statements = new HashMap<>(); + + final SortedList sortedSet = getItems().sorted((Comparator) ORDER_BY_POINT); + final Iterator tit = trackingPoints.iterator(); + final Iterator sit = sortedSet.iterator(); + if (tit.hasNext()) { + TrackingPoint tp = tit.next(); + Predicate predicate = new BelongsToTrackingPoint(tp); + ArrayList ptList = new ArrayList<>(); + statements.put(tp, ptList); + while (sit.hasNext()) { + final T st = sit.next(); + if (!predicate.test(st)) { + if (tit.hasNext()) { + tp = tit.next(); + predicate = new BelongsToTrackingPoint(tp); + ptList = new ArrayList<>(); + statements.put(tp, ptList); + } else { + break; + } + } + + ptList.add(st); + } + } + + return statements; + } + + /** + * Check if the given statement belongs to given tracking point. + * @param point The point to test + * @param statement The statement to test against input point. + * @return True if given statement belongs to given tracking point, false otherwise. + */ + public static boolean belongToPoint(final TrackingPoint point, final Statement statement) { + final LocalDate date = statement.getDate(); + if (date == null || statement.getTrackingPoint() == null) { + return false; + } + + return (point.getName().equals(statement.getTrackingPoint()) + && (date.isAfter(point.getYearStart()) || date.isEqual(point.getYearStart())) + && (date.isBefore(point.getYearEnd()) || date.isEqual(point.getYearEnd()))); + } + + private static class BelongsToTrackingPoint implements Predicate { + final String name; + final LocalDate filterStart; + final LocalDate filterEnd; + + private BelongsToTrackingPoint(final TrackingPoint tp) { + name = tp.getName(); + filterStart = tp.getYearStart().minusDays(1); + filterEnd = tp.getYearEnd().plusDays(1); + } + + @Override + public boolean test(Statement t) { + return name.equals(t.getTrackingPoint()) + && t.getDate() != null + && t.getDate().isAfter(filterStart) + && t.getDate().isBefore(filterEnd); + } + } + + private static Comparator ORDER_BY_POINT = (s1, s2) -> { + final String t1 = s1.getTrackingPoint(); + final String t2 = s2.getTrackingPoint(); + + int comp; + if (t1 == t2) + comp = 0; + else if (t1 != null) + comp = t1.compareTo(t2); + else + comp = 1; + + if (comp == 0) { + final LocalDate d1 = s1.getDate(); + final LocalDate d2 = s2.getDate(); + if (d1 != d2) { + if (d1 == null) + comp = 1; + else + comp = d1.compareTo(d2); + } + } + + return comp; + }; +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/ObjectWrapper.java b/core/src/main/java/fr/cenra/rhomeo/api/data/ObjectWrapper.java new file mode 100644 index 0000000..ca7ca2a --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/ObjectWrapper.java @@ -0,0 +1,56 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import java.util.function.Function; + +/** + * A simple function designed to wrap an object of type I into another of type + * O. + * + * @author Alexis Manin (Geomatys) + * @param Type of the objects to wrap (parameter). + * @param Type of the wrapper object (returned). + */ +public interface ObjectWrapper extends Function { + + Class getInputType(); + + Class getOutputType(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Protocol.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Protocol.java new file mode 100644 index 0000000..61b3a07 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Protocol.java @@ -0,0 +1,104 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.process.Indicator; +import java.time.LocalDate; +import java.util.Set; +import javax.validation.constraints.NotNull; + +/** + * Defines a scientific protocol designed to hold a specific set of measures. + * + * @author Alexis Manin (Geomatys) + */ +public interface Protocol extends IdentifiedObject, Comparable { + + /** + * + * @return {@link Statement} class defining measure content. Must not be null. + */ + @NotNull + Class getDataType(); + + /** + * + * @return Set of reference types needed to complete seizure / processing of + * the protocol measures. Should never be null, but can be empty. + */ + @NotNull + Set getReferenceTypes(); + + /** + * + * @param site A site to analyze. + * @return True if we can process {@link Indicator}s associated to the current + * protocol with given site. False otherwise. + */ + boolean isCompatible(Site site); + + /** + * Create a tracking point with the provided name, whose year contains the + * given date. This method is useful when you've got a {@link Statement}, + * and want to know which stating year owns it. + * + * @param name The name of the tracking point. + * @param statementDate A date to get matching year for. + * @return A new tracking point. Never null. + */ + default TrackingPoint createTrackingPoint(final String name, final LocalDate statementDate) { + return new TrackingPoint(name, statementDate.getYear()); + } + + /** + * Create a new tracking point named as specified, on the given year. A year + * is not always a calendar year. It can go between two chosen dates. + * + * Ex : You can specify that year 2015 goes from September, 1st of 2014 to + * August, 31th of 2015 + * + * @param name The name to set on the new tracking point. + * @param year The year to affect to the tracking point. + * @return A new tracking point. Never null. + */ + default TrackingPoint createTrackingPoint(final String name, final int year) { + return new TrackingPoint(name, year); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Reference.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Reference.java new file mode 100644 index 0000000..818dc55 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Reference.java @@ -0,0 +1,46 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public interface Reference { +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/ReferenceDescription.java b/core/src/main/java/fr/cenra/rhomeo/api/data/ReferenceDescription.java new file mode 100644 index 0000000..8838dd3 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/ReferenceDescription.java @@ -0,0 +1,54 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.IdentifiedObject; + +/** + * Contains generic information about a specific {@link Reference} implementation. + * There should be a unique instance for each type of reference available in application. + * + * @author Alexis Manin (Geomatys) + * @param The type of reference described by this object. + */ +public interface ReferenceDescription extends IdentifiedObject { + + Class getReferenceType(); + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Site.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Site.java new file mode 100644 index 0000000..b1e0ece --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Site.java @@ -0,0 +1,66 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import com.vividsolutions.jts.geom.MultiPolygon; +import fr.cenra.rhomeo.api.IdentifiedObject; + +import javax.validation.constraints.NotNull; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public interface Site extends IdentifiedObject, Comparable { + + @NotNull + MultiPolygon getGeometry(); + + String getCountyCode(); + + String getReferent(); + + String getOrganization(); + + String getZoneType(); + + String getOdonateType(); + + String getOrthoptereType(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Statement.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Statement.java new file mode 100644 index 0000000..f09c309 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Statement.java @@ -0,0 +1,122 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.InternationalDescription; +import fr.cenra.rhomeo.api.InternationalResource; +import java.time.LocalDate; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javax.validation.constraints.NotNull; + +/** + * Represents an entry in a {@link Dataset}. It's an entity containing several + * physical measures. + * + * Note : we force overriding of {@link #equals(java.lang.Object) } and {@link #hashCode() + * } methods, because statements will be compared in application to remove + * potential doublons. + * + * @author Alexis Manin (Geomatys) + */ +public abstract class Statement implements InternationalResource, InternationalDescription { + + protected final SimpleStringProperty trackingPoint = new SimpleStringProperty(this, "trackingPoint"); + + protected final SimpleObjectProperty date = new SimpleObjectProperty<>(this, "date"); + + protected final StringProperty remarksProperty = new SimpleStringProperty(); + + protected Statement() {} + + protected Statement(final String trackingPoint, final LocalDate date) { + this.trackingPoint.set(trackingPoint); + this.date.set(date); + } + + protected Statement(final String trackingPoint, final LocalDate date, final String remarks) { + this(trackingPoint, date); + this.remarksProperty.set(remarks); + } + + @NotNull + public LocalDate getDate() { + return date.get(); + } + + public void setDate(final LocalDate date) { + this.date.set(date); + } + + public ObjectProperty dateProperty() { + return date; + } + + public String getTrackingPoint() { + return trackingPoint.get(); + } + + public void setTrackingPoint(final String tPoint) { + trackingPoint.set(tPoint); + } + + public StringProperty trackingPointProperty() { + return trackingPoint; + } + + public String getRemarks() { + return remarksProperty.get(); + } + + public void setRemarks(final String remarks) { + this.remarksProperty.set(remarks); + } + + public StringProperty remarksProperty() { + return remarksProperty; + } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPoint.java b/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPoint.java new file mode 100644 index 0000000..123e214 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPoint.java @@ -0,0 +1,210 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.time.LocalDate; +import java.util.Collection; +import java.util.Collections; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import org.apache.sis.util.ArgumentChecks; + +/** + * A point on which a series of statements have been done. The point is + * identified by a name and the year on which statements have been done. + * + * It's an (almost) immutable object, because its just a reflection of + * statements name and date property, to use grouping. Moreover, introducing + * points with null year would be problematic for computing work flow. The only + * editable attribute is the remarks property, as it's not a defining data. + * + * @author Alexis Manin (Geomatys) + */ +public final class TrackingPoint implements IdentifiedObject, InternationalResource, Externalizable { + + protected static final long serialVersionUID = 1L; + + private String name; + + private int year; + + private LocalDate yearStart; + private LocalDate yearEnd; + + private final StringProperty remarks; + + public TrackingPoint() { + this("default", 2016); + } + + public TrackingPoint(final String name, final int year) { + this(name, year, null, null); + } + + public TrackingPoint(final String name, final LocalDate yearStart, final LocalDate yearEnd) { + this( + name, + // Try to find a valid year from input dates. + yearEnd != null ? yearEnd.getYear() : yearStart != null ? yearStart.getYear() : LocalDate.now().getYear(), + yearStart, + yearEnd + ); + } + + public TrackingPoint(String name, final int year, LocalDate yearStart, LocalDate yearEnd) { + if (name == null || (name = name.trim()).isEmpty()) { + throw new IllegalArgumentException("Given name is blank !"); + } + this.name = name; + this.remarks = new SimpleStringProperty(); + + if (yearStart == null) { + if (yearEnd != null) { + yearStart = yearEnd.minusYears(1).plusDays(1); + } else { + yearStart = LocalDate.of(year, 01, 01); + } + } + + if (yearEnd == null) { + yearEnd = yearStart.plusYears(1).minusDays(1); + } + + if (yearStart.isBefore(yearEnd)) { + this.yearStart = yearStart; + this.yearEnd = yearEnd; + } else { + this.yearStart = yearEnd; + this.yearEnd = yearStart; + } + + ArgumentChecks.ensureBetween("Year", this.yearStart.getYear(), this.yearEnd.getYear(), year); + this.year = year; + } + + /** + * + * @return Year associated to this point. + */ + public int getYear() { + return year; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 71 * hash + this.year; + hash = 71 * hash + this.name.hashCode(); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TrackingPoint other = (TrackingPoint) obj; + return this.year == other.year && this.name.equals(other.name); + } + + @Override + public String toString() { + return new StringBuilder("TrackingPoint{ name=").append(name).append(" year=").append(year).append(" }").toString(); + } + + public LocalDate getYearStart() { + return yearStart; + } + + public LocalDate getYearEnd() { + return yearEnd; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getRemarks() { + return remarks.get(); + } + + public void setRemarks(String remarks) { + this.remarks.set(remarks); + } + + public StringProperty remarksProperty() { + return remarks; + } + + @Override + public Collection getAlias() { + return Collections.EMPTY_SET; + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(name); + out.writeInt(year); + RhomeoCore.writeDate(yearStart, out); + RhomeoCore.writeDate(yearEnd, out); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + year = in.readInt(); + yearStart = RhomeoCore.readDate(in); + yearEnd = RhomeoCore.readDate(in); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPointFilter.java b/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPointFilter.java new file mode 100644 index 0000000..2008331 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/TrackingPointFilter.java @@ -0,0 +1,100 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class TrackingPointFilter implements Predicate, Serializable { + + public TrackingPoint point; + + public TrackingPointFilter() {} + + public TrackingPointFilter(final TrackingPoint tp) { + point = tp; + } + + @Override + public boolean test(Statement t) { + if (t == null) + return false; + if (!Objects.equals(point == null? null : point.getName(), t.getTrackingPoint())) + return false; + return checkDateRange(t.getDate()); + } + + private boolean checkDateRange(final LocalDate toTest) { + final LocalDate yearStart = (point == null)? null : point.getYearStart(); + final LocalDate yearEnd = (point == null)? null : point.getYearEnd(); + if (toTest == yearStart || toTest == yearEnd) + return true; + if (toTest == null) + return false; + if (yearStart != null && toTest.isBefore(yearStart)) + return false; + + return !(yearEnd != null && toTest.isAfter(yearEnd)); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 89 * hash + Objects.hashCode(this.point); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final TrackingPointFilter other = (TrackingPointFilter) obj; + return Objects.equals(this.point, other.point); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/data/Warning.java b/core/src/main/java/fr/cenra/rhomeo/api/data/Warning.java new file mode 100644 index 0000000..4bb8e11 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/data/Warning.java @@ -0,0 +1,51 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.data; + +import javax.validation.ConstraintViolation; +import javax.validation.Payload; + +/** + * A simple interface defining that the attached {@link ConstraintViolation} is + * a warning, a recoverable error. + * @author Alexis Manin (Geomatys) + */ +public interface Warning extends Payload { + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/preferences/InternationalPreferenceKey.java b/core/src/main/java/fr/cenra/rhomeo/api/preferences/InternationalPreferenceKey.java new file mode 100644 index 0000000..bc825ff --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/preferences/InternationalPreferenceKey.java @@ -0,0 +1,106 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.preferences; + +import fr.cenra.rhomeo.api.InternationalDescription; +import static fr.cenra.rhomeo.api.InternationalDescription.DESCRIPTION_KEY; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.logging.Level; +import static fr.cenra.rhomeo.api.InternationalDescription.LABEL_KEY; + +/** + * An interface to be implemented by preference keys which have an international + * description. + * + * This interface provides a default implementation usefull for {@link Enum} + * based implementations, assuming the {@link InternationalPreferenceKey#name() } + * method is implemented. + * + * @author Samuel Andrés (Geomatys) + */ +public interface InternationalPreferenceKey extends InternationalDescription, SimplePreferenceKey { + + /** + * The name of the key. + * + * The purpose of this method is to provide a default implementation of + * {@link InternationalPreferenceKey#getLabel(java.util.Locale) } and + * {@link InternationalPreferenceKey#getDescription(java.util.Locale) } for + * {@link Enum} based implementations. In case the default implementation is + * overriden, the InternationalPreferenceKey#name() method implementation is + * free and even posibly useless. + * + * @see Enum#name() + * + * @return + */ + String name(); + + /** + * + * @param locale + * @return + */ + @Override + default String getDescription(final Locale locale) { + try { + return ResourceBundle.getBundle(getClass().getName(), locale).getString(name() + '.' + DESCRIPTION_KEY); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "A bundle key cannot be found !", e); + return name().concat(" (No traduction available)"); + } + } + + /** + * + * @param locale + * @return + */ + @Override + default String getLabel(final Locale locale) { + try { + return ResourceBundle.getBundle(getClass().getName(), locale).getString(name() + '.' + LABEL_KEY); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "A bundle key cannot be found !", e); + return name().concat(" (No traduction available)"); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/preferences/PasswordKey.java b/core/src/main/java/fr/cenra/rhomeo/api/preferences/PasswordKey.java new file mode 100644 index 0000000..9dddddd --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/preferences/PasswordKey.java @@ -0,0 +1,49 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.preferences; + +/** + * A simple interface to identify a {@link SimplePreferenceKey} as holding the + * value of a password. It's useful to adapt preference editors. + * + * @author Alexis Manin (Geomatys) + */ +public interface PasswordKey { + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/preferences/PreferenceGroup.java b/core/src/main/java/fr/cenra/rhomeo/api/preferences/PreferenceGroup.java new file mode 100644 index 0000000..daf47b6 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/preferences/PreferenceGroup.java @@ -0,0 +1,86 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.preferences; + +import fr.cenra.rhomeo.api.InternationalDescription; +import java.util.List; + +/** + * A group of preferences. + * + * @author Samuel Andrés (Geomatys) + * + * @param The type of preference keys. + */ +public interface PreferenceGroup extends InternationalDescription, Comparable { + + /** + * + * @param key + * @return The value of the preference associated to the given key in the preference group. + */ + String getPreference(T key); + + /** + * Sets the value of the preference associated to the given key in the preference group. + * + * @param key + * @param value + */ + void setPreference(T key, String value); + + /** + * + * @return The list of the keys of the preference group. + */ + List getKeys(); + + /** + * Priority of this group, used for comparison. + * + * @return The priority of this group. + */ + int getPriority(); + + + @Override + default int compareTo(PreferenceGroup o) { + return getPriority() - o.getPriority(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/preferences/SimplePreferenceKey.java b/core/src/main/java/fr/cenra/rhomeo/api/preferences/SimplePreferenceKey.java new file mode 100644 index 0000000..45151e5 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/preferences/SimplePreferenceKey.java @@ -0,0 +1,60 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.preferences; + +/** + * A simple interface for preference keys, providing a default value. + * + * @author Samuel Andrés (Geomatys) + */ +public interface SimplePreferenceKey { + + /** + * + * @return The key used to retrive a preference value from the storage, for + * a given implementation. + */ + String getKey(); + + /** + * + * @return The default value associated to this preference key. + */ + String getDefaultValue(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/preferences/UserPackagePreferenceGroup.java b/core/src/main/java/fr/cenra/rhomeo/api/preferences/UserPackagePreferenceGroup.java new file mode 100644 index 0000000..0219da4 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/preferences/UserPackagePreferenceGroup.java @@ -0,0 +1,64 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.preferences; + +import java.util.prefs.Preferences; + +/** + * Abstract {@link PreferenceGroup} using {@link Preferences#userNodeForPackage(java.lang.Class) } storage. + * + * @author Samuel Andrés (Geomatys) + * @param + */ +public abstract class UserPackagePreferenceGroup implements PreferenceGroup{ + + protected final Preferences prefs = Preferences.userNodeForPackage(getClass()); + + @Override + public String getPreference(final T key) { + return prefs.get(key.getKey(), key.getDefaultValue()); + } + + @Override + public void setPreference(final T key, String value){ + if (value == null || (value = value.trim()).isEmpty()) + prefs.remove(key.getKey()); + else prefs.put(key.getKey(), value); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/process/AbstractIndicator.java b/core/src/main/java/fr/cenra/rhomeo/api/process/AbstractIndicator.java new file mode 100644 index 0000000..b32f0b8 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/process/AbstractIndicator.java @@ -0,0 +1,128 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.process; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.ReferenceManager; +import fr.cenra.rhomeo.core.list.HumidZoneType; +import java.beans.IntrospectionException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +public abstract class AbstractIndicator implements Indicator { + + private final String name; + private final String remarks; + + public AbstractIndicator(final String name, final String remarks){ + this.name = name; + this.remarks = remarks; + } + + @Override + public String getName() { + return name; + } + + @Override + public Collection getAlias() { + return Collections.singleton(name); + } + + @Override + public String getRemarks() { + return remarks; + } + + /** + * Default implementation. If some {@link Indicator} subclass is not compatible + * with all {@link HumidZoneType}, this method MUST be overriden. + * + * @return a set containing all environment codes because this indicator is + * compatible with all of them. + */ + @Override + public Set getZoneTypeCodes() { + final Set result = new HashSet<>(); + for(final HumidZoneType type : HumidZoneType.values()){ + result.add(type.getCode()); + } + return result; + } + + @Override + public int compareTo(final Indicator i){ + return this.getName().compareTo(i.getName()); + } + + @Override + public String toString() { + return "AbstractIndicator{" + "name=" + name + ", remarks=" + remarks + '}'; + } + + + + /** + * Get the references relative to the reference description parameter. + * + * => DEPLACER VERS ReferenceManager ? + * + * @param + * @param description + * @return + * @throws IntrospectionException + */ + public static List getReferences(final ReferenceDescription description) throws IntrospectionException { + final ReferenceManager referenceManager = ReferenceManager.getOrCreate(description); + final DataContext dc = Session.getInstance().getDataContext(); + final Version versionSel = dc.getReferences().get(description.getReferenceType()); + return referenceManager.getValues(versionSel != null ? versionSel : referenceManager.getInstalledVersions().iterator().next()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/process/AdditionalProcessSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/process/AdditionalProcessSpi.java new file mode 100644 index 0000000..35def54 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/process/AdditionalProcessSpi.java @@ -0,0 +1,69 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.process; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javafx.concurrent.Task; + +/** + * Note : this process is made for the need of "complementary values" provided + * along indicator indices. However, this is still an abstract concept for now, + * and will need to be completed later. + * + * @author Alexis Manin (Geomatys) + */ +public interface AdditionalProcessSpi { + + /** + * + * @return set of indicators for which this + */ + Set getTargets(); + + /** + * + * @param results Set of processes finished and succeeded, sorted by indicator. + * + * @return A task to compute complementary values over the given indicators, + * or nothing if we cannot extract enough data from input parameters. + */ + Optional prepareProcess(final Map> results); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/process/Indicator.java b/core/src/main/java/fr/cenra/rhomeo/api/process/Indicator.java new file mode 100644 index 0000000..72683dc --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/process/Indicator.java @@ -0,0 +1,101 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.process; + +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.list.HumidZoneType; +import java.util.Set; +import javax.validation.constraints.NotNull; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public interface Indicator extends IdentifiedObject, Comparable { + + /** + * + * @return Source protocol to work with. Defines input {@link Statement} type. + */ + @NotNull + Protocol getProtocol(); + + /** + * Create all processes needed to complete {@link Index} computation over + * current session {@link Dataset}. + * The statements inside the {@link Dataset} must match the type given by + * {@link Protocol#getDataType()} from this indicator {@link #getProtocol() }. + * + * Each process is designed to compute part or all of this indicator indices, + * on part or all of the input dataset. Which means developper is completely + * free to split his processes by data or by result. The only important point + * is that once all processes are over and succeeded, the union of their + * results represents all possible indices on the entire input dataset. + * + * @param ctx The context to use as input for the newly created processes. + * + * @return A set of processes ready to be run. No insurance is done on + * execution order nor on parallelisation. The processes returned here should + * be independants. + */ + @NotNull + Set createProcesses(ProcessContext ctx); + + /** + * + * @return Name of the index considered as the main result for processes of + * this indicator. + */ + String getDefaultIndex(); + + /** + * + * @return The compatible environment codes to determine if the indicator is + * applicable to potential sites. + * + * @see Site + * @see HumidZoneType + * + */ + Set getZoneTypeCodes(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/process/Process.java b/core/src/main/java/fr/cenra/rhomeo/api/process/Process.java new file mode 100644 index 0000000..7689924 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/process/Process.java @@ -0,0 +1,79 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.process; + +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.result.Index; +import java.util.Set; +import javafx.concurrent.Task; +import javax.validation.constraints.NotNull; +import org.apache.sis.util.ArgumentChecks; + +/** + * A Calculation over a {@link TrackingPoint}. This class represents a process + * created from an {@link Indicator}. + * + * Note : For process independent of any indicator, you should prefer implementing + * {@link AdditionalProcessSpi}. + * + * @author Alexis Manin (Geomatys) + */ +public abstract class Process extends Task> { + + protected final ProcessContext ctx; + + protected Process(final ProcessContext ctx) { + ArgumentChecks.ensureNonNull("Process context", ctx); + this.ctx = ctx; + } + + /** + * @return The indicator bound to this process. Should never be null. + */ + @NotNull + public abstract Indicator getIndicator(); + + /** + * @return The processing context which provide input data. Never null. + */ + @NotNull + public ProcessContext getContext() { + return ctx; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/process/ProcessContext.java b/core/src/main/java/fr/cenra/rhomeo/api/process/ProcessContext.java new file mode 100644 index 0000000..c8ae4ba --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/process/ProcessContext.java @@ -0,0 +1,214 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.process; + +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.result.Index; +import java.io.Externalizable; +import java.io.Serializable; +import java.util.function.Predicate; +import javafx.beans.Observable; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javax.validation.constraints.NotNull; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.ObservableSet; +import javafx.collections.SetChangeListener; +import org.apache.sis.util.ArgumentChecks; + +/** + * Contains all source needed for {@link Index} processing. + * + * IMPORTANT : Application attempts to save restore state of this object in case + * of unexpected shutdown. For the {@link #getFilter() } property to be saved, + * it MUST be {@link Serializable} or {@link Externalizable}. + * + * @author Alexis Manin (Geomatys) + */ +public class ProcessContext { + + private final Dataset source; + private final DataContext dataCtx; + private final ObservableSet targetIndicators = FXCollections.observableSet(); + + private Dataset processData; + + /** + * A filter which allow user to specify a filter for source to take in + * account. + */ + private final ObjectProperty> filterProperty = new SimpleObjectProperty<>(this, "filter", null); + + public ProcessContext(Dataset data, DataContext dataCtx) { + ArgumentChecks.ensureNonNull("Input data", data); + ArgumentChecks.ensureNonNull("Input data context", dataCtx); + this.source = data; + this.dataCtx = dataCtx; + targetIndicators.addListener((SetChangeListener) this::indicatorsChanged); + filterProperty.addListener(this::filterChanged); + } + + /** + * Dataset to use for indicator computing. It contains imported data that + * has been selected by user for processing. + * + * Note : User selection is reflected by {@link #filterProperty}. But if it + * changes, previously returned dataset won't be updated. To get an up to + * date dataset, you must acquire a new dataset by calling this method + * again. + * + * @return A dataset built from user selected data. Every time this + * method is called, the same dataset is returned, until {@link #filterProperty()} + * change. At this time a new dataset is generated, updating contained data + * to match new filter. + */ + @NotNull + public synchronized Dataset getData() { + if (processData == null) { + if (filterProperty.get() == null) + processData = source; + else { + processData = new Dataset(source.getProtocol()); + processData.getItems().addAll(source.getItems().filtered(filterProperty.get())); + } + } + + return processData; + } + + /** + * Return data seized by user as total dataset. This dataset should not be + * used for processing, as it represents all data imported, not the + * selection made after it. + * + * WARNING : Do not use this dataset for processing indicators. Use {@link #getData() + * } + * instead. + * + * @return Dataset which has been used for building process data. + */ + @NotNull + public Dataset getSourceDataset() { + return source; + } + + /** + * + * @return Seizure context. Contains chosen site and protocol for computing. + */ + @NotNull + public DataContext getDataCtx() { + return dataCtx; + } + + /** + * + * @return a set of tracking points, never {@code null}. + */ + @NotNull + public ObservableList getTrackingPoints() { + return FXCollections.unmodifiableObservableList(getData().getTrackingPoints()); + } + + @NotNull + public ObservableSet getIndicators() { + return targetIndicators; + } + + /** + * Allow user to filter source dataset. The filter will be applied when + * calling {@link #getData() }. It serves to decimate data used by indicator + * processes. + * + * @return Currently configured filter, or null if no filter is defined. + */ + public Predicate getFilter() { + return filterProperty.get(); + } + + /** + * Allow user to specify a filter to apply on top of tracking point + * decimation. If any, previous, configured filter is completely forgotten + * after this call. To chain previously configured filter with your new one, + * you can do something like : + * {@code + * final Predicate myFilter = createFilter(); // Prepare your stuff + * final Predicate previous = processContext.getFilter(); + * processContext.setAdditionalFilter(previous.andThen(myFilter)); + * } + * + * @param filter Filter to configure for current processing context. + */ + public void setFilter(Predicate filter) { + this.filterProperty.set(filter); + } + + public ObjectProperty> filterProperty() { + return filterProperty; + } + + /** + * A listener which check added values for {@link #targetIndicators}. It + * automatically remove indicators not compatible with source site. + * + * @param c + */ + private void indicatorsChanged(final SetChangeListener.Change c) { + if (c.wasAdded() && !c.getElementAdded().getZoneTypeCodes().contains(getDataCtx().getSite().getZoneType())) { + c.getSet().remove(c.getElementAdded()); + } + } + + /** + * Trigger data refresh when filter property change. The process data will + * be regenerated on next call to {@link #getData() } + * + * @param obs + * @param oldFilter + * @param newFilter + */ + private synchronized void filterChanged(final Observable obs) { + processData = null; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/AbstractParametrableIndex.java b/core/src/main/java/fr/cenra/rhomeo/api/result/AbstractParametrableIndex.java new file mode 100644 index 0000000..3b9f8e9 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/AbstractParametrableIndex.java @@ -0,0 +1,122 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import fr.cenra.rhomeo.api.data.TrackingPoint; +import java.util.Objects; + +/** + * Abstract {@link Index} implementation which contains a value for a given entity + * (for instance a {@link TrackingPoint}) and references a specific {@link IndexSpi}. + * + * @author Samuel Andrés (Geomatys) + * + * @param The type of index value. + * @param The type of the index base the index value is associated with (for instance, a {@link TrackingPoint}…). + */ +public abstract class AbstractParametrableIndex implements Index { + + protected final V value; + protected final IndexSpi indexSpi; + protected final B base; + protected final Class baseClass; + + public AbstractParametrableIndex(final V value, final IndexSpi indexSpi, final B base, final Class baseClass){ + this.value = value; + this.indexSpi = indexSpi; + this.base = base; + this.baseClass = baseClass; + } + + @Override + public V getValue() { + return value; + } + + public B getBase(){ + return base; + } + + public Class getBaseClass(){return baseClass;} + + @Override + public abstract int getYear(); + + @Override + public IndexSpi getSpi() { + return indexSpi; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 83 * hash + this.getYear(); + hash = 83 * hash + Objects.hashCode(this.value); + hash = 83 * hash + Objects.hashCode(this.indexSpi); + hash = 83 * hash + Objects.hashCode(this.base); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AbstractParametrableIndex other = (AbstractParametrableIndex) obj; + if (this.getYear() != other.getYear()) { + return false; + } + if (!Objects.equals(this.value, other.value)) { + return false; + } + if (!Objects.equals(this.indexSpi, other.indexSpi)) { + return false; + } + if (!Objects.equals(this.base, other.base)) { + return false; + } + return true; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValue.java b/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValue.java new file mode 100644 index 0000000..b735719 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValue.java @@ -0,0 +1,48 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +/** + * Complementary data computed from protocol data and indicator indices. + * + * @author Alexis Manin (Geomatys) + */ +public interface AdditionalValue { + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValueMapper.java b/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValueMapper.java new file mode 100644 index 0000000..b246db8 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/AdditionalValueMapper.java @@ -0,0 +1,76 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Set; + +/** + * A service whose aim is to serialize / deserialize {@link AdditionalValue } + * from file-system. + * + * @author Alexis Manin (Geomatys) + */ +public interface AdditionalValueMapper { + + /** + * Read additional data contained in given folder, if any. + * + * @param folder The directory containing data to read. + * @return Set of read values. Can be empty, but should not be null. + * @throws java.io.IOException If values have been found, but an error + * occurred while reading them. + */ + Set readValues(final Path folder) throws IOException; + + /** + * Write a given set of {@link AdditionalValue} in a specific folder, if the + * current service is designed to treat input data type. Do nothing + * otherwise. + * + * @param folder To put data into. + * @param toWrite Set of values to serialise. + * @return True if current service is able to write input values (and has + * succeeded in doing so). False otherwise. + * @throws java.io.IOException If an error occurs while writing given + * values. + */ + boolean writeValues(final Path folder, final Set toWrite) throws IOException; +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/DashboardResultItem.java b/core/src/main/java/fr/cenra/rhomeo/api/result/DashboardResultItem.java new file mode 100644 index 0000000..c62d3ae --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/DashboardResultItem.java @@ -0,0 +1,124 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +/** + * POJO representing a result item in the dashboard table. + * + * @author Cédric Briançon (Geomatys) + */ +public class DashboardResultItem { + private int year; + private String site; + private String indicator; + private Double value; + private boolean published; + + public DashboardResultItem() { + } + + public DashboardResultItem(int year, String site, String indicator, Double value, boolean published) { + this.year = year; + this.site = site; + this.indicator = indicator; + this.value = value; + this.published = published; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public String getSite() { + return site; + } + + public void setSite(String site) { + this.site = site; + } + + public String getIndicator() { + return indicator; + } + + public void setIndicator(String indicator) { + this.indicator = indicator; + } + + public Double getValue() { + return value; + } + + public void setValue(Double value) { + this.value = value; + } + + public boolean isPublished() { + return published; + } + + public void setPublished(boolean published) { + this.published = published; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DashboardResultItem that = (DashboardResultItem) o; + + if (year != that.year) return false; + if (!site.equals(that.site)) return false; + return indicator.equals(that.indicator); + + } + + @Override + public int hashCode() { + int result = year; + result = 31 * result + site.hashCode(); + result = 31 * result + indicator.hashCode(); + return result; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultIndex.java b/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultIndex.java new file mode 100644 index 0000000..9e9a58b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultIndex.java @@ -0,0 +1,112 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import java.util.Objects; + +/** + * {@link Index} implementation which contains a value for a given year + * and references a specific {@link IndexSpi}. + * + * @author Samuel Andrés (Geomatys) + * + * @param The type of the {@link Index} value. + */ +public class DefaultIndex implements Index { + + private final V value; + private final int year; + private final IndexSpi indexSpi; + + /** + * @param value + * @param indexSpi + * @param year + */ + public DefaultIndex(final V value, final IndexSpi indexSpi, final int year){ + this.value = value; + this.indexSpi = indexSpi; + this.year = year; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int getYear() { + return year; + } + + @Override + public IndexSpi getSpi() { + return indexSpi; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 83 * hash + this.year; + hash = 83 * hash + Objects.hashCode(this.value); + hash = 83 * hash + Objects.hashCode(this.indexSpi); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DefaultIndex other = (DefaultIndex) obj; + if (this.year != other.year) { + return false; + } + if (!Objects.equals(this.value, other.value)) { + return false; + } + return Objects.equals(this.indexSpi, other.indexSpi); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultTrackingPointIndex.java b/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultTrackingPointIndex.java new file mode 100644 index 0000000..3e9b69c --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/DefaultTrackingPointIndex.java @@ -0,0 +1,107 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import fr.cenra.rhomeo.api.data.TrackingPoint; +import java.util.Objects; +import org.apache.sis.util.ArgumentChecks; + +/** + * Holds a value for a specific indicator, at a given tracking point. + * + * @param The type of the {@link Index} value. + * @author Alexis Manin (Geomatys) + */ +public class DefaultTrackingPointIndex implements TrackingPointIndex { + + private final V value; + private final TrackingPoint location; + private final IndexSpi spi; + + public DefaultTrackingPointIndex(final V value, final TrackingPoint location, final IndexSpi spi) { + ArgumentChecks.ensureNonNull("Value", value); + ArgumentChecks.ensureNonNull("Tracking point", location); + ArgumentChecks.ensureNonNull("Index spi", spi); + this.value = value; + this.location = location; + this.spi = spi; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int getYear() { + return location.getYear(); + } + + @Override + public IndexSpi getSpi() { + return spi; + } + + @Override + public TrackingPoint getPoint() { + return location; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 47 * hash + Objects.hashCode(this.value); + hash = 47 * hash + Objects.hashCode(this.location); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!TrackingPointIndex.class.isAssignableFrom(obj.getClass())) + return false; + + final TrackingPointIndex other = (TrackingPointIndex) obj; + return Objects.equals(this.value, other.getValue()) + && Objects.equals(this.location, other.getPoint()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/Index.java b/core/src/main/java/fr/cenra/rhomeo/api/result/Index.java new file mode 100644 index 0000000..bab1e7f --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/Index.java @@ -0,0 +1,71 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import fr.cenra.rhomeo.api.process.Indicator; +import javax.validation.constraints.NotNull; + +/** + * An index computed from an {@link Indicator}. Should be immutable, as it + * describes a result of a finished process. + * + * @author Alexis Manin (Geomatys) + * @param Type of number value (double, int, etc.). + */ +public interface Index { + + /** + * + * @return Computed value. Should never be null. + */ + T getValue(); + + /** + * + * @return The year for which this index is valid. + */ + int getYear(); + + /** + * + * @return Thee SPI associated with this value. + */ + @NotNull + IndexSpi getSpi(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/IndexMapper.java b/core/src/main/java/fr/cenra/rhomeo/api/result/IndexMapper.java new file mode 100644 index 0000000..7a444d7 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/IndexMapper.java @@ -0,0 +1,122 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * An utility class to map association between instances from a given base + * class and some indexed numeric values. + * + * The equality of two {@link IndexMapper} is based ont the equality of their + * included "key" instances. + * + * @param The type of the base class. + * + * @author Samuel Andrés (Geomatys) + * @author Cédric Briançon (Geomatys) + */ +public class IndexMapper { + + private final ObjectProperty keyProperty = new SimpleObjectProperty<>(); + + private final Map> map = new HashMap<>(); + + /** + * + * @param key + */ + public IndexMapper(final C key){ + this.keyProperty.set(key); + } + + public C getKey(){return keyProperty.get();} + + public ReadOnlyObjectProperty keyProperty(){return keyProperty;} + + public ReadOnlyObjectProperty valueProperty(final String k){return map.get(k);} + + public void setValue(final String k, final Number v){ + if(valueProperty(k)==null){ + map.put(k, new SimpleObjectProperty<>()); + } + map.get(k).set(v); + } + + public Collection> getValues() { + return map.values(); + } + + public Set getValueAccessors(){ + return map.keySet(); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + Objects.hashCode(this.keyProperty); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final IndexMapper other = (IndexMapper) obj; + if (!Objects.equals(this.keyProperty, other.keyProperty)) { + return false; + } + return true; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/IndexSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/result/IndexSpi.java new file mode 100644 index 0000000..a734138 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/IndexSpi.java @@ -0,0 +1,106 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.process.Indicator; +import javax.measure.unit.Unit; +import javax.validation.constraints.NotNull; + +/** + * Describe a particular type of index, including its name, description, unit, etc. + * + * Note : Must be a Spring component + * + * @author Alexis Manin (Geomatys) + * + * @param Type of index value + * @param Type of index base (Integer for a year, {@link TrackingPoint}…) + */ +public interface IndexSpi extends IdentifiedObject, Comparable { + + /** + * + * @return The unit of this index value. Should never be null. If no unit is + * defined for the value, {@link Unit#ONE} should be returned. + */ + @NotNull + Unit getUnit(); + + /** + * + * @return The indicator associated to this index. + */ + @NotNull + Indicator getIndicator(); + + /** + * Create an index initialized with given value. + * @param value The value to set on created index. + * @param entity The base the index is associated with. For instance an integer to represent a year, a {@link TrackingPoint}… + * @return The created index. + */ + @NotNull + Index createIndex(final V value, final B entity); + + /** + * Denotes this index as a major indicator, or a minor indicator. It differs + * from {@link Indicator#getDefaultIndex() } by the fact that multiple + * indice types can be primary for an indicator, but only one is the default + * index shown on dashboard. + * + * @return True if this spi indices are considered as main results for the + * target indicator (see {@link #getIndicator() }. False if it describes a + * complementary index. + */ + default boolean isPrimary() { + return getName().equals(getIndicator().getDefaultIndex()); + } + + @Override + default int compareTo(IndexSpi o) { + if (o == null) { + return -1; + } + + return (getIndicator() == o.getIndicator())? 0 : + getIndicator() == null? 1 : getIndicator().compareTo(o.getIndicator()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/IndicatorValuesByYearItem.java b/core/src/main/java/fr/cenra/rhomeo/api/result/IndicatorValuesByYearItem.java new file mode 100644 index 0000000..b49a583 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/IndicatorValuesByYearItem.java @@ -0,0 +1,121 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import java.util.HashMap; +import java.util.Map; + +/** + * POJO regrouping indicator values for each year. + * + * @author Cédric Briançon (Geomatys) + */ +public class IndicatorValuesByYearItem implements Comparable { + private final int year; + + private final Map indicatorValues = new HashMap<>(); + + public IndicatorValuesByYearItem(final int year) { + this.year = year; + } + + public int getYear() { + return year; + } + + /** + * Get the value for the given indicator. May return {@code null} if not found. + * + * @param indicator Indicator name. + * @return The value or {@code null} if not defined for this indicator + */ + public PublishedValue getValueForIndicator(final String indicator) { + return indicatorValues.get(indicator); + } + + public void addValueForIndicator(final String indicator, final Double value, final boolean published) { + indicatorValues.put(indicator, new PublishedValue(value, published)); + } + + @Override + public int compareTo(IndicatorValuesByYearItem o) { + if (o == null) { + return -1; + } + return year < o.getYear() ? -1 : year == o.getYear() ? 0 : 1; + } + + /** + * Try to find if the value for this indicator is published. + * + * @param indicatorName + * @return True or false depending if the value has been published or not for this indicator, + * or {@code null} if not defined for this indicator. + */ + public Boolean isPublished(final String indicatorName) { + final PublishedValue pubVal = indicatorValues.get(indicatorName); + if (pubVal == null) { + return null; + } + return pubVal.published; + } + + public static final class PublishedValue { + + private Double value; + private boolean published; + + PublishedValue(final Double value, final boolean published) { + this.value = value; + this.published = published; + } + + public Double getValue() { + return value; + } + + public boolean isPublished() { + return published; + } + + public void setPublished(boolean published) { + this.published = published; + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/result/TrackingPointIndex.java b/core/src/main/java/fr/cenra/rhomeo/api/result/TrackingPointIndex.java new file mode 100644 index 0000000..d0edc96 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/result/TrackingPointIndex.java @@ -0,0 +1,59 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.result; + +import fr.cenra.rhomeo.api.data.TrackingPoint; +import javax.validation.constraints.NotNull; + +/** + * Describe an index computed on a single {@link TrackingPoint}. Should be + * immutable, as it describes a result of a finished process. + * + * @author Alexis Manin (Geomatys) + * @param Type of number value (double, int, etc.). + */ +public interface TrackingPointIndex extends Index { + + /** + * + * @return The point on which this index has been computed on. + */ + @NotNull + TrackingPoint getPoint(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/AdditionalResultSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/AdditionalResultSpi.java new file mode 100644 index 0000000..311afdd --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/AdditionalResultSpi.java @@ -0,0 +1,57 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.result.AdditionalValue; +import java.util.Collection; +import java.util.Optional; +import javafx.scene.Node; + +/** + * An SPI providing view component to display a set of additional results. + * + * @author Alexis Manin (Geomatys) + */ +public interface AdditionalResultSpi { + + Protocol getProtocol(); + + Optional getResultNode(final Collection values); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/DashboardMenuItem.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/DashboardMenuItem.java new file mode 100644 index 0000000..2ade0ee --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/DashboardMenuItem.java @@ -0,0 +1,59 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import java.util.Optional; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableView; +import org.springframework.stereotype.Component; + +/** + * Describe a menu item which an be added to the context menu of the dashboard + * result table. + * + * Note : implementations should be {@link Component}s. This way, the dashboard + * will be able to inject all available items. + * + * @author Alexis Manin (Geomatys) + */ +public interface DashboardMenuItem { + + public Optional createItem(final TableView target); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTable.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTable.java new file mode 100644 index 0000000..b94c3e3 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTable.java @@ -0,0 +1,58 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Statement; +import java.util.function.Predicate; +import javafx.beans.property.ObjectProperty; +import javafx.scene.control.TableView; + +/** + * + * @author Alexis Manin (Geomatys) + * @param Type of filter given by this component. + */ +public abstract class FilterTable extends TableView { + + public abstract Predicate getPreFilter(); + + public abstract void setPreFilter(final Predicate filter); + + public abstract ObjectProperty> preFilterProperty(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTableSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTableSpi.java new file mode 100644 index 0000000..da1403b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/FilterTableSpi.java @@ -0,0 +1,56 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Protocol; +import java.util.function.Predicate; +import javafx.scene.control.TableView; + +/** + * A simple pojo capable to provide a {@link TableView} to display information + * of filters o apply on a specific dataset. + * + * @author Alexis Manin (Geomatys) + */ +public interface FilterTableSpi { + + Protocol getProtocol(); + + FilterTable createEmptyTable(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/PreferencePane.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/PreferencePane.java new file mode 100644 index 0000000..c8e5467 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/PreferencePane.java @@ -0,0 +1,70 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; +import javafx.scene.Node; + +/** + * + * A simple interface which allows to discover JavaFX components able to edit + * a specific {@link PreferenceGroup}. + * + * @author Samuel Andrés (Geomatys) + * @param + */ +public interface PreferencePane { + + /** + * Saves preferences edited by the pane. + */ + void save(); + + /** + * + * @return + */ + PreferenceGroup getPreferenceGroup(); + + /** + * + * @return The editor node to include in the scene. + */ + Node getEditor(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/ProtocolEditorSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/ProtocolEditorSpi.java new file mode 100644 index 0000000..e80d4a1 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/ProtocolEditorSpi.java @@ -0,0 +1,71 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.core.WorkflowStep; +import java.util.Set; +import javafx.scene.Node; + +/** + * Define edition rules for a specific protocol. For example, it allows to + * replace a {@link WorkflowStep} panel by one chosen here. + * + * @author Alexis Manin (Geomatys) + */ +public interface ProtocolEditorSpi { + + Protocol getProtocol(); + + /** + * + * @param step The step user required to display. + * @return A node allowing to edit protocol related data for the given step. + * Can be null, in which case application considers it can use a default + * editor. + */ + Node getEditor(final WorkflowStep step); + + /** + * + * @return A set of steps which must be ignored for given protocol. Can be + * empty, but should never be null. + */ + Set getStepsToIgnore(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditor.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditor.java new file mode 100644 index 0000000..1a4f67c --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditor.java @@ -0,0 +1,81 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Statement; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; + +/** + * An editor created from {@link StatementEditorSpi#createEditor() }. + * @author Alexis Manin (Geomatys) + * @param Manageable data type. + */ +public interface StatementEditor { + + /** + * + * @return Data type manageable by current editor. + */ + Class getDataType(); + + /** + * Asks the editor to focus on a particular item. Optionally, user can also + * give a property name to focus on into input object. + * + * @param item The item to focus on in the editor. + * @param propertyName If not null, this represents a property of the given + * item to focus on. + */ + void focusOn(final T item, final String propertyName); + + /** + * + * @return List of items contained (to edit) in this component. + */ + ObservableList getItems(); + + /** + * + * @param items Configure this component to work with given set of items. + */ + void setItems(ObservableList items); + + ObjectProperty> itemsProperty(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditorSpi.java b/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditorSpi.java new file mode 100644 index 0000000..fd9c286 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/api/ui/StatementEditorSpi.java @@ -0,0 +1,65 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.ui; + +import fr.cenra.rhomeo.api.data.Statement; +import javax.validation.constraints.NotNull; + +/** + * A provider which can return back an editor to fill objects of a specific type. + * + * @author Alexis Manin (Geomatys) + * @param Manageable data type. + */ +public interface StatementEditorSpi { + + /** + * + * @return A new editor for data seizure. Never null. + */ + @NotNull + StatementEditor createEditor(); + + /** + * + * @return Data type manageable by current editor provider. + */ + @NotNull + Class getDataType(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/BeanUtils.java b/core/src/main/java/fr/cenra/rhomeo/core/BeanUtils.java new file mode 100644 index 0000000..370ffbe --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/BeanUtils.java @@ -0,0 +1,98 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import org.geotoolkit.data.bean.BeanFeature; +import org.geotoolkit.feature.type.FeatureType; +import org.opengis.feature.Feature; +import org.opengis.filter.identity.FeatureId; +import org.opengis.referencing.crs.CoordinateReferenceSystem; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class BeanUtils { + + /** + * Build a feature type corresponding to given bean class, i.e which reproduce + * its properties. + * + * @param beanClass The class to build a feature type for. + * @return A feature type to map pojo properties into {@link Feature} API. + */ + public static FeatureType createFeatureType(final Class beanClass) { + return createMapping(beanClass).featureType; + } + + public static BeanFeature.Mapping createMapping(final Class beanClass) { + return new Mapping(beanClass, "no namespace", null, null); + } + + public static BeanFeature.Mapping createMapping(final Class beanClass, final String namespace) { + return new Mapping(beanClass, namespace, null, null); + } + + private static class Mapping extends BeanFeature.Mapping { + + public Mapping(Class clazz, String namespace, CoordinateReferenceSystem crs, String idField) { + super(clazz, namespace, crs, idField); + } + + @Override + public FeatureId buildId(Object bean) { + if (idAccessor == null) { + return new FeatureId() { + + @Override + public String getID() { + return ""; + } + + @Override + public boolean matches(Object o) { + return false; + } + }; + } else { + return super.buildId(bean); + } + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/CSVDecoder.java b/core/src/main/java/fr/cenra/rhomeo/core/CSVDecoder.java new file mode 100644 index 0000000..cd591e7 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/CSVDecoder.java @@ -0,0 +1,426 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.regex.Pattern; +import org.apache.sis.util.Numbers; +import org.apache.sis.util.ObjectConverter; +import org.apache.sis.util.ObjectConverters; +import org.geotoolkit.util.collection.CloseableIterator; + +/** + * Read a given CSV file, trying to create a list of pojo of the given type to hold + * its data. + * + * @author Alexis Manin (Geomatys) + */ +public class CSVDecoder extends CSVMapper { + + /** + * At runtime, we detect doubled double-quote in escaped strings, to replace + * them by single ones. + */ + static final Pattern DOUBLED_DOUBLE_QUOTE = Pattern.compile("\"\""); + static final String DOUBLE_QUOTE_REPLACEMENT = "\""; + + /** + * Contains all cell value decoders. For each line read, we take each column + * value, and give it to an appropriate converter which will put them into + * the right properties of a target Pojo. + */ + private final BiConsumer[] propertySetters; + + /** + * Create a new decoder whose aim is to read data hold by input file, and + * return it as a list of pojos of the given type. + * @param input The file to read. MUST BE READABLE + * @param beanClass The class defining POJO object to use as data container. + * @throws IOException If we cannot analyze input file header. + * @throws IntrospectionException If we cannot analyze input pojo type. + */ + public CSVDecoder(Path input, Class beanClass) throws IOException, IntrospectionException { + this(input, beanClass, null); + } + + /** + * Create a new decoder whose aim is to read data hold by input file, and + * return it as a list of pojos of the given type. + * @param input The file to read. MUST BE READABLE + * @param beanClass The class defining POJO object to use as data container. + * to read. + * @param fileEncoding Encoding to use for reading. + * @throws IOException If we cannot analyze input file header. + * @throws IntrospectionException If we cannot analyze input pojo type. + */ + public CSVDecoder(Path input, Class beanClass, Charset fileEncoding) throws IOException, IntrospectionException { + this(input, beanClass, fileEncoding, null); + } + + public CSVDecoder(Path input, Class beanClass, Charset fileEncoding, Set ignorableProperties) throws IOException, IntrospectionException { + this(input, beanClass, null, fileEncoding, ignorableProperties); + } + + public CSVDecoder(Path input, Class beanClass, final Character separator, final Charset fileEncoding, final Set ignorableProperties) throws IOException, IntrospectionException { + super(input, beanClass, separator, fileEncoding, ignorableProperties); + + if (!Files.isReadable(input)) { + throw new IOException("Given file is not readable : ".concat(input.toString())); + } + + /* Prepare data conversion. For each property present in input bean type, + * we ensure that a read method is available. Then, we analyze its return + * type. We try to find an appropriate converter to get a string + * representation writable in CSV file. + */ + final PropertyDescriptor[] pDescriptors = beanInfo.getPropertyDescriptors(); + propertySetters = new BiConsumer[pDescriptors.length]; + for (int i = 0; i < pDescriptors.length; i++) { + final PropertyDescriptor p = pDescriptors[i]; + final Method writeMethod = p.getWriteMethod(); + if (writeMethod == null) + throw new IllegalArgumentException( + new StringBuilder("Property ") + .append(p.getName()) + .append(" from class ") + .append(beanClass.getCanonicalName()) + .append("is not writable.") + .toString()); + + writeMethod.setAccessible(true); + final Class propertyType = p.getPropertyType(); + final Function conv = createPropertyConverter(propertyType); + + final BiConsumer setter; + setter = (pojo, value) -> { + try { + writeMethod.invoke(pojo, value); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RhomeoRuntimeException(e); + } + }; + + if (conv == null) { + propertySetters[i] = (BiConsumer) setter; + } else { + propertySetters[i] = (pojo, strValue) -> setter.accept(pojo, conv.apply(strValue)); + } + } + } + + /** + * Read given string (must be a line in the csv, not header) and map its + * data into a POJO. + * + * @param line A line of the CSV file. + * @param lineNumber Number of the read line. It serves to build error messages. + * @return A Pojo containing line data. If input line is blank, a null value is returned. + * @throws InstantiationException + * @throws IllegalAccessException + * @throws IOException + */ + private T decodeLine(final String line, int lineNumber) throws InstantiationException, IllegalAccessException, IOException { + final List groups = splitByProperty(line); + if (groups.isEmpty()) + return null; + final T newInstance = dataType.newInstance(); + CharBuffer buffer; + for (int i = 0; i < groups.size(); i++) { + buffer = groups.get(i); + try { + if (buffer.hasRemaining() && propertyOrder[i] >= 0) { + propertySetters[propertyOrder[i]].accept(newInstance, unescape(buffer)); + } + } catch (Exception e) { + throwWithDescription(lineNumber, i, buffer, e); + } + } + + return newInstance; + } + + /** + * + * @return A list of POJO, each one containing data of a line. Pojos are + * sorted according to line number in the source file. + * + * @throws IOException Reading fails. + * @throws InstantiationException Cannot create a new pojo (Mostly no default builder found). + * @throws IllegalAccessException A property of the pojo type cannot be used by the decoder. + */ + public List decode() throws IOException, InstantiationException, IllegalAccessException { + try (final BufferedReader reader = Files.newBufferedReader(target, charset)) { + // No data ... + String line = reader.readLine(); + if (line == null) { + return new ArrayList<>(); + } else { + updatePropertyOrder(line); + } + + // Do not check header content. It has been done at built. + final List result = new ArrayList<>(); + int lineCount = 1; + T read; + while ((line = reader.readLine()) != null) { + read = decodeLine(line, lineCount++); + if (read != null) + result.add(read); + } + return result; + } + } + + /** + * /!\ You MUST CLOSE the iterator once you're done with it. + * @return An iterator which will decode lines of the source file as we iterate + * on them. + */ + public CloseableIterator decodeLazy() { + return new CloseableIterator() { + + BufferedReader fileReader; + T next; + int lineCount = 0; + + @Override + public boolean hasNext() { + if (next == null) { + try { + // first time, check header. + if (fileReader == null) { + fileReader = Files.newBufferedReader(target, charset); + final String header = fileReader.readLine(); + lineCount++; + // check header + if (header == null) + return false; + else + updatePropertyOrder(header); + } + + // read next line + String line; + while (next == null) { + line = fileReader.readLine(); + if (line == null) + break; + next = decodeLine(line, ++lineCount); + } + } catch (IOException e) { + throw new RhomeoRuntimeException(e.getMessage(), e); + } catch (InstantiationException | IllegalAccessException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot set output properties.", ex); + } + } + + return next != null; + } + + @Override + public T next() { + if (next == null) { + throw new IllegalStateException("No more element available !"); + } + + try { + return next; + } finally { + next = null; + } + } + + @Override + public void close() { + if (fileReader != null) { + try { + fileReader.close(); + } catch (IOException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "A reader cannot be closed on file ".concat(target.toString()), ex); + } + } + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + close(); + } + }; + } + + /** + * Analyze a line of input CSV file, and split its character by column. To + * find columns, we search for separators which are not enclosed in an + * escaped character sequence. + * + * NOTE : return char buffers are already trimmed, but are still escaped (if any). + * + * @param line input line to find property indices for. + * @return A list of buffer. Each represents a part of the original string. + * They're sorted by order of appearance in the input string. + */ + private List splitByProperty(final String line) { + final CharBuffer cLine = CharBuffer.wrap(line); + final ArrayList groups = new ArrayList<>(); + int lastGroup = cLine.position(); + boolean escapeMode = false; + char c; + for (int i = cLine.position(); i < cLine.limit(); i++) { + c = cLine.get(i); + if (c == '"') { + escapeMode = !escapeMode;// If two double quotes follows, escape mode is reactivated. + } else if (!escapeMode) { + if (c == separator) { + groups.add(cLine.subSequence(lastGroup, i)); + lastGroup = i + 1; + } else if (Character.isWhitespace(c) && i == lastGroup) { + lastGroup++; // remove trailing spaces + } else if (c == COMMENT_CHAR) { + break; // A comment have been found. + } + } + } + + // last value on the line + if (lastGroup < cLine.limit()) { + groups.add(cLine.subSequence(lastGroup, cLine.remaining())); + } + + return groups; + } + + /** + * + * @param input A char buffer representing one cell data. + * @return A string which is the cell data, from which we remove trailing + * double-quotes and doubled ones if needed. + */ + private String unescape(final CharBuffer input) { + if (!input.hasRemaining()) { + return ""; + } + + if (input.get(input.position()) == '"' && input.get(input.limit() -1) == '"') { + if (input.remaining() < 3) // Empty escaped sequence. + return ""; + return DOUBLED_DOUBLE_QUOTE.matcher(input.subSequence(1, input.remaining() -1)).replaceAll(DOUBLE_QUOTE_REPLACEMENT); + } + + return input.toString(); + } + + /** + * Search for an appropriate converter between given type and string. + * + * Note : for numbers and primitives, we use direct conversion using wrapping + * class utilities. We make a distinction between primitive and wrapping class + * case, as the first one cannot hold null value information. + * + * @param inputType Type of object to convert. + * @return A ready-to-use converter, or null if we do not need any. + */ + private Function createPropertyConverter(final Class inputType) { + final Function result; + if (CharSequence.class.isAssignableFrom(inputType)) { + return null; + // boolean + } else if (Boolean.class.isAssignableFrom(inputType)) { + result = input -> Boolean.valueOf(input); + } else if (Boolean.TYPE.isAssignableFrom(inputType)) { + result = input -> Boolean.parseBoolean(input); + // character + } else if (Character.class.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? null : input.charAt(0); + } else if (Character.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? '?' : input.charAt(0); + // bytes + } else if (Byte.class.isAssignableFrom(inputType)) { + result = input -> Byte.valueOf(input); + } else if (Byte.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? 0 : Byte.parseByte(input); + // short + } else if (Short.class.isAssignableFrom(inputType)) { + result = input -> Short.valueOf(input); + } else if (Short.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? 0 : Short.parseShort(input); + // integer + } else if (Integer.class.isAssignableFrom(inputType)) { + result = input -> Integer.valueOf(input); + } else if (Integer.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? 0 : Integer.parseInt(input); + // long + } else if (Long.class.isAssignableFrom(inputType)) { + result = input -> Long.valueOf(input); + } else if (Long.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? 0 : Long.parseLong(input); + // float + } else if (Float.class.isAssignableFrom(inputType)) { + result = input -> Float.valueOf(input); + } else if (Float.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? Float.NaN : Float.parseFloat(input); + // double + } else if (Double.class.isAssignableFrom(inputType)) { + result = input -> Double.valueOf(input); + } else if (Double.TYPE.isAssignableFrom(inputType)) { + result = input -> input == null || input.isEmpty()? Double.NaN : Double.parseDouble(input); + } else { + final Class tmpClass = Numbers.primitiveToWrapper(inputType); + final ObjectConverter sisConverter = ObjectConverters.find(String.class, tmpClass); + result = input -> (input == null || input.isEmpty())? null : sisConverter.apply(input); + } + + return result; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/CSVEncoder.java b/core/src/main/java/fr/cenra/rhomeo/core/CSVEncoder.java new file mode 100644 index 0000000..4c153ba --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/CSVEncoder.java @@ -0,0 +1,298 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Iterator; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.regex.Pattern; +import org.apache.sis.util.Numbers; +import org.apache.sis.util.ObjectConverter; +import org.apache.sis.util.ObjectConverters; + +/** + * Encode data holded by POJO of a specific type into a CSV file. + * + * @author Alexis Manin (Geomatys) + */ +public class CSVEncoder extends CSVMapper { + + static final Pattern DOUBLE_QUOTE = Pattern.compile("\""); + static final String DOUBLE_QUOTE_REPLACEMENT = "\"\""; + + private final Function[] converters; + + /** + * Create a new encoder whose aim is to write pojo properties in the given file. + * @param output The file to write. If not exists, we'll try to create it. + * However, its parent directory MUST exist already. + * @param beanClass The class defining POJO object we'll have to extract data from. + * @throws IOException If we cannot create the file, or it already exists and we cannot analyze its header. + * @throws IntrospectionException If we cannot analyze input pojo type. + */ + public CSVEncoder(Path output, Class beanClass) throws IOException, IntrospectionException { + this(output, beanClass, null); + } + + /** + * Create a new encoder whose aim is to write pojo preoprties in the given file. + * @param output The file to write. If not exists, we'll try to create it. + * However, its parent directory MUST exist already. + * @param beanClass The class defining POJO object we'll have to extract data from. + * to read. + * @param fileEncoding Encoding to use for writing. + * @throws IOException If we cannot create the file, or it already exists and we cannot analyze its header. + * @throws IntrospectionException If we cannot analyze input pojo type. + */ + public CSVEncoder(Path output, Class beanClass, Charset fileEncoding) throws IOException, IntrospectionException { + this(output, beanClass, fileEncoding, null); + } + + public CSVEncoder(Path output, Class beanClass, Charset fileEncoding, Set ignorableProperties) throws IOException, IntrospectionException { + this(output, beanClass, null, fileEncoding, ignorableProperties); + } + + public CSVEncoder(final Path output, final Class beanClass, final Character separator, final Charset fileEncoding, final Set ignorableProperties) throws IOException, IntrospectionException { + super(output, beanClass, separator, fileEncoding, ignorableProperties); + + /* Prepare data conversion. For each property present in input bean type, + * we ensure that a read method is available. Then, we analyze its return + * type. We try to find an appropriate converter to get a string + * representation writable in CSV file. + */ + final PropertyDescriptor[] pDescriptors = beanInfo.getPropertyDescriptors(); + converters = new Function[pDescriptors.length]; + for (int i = 0 ; i < pDescriptors.length; i++) { + final PropertyDescriptor p = pDescriptors[i]; + final Method readMethod = p.getReadMethod(); + if (readMethod == null) + throw new IllegalArgumentException( + new StringBuilder("Property ") + .append(p.getName()) + .append(" from class ") + .append(beanClass.getCanonicalName()) + .append("is not readable.") + .toString()); + + readMethod.setAccessible(true); + final Class returnType = readMethod.getReturnType(); + Function conv = createPropertyConverter(returnType); + final Function getter = in -> { + try { + return readMethod.invoke(in); + } catch (IllegalAccessException|InvocationTargetException e) { + throw new RhomeoRuntimeException(e); + } + }; + + if (conv ==null) { + converters[i] = getter; + } else { + converters[i] = getter.andThen(conv); + } + } + } + + /** + * Create string representation of a given object. + * @param data the object to encode in CSV. + * @return A line to put in CSV file. + */ + private String encode(T data) { + final StringBuilder builder = new StringBuilder(); + Object value = getPropertyValue(data, 0); + if (value != null) { + builder.append(trimAndEscape(value)); + } + + for (int i = 1; i < converters.length; i++) { + builder.append(this.separator); + value = getPropertyValue(data, i); + if (value != null) { + builder.append(trimAndEscape(value)); + } + } + + return builder.toString(); + } + + /** + * Extract value of a property from input pojo, the one located on the given + * column index in the file. + * @param data The pojo to extract value from. + * @param colIndex Index of the property to extract in target file columns. + * @return The extracted value or a default one if input object property is + * null or does not exists. Can return a null value. + */ + private Object getPropertyValue(final T data, final int colIndex) { + Object value; + final int pIndex = propertyOrder[colIndex]; + if (pIndex < 0) + value = null; + else + value = converters[pIndex].apply(data); + return value; + } + + /** + * Write data pointed by given iterator. + * @param data An iterator which provides objects to write. + * @param replaceExisting If true, data previously written will be erased. + * Otherwise, we'll append data at the end of the file. + * @throws IOException If an error occurs while writing data. + */ + public synchronized void encode(Iterator data, final boolean replaceExisting) throws IOException { + if (!Files.exists(target)) { + Files.createFile(target); + } + + /* Check target file header. If we're in append mode, but no header exists, + * we set a flag to ensure it will be created. If it is here but is not + * compatible with our data type, we raise an exception. + */ + boolean emptyHeader = false; + try (final BufferedReader reader = Files.newBufferedReader(target, charset)) { + final String firstLine = reader.readLine(); + if (firstLine == null) { + emptyHeader = true; + } else if (!replaceExisting) { + if (isValidHeader(firstLine)) { + updatePropertyOrder(firstLine); + } else { + throw new IllegalArgumentException("Given file already exists, but its header is not complient with given data type."); + } + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(target, charset, + StandardOpenOption.CREATE, + replaceExisting ? StandardOpenOption.TRUNCATE_EXISTING : StandardOpenOption.APPEND)) { + if (replaceExisting || emptyHeader) { + writer.write(buildHeader()); + } + while (data.hasNext()) { + writer.newLine(); + writer.write(encode(data.next())); + + } + } + } + + /** + * Write data pointed by given collection. + * @param data A collection which provides objects to write. + * @param replaceExisting If true, data previously written will be erased. + * Otherwise, we'll append data at the end of the file. + * @throws IOException If an error occurs while writing data. + */ + public void encode(Iterable data, final boolean replaceExisting) throws IOException { + final Iterator it = data.iterator(); + try { + encode(it, replaceExisting); + } finally { + if (it instanceof AutoCloseable) { + try { + ((AutoCloseable)it).close(); + } catch (Exception ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot close a resource !", ex); + } + } + } + } + + /** + * Search for an appropriate converter between given type and string. + * @param Type of object to convert. + * @param inputType Type of object to convert. + * @return A ready-to-use converter, or null if we don't need any. + */ + private Function createPropertyConverter(final Class inputType) { + final Function result; + if (inputType.isPrimitive() + || Number.class.isAssignableFrom(inputType) + || CharSequence.class.isAssignableFrom(inputType) + || Boolean.class.isAssignableFrom(inputType)) { + result = null; + + } else { + final Class tmpClass = Numbers.primitiveToWrapper(inputType); + ObjectConverter sisConverter = ObjectConverters.find(tmpClass, String.class); + result = input -> { + return input == null? null : sisConverter.apply(input); + }; + } + + return result; + } + + /** + * Analyze input string, and modify it if the CSV separator character is + * found inside. First, we remove blank spaces at start and end of the + * string. Then, we'll search for provided separator character. If we find + * it, we escape the string using '"' character. If the string also contains + * '"' characters, we double them. + * + * @param input String to analyze. + * @return Input string if we don't have to escape it. A new one otherwise. + */ + private String trimAndEscape(final Object input) { + String result = input.toString().trim(); + if (result.contains(String.valueOf(separator)) || result.contains(String.valueOf(COMMENT_CHAR))) { + result = new StringBuilder(result.length() +2) + .append('"') + .append(DOUBLE_QUOTE.matcher(result).replaceAll(DOUBLE_QUOTE_REPLACEMENT)) + .append('"') + .toString(); + } + + return result; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/CSVMapper.java b/core/src/main/java/fr/cenra/rhomeo/core/CSVMapper.java new file mode 100644 index 0000000..b284431 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/CSVMapper.java @@ -0,0 +1,286 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.sis.util.ArgumentChecks; + +/** + * TODO : Create a builder, to manage encoder / decoder creation. + * + * @author Alexis Manin (Geomatys) + * @param Type of java bean to map to the CSV file. + */ +public abstract class CSVMapper { + + public static final char COMMENT_CHAR = '#'; + public static final char DEFAULT_SEPARATOR = ';'; + + /** + * Expected objects in input or output of the file. + */ + protected final Class dataType; + + /** + * Information about {@link #dataType}. + */ + protected final BeanInfo beanInfo; + + /** + * Separator between columns. + */ + protected final char separator; + + /** + * Charset to use with given file. + */ + protected final Charset charset; + + /** + * Path to the CSV file to read or write. + */ + protected final Path target; + + /** + * For each column of the file, give the property index corresponding in + * {@link #beanInfo}. + */ + protected int[] propertyOrder; + + /** + * List properties that can be missing in the read / written file. + */ + private final Set ignorable; + + protected CSVMapper(Path target, Class beanClass, Character separator, Charset fileEncoding) throws IOException, IntrospectionException { + this(target, beanClass, separator, fileEncoding, null); + } + + protected CSVMapper(Path target, Class beanClass, Character separator, Charset fileEncoding, Set ignorableProperties) throws IOException, IntrospectionException { + ArgumentChecks.ensureNonNull("Output path", target); + ArgumentChecks.ensureNonNull("Data type", beanClass); + + if (ignorableProperties != null) { + ignorable = Collections.unmodifiableSet(ignorableProperties); + } else { + ignorable = Collections.EMPTY_SET; + } + + // Check global information + beanInfo = Introspector.getBeanInfo(beanClass, Object.class); + this.dataType = beanClass; + + this.separator = separator == null ? DEFAULT_SEPARATOR : separator; + + charset = fileEncoding == null ? Charset.defaultCharset() : fileEncoding; + + // If input file exists, we check its header is compatible with given data type. + String firstLine; + if (Files.isReadable(target)) { + try (final BufferedReader reader = Files.newBufferedReader(target, charset)) { + // We read lines until we find the first not empty / commented line. + firstLine = reader.readLine(); + while (firstLine != null && ((firstLine = firstLine.trim()).isEmpty() || firstLine.charAt(0) == COMMENT_CHAR)) + firstLine = reader.readLine(); + + if (firstLine != null && !isValidHeader(firstLine)) { + throw new IllegalArgumentException("L'entête du fichier n'est pas compatible avec le type de donnée associé."); + } + } catch (CharacterCodingException e) { + throw new IllegalArgumentException("Le jeu de caractères du fichier est invalide. Attendu : ".concat(charset.displayName())); + } + } else if (Files.exists(target)) { + throw new IllegalArgumentException("Le fichier source est illisible !"); + } else { + firstLine = null; + } + + updatePropertyOrder(firstLine); + + this.target = target; + } + + public Class getDataType() { + return dataType; + } + + /** + * Create a string displaying names of all properties (except the transient + * and {@link Object} inherited ones) defined by given bean information. + * + * @return A string which is the list of found properties, splitted by a + * given separator. + */ + public String buildHeader() { + final StringBuilder builder = new StringBuilder(); + final PropertyDescriptor[] pDs = beanInfo.getPropertyDescriptors(); + + for (int i = 0; i < propertyOrder.length; i++) { + final PropertyDescriptor p = pDs[propertyOrder[i]]; + // Ignore properties inherited from object. + if (p.getReadMethod() == null || p.getReadMethod().getDeclaringClass().equals(Object.class)) + continue; + builder.append(p.getName()).append(separator); + } + + return builder.substring(0, builder.length() - 1); + } + + /** + * Check if given header lists all properties defined in given bean type. + * + * @param firstLine The header to check. + * @return True if the file header describes the current reference type, + * false otherwise. + */ + public final boolean isValidHeader(final String firstLine) { + return isValidHeader(firstLine, separator, beanInfo, ignorable); + } + + /** + * Check if given header lists all properties defined in given bean type. + * + * @param firstLine The header to check. + * @param separator The character which is used as separator between + * columns. + * @param beanInfo Bean information to check header against. + * @return True if the file header describes the current reference type, + * false otherwise. + */ + public static boolean isValidHeader(final String firstLine, final char separator, final BeanInfo beanInfo) { + return isValidHeader(firstLine, separator, beanInfo, null); + } + + public static boolean isValidHeader(final String firstLine, final char separator, final BeanInfo beanInfo, final Set ignorable) { + + final String[] splitted = firstLine.split(new StringBuilder("\\s*").append(separator).append("\\s*").toString()); + final Set headerNames = new HashSet<>(Arrays.asList(splitted)); + if (ignorable != null) + headerNames.addAll(ignorable); + + for (final PropertyDescriptor p : beanInfo.getPropertyDescriptors()) { + // Ignore properties unreadable or inherited from object. + if (p.getReadMethod() == null || p.getReadMethod().getDeclaringClass().equals(Object.class)) + continue; + + if (!headerNames.remove(p.getName())) { + return false; + } + } + + /* If we arrive here, it means all mandatory properties have been found + * in input header. If we're in read-only mode, we set it as valid, + * because for columns not present in java model, we can ignore them. + * On the contrary, if we must write into the file, we cannot allow + * losing data contained in it, so we ensure each column of the header + * can be bound to the java model. + */ + return true; + } + + /** + * Index property columns. + * + * @param header the header of the file. + */ + protected final void updatePropertyOrder(String header) { + // No header. Natural order initialized. + if (header == null) { + propertyOrder = new int[beanInfo.getPropertyDescriptors().length]; + for (int i = 0; i < propertyOrder.length; i++) { + propertyOrder[i] = i; + } + } else { + final String[] splitted = header.split(new StringBuilder("\\s*").append(separator).append("\\s*").toString()); + propertyOrder = new int[splitted.length]; + Arrays.fill(propertyOrder, -1); + final Map columns = new HashMap<>(splitted.length); + int i = 0; + for (final String columnName : splitted) { + columns.put(columnName, i++); + } + + i = 0; + for (final PropertyDescriptor p : beanInfo.getPropertyDescriptors()) { + final Integer colIndex = columns.get(p.getName()); + if (colIndex != null) + propertyOrder[colIndex] = i; + i++; + } + } + } + + /** + * Throw a new IOException whose message describe location (line, column) of + * the error. + * + * @param line Number of the line where error happened. Must be > 0. + * @param column Index of the field (not character column) on which error occurred. Must be > 0. + * @param token The field value we failed against. + * @param cause Original exception raised. + * @throws IOException In all cases. + */ + protected static void throwWithDescription(final int line, int column, Object token, final Exception cause) throws IOException { + final StringBuilder msg = new StringBuilder("Erreur : "); + if (line > 0) { + msg.append("ligne ").append(line).append(", "); + } + + if (column > 0) { + msg.append("colonne ").append(column).append(", "); + } + msg.append("valeur ").append(token); + throw new IOException(msg.toString(), cause); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/CSVMappingBuilder.java b/core/src/main/java/fr/cenra/rhomeo/core/CSVMappingBuilder.java new file mode 100644 index 0000000..8806443 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/CSVMappingBuilder.java @@ -0,0 +1,79 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class CSVMappingBuilder { + + Path target; + + Charset charset; + + char separator = CSVMapper.DEFAULT_SEPARATOR; + + public CSVMappingBuilder forPath(final Path target) { + if (Files.isDirectory(target)) { + throw new IllegalArgumentException("Provided path is a directory !"); + } + + this.target = target; + return this; + } + + public CSVMappingBuilder withEncoding(final Charset charset) { + this.charset = charset; + return this; + } + + public CSVMappingBuilder withSeparator(final char separator) { + this.separator = separator; + return this; + } + + public TypedCSVMappingBuilder forType(final Class dataType) { + return new TypedCSVMappingBuilder<>(this, dataType); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/RhomeoCore.java b/core/src/main/java/fr/cenra/rhomeo/core/RhomeoCore.java new file mode 100644 index 0000000..059aa7e --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/RhomeoCore.java @@ -0,0 +1,554 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.core.data.UpdateInfo; +import fr.cenra.rhomeo.core.preferences.net.NetPreferences; + +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.security.GeneralSecurityException; +import java.text.NumberFormat; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import org.apache.sis.referencing.CRS; +import org.apache.sis.util.logging.Logging; +import org.geotoolkit.factory.Hints; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.util.FactoryException; +import fr.cenra.rhomeo.core.list.CodeValue; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.spi.FileSystemProvider; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javafx.scene.control.TableCell; +import javafx.util.Callback; +import javafx.util.StringConverter; + + +/** + * Regroups generic methods / constants used by the whole application. + * JavaFX features should be deported to fr.cenra.rhomeo.Rhomeo class, + * in the desktop module, not here. + * + * @author Cédric Briançon (Geomatys) + */ +public class RhomeoCore { + /** + * Generic logger to use in the whole application. + */ + public static final Logger LOGGER = Logging.getLogger("fr.cenra.rhomeo"); + + /** + * Title to display for all popups in the application. + */ + public static final String APPLICATION_TITLE = "Calculette RhoMeo"; + + /** + * Application main folder, in the user home. + */ + public static final String NAME = "rhomeo"; + + /** + * Key for the environment variable for the .rhomeo folder. + */ + public static final String ENV_RHOMEO_PATH_KEY = "rhomeo.path"; + + /** + * Path for the configuration app main folder. + */ + public static final Path CONFIGURATION_PATH; + static { + final String rhomeoPath = System.getProperty(ENV_RHOMEO_PATH_KEY) != null ? System.getProperty(ENV_RHOMEO_PATH_KEY) : NAME; + Path tmpPath = Paths.get(System.getProperty("user.home"), "."+ rhomeoPath); + + if (!Files.isDirectory(tmpPath)) { + try { + Files.createDirectory(tmpPath); + } catch (IOException ex) { + try { + tmpPath = Files.createTempDirectory(NAME); + } catch (IOException ex1) { + ex.addSuppressed(ex1); + throw new ExceptionInInitializerError(ex); + } + } + } + CONFIGURATION_PATH = tmpPath; + } + + /** + * System preference for the home directory of derby. + */ + public static final String DERBY_HOME_KEY = "derby.system.home"; + + /** + * Spring application context path in the resources. + */ + public static final String SPRING_CONTEXT_XML = "/fr/cenra/rhomeo/spring/spring-context.xml"; + + /** + * Path of the EPSG database. + */ + public static final Path EPSG_PATH = CONFIGURATION_PATH.resolve("EPSG"); + + /** + * Path to the folder which will contain reference lists. + */ + public static final Path REFERENCE_PATH = CONFIGURATION_PATH.resolve("references"); + + /** + * Path to the folder which will contain geographic reference datas. + * Path content will look like : + * ./site_id/dataname_year/*.shp + */ + public static final Path REFERENCIELS_GEO_PATH = CONFIGURATION_PATH.resolve("referential_geo"); + + /** + * Sites folder and shp name. + */ + public static final String SITES = "sites"; + + /** + * Path of the sites folder. + */ + public static final Path SITES_PATH = CONFIGURATION_PATH.resolve(SITES); + + /** + * Path of the sites folder. + */ + public static final Path SITES_SHP_PATH = SITES_PATH.resolve(SITES +".shp"); + + /** + * Path of the CSV file for dashboard published results. + */ + public static final Path DASHBOARD_PUBLISHED_RESULTS = CONFIGURATION_PATH.resolve("dashboard_results.csv"); + + /** + * Path of the JSON file for data contexts for sites / protocols. + */ + public static final Path DATA_CONTEXT_SITE_PATH = CONFIGURATION_PATH.resolve("data_contexts.json"); + + public static final Path RESULT_PATH = CONFIGURATION_PATH.resolve("results"); + /** + * Path to the context restoration folder. + */ + public static final Path RESTORATION_FOLDER = CONFIGURATION_PATH.resolve("state"); + + /** + * Number of millis for opening the connection before timeout. + */ + public static final int CONNECTION_TIMEOUT = 5000; + + /** + * Number of millis to wait after launching a timeout if no data has been transferred. + */ + public static final int READ_TIMEOUT = 20000; + + private static CoordinateReferenceSystem SITES_CRS; + private static final int SRID = 2154; + + /** + * Check for word character at the start of a text. + */ + public static final Pattern CODE_PATTERN = Pattern.compile("^[\\w\\.]+"); + + public enum FT_PROPERTIES { + the_geom, NAME, COUNTY, ODONATE, ORTHOPTERE, REFERENT, ORG, TYPE, REMARKS + } + + /** + * A regex to detect file extensions. + */ + public static final Pattern EXT_PATTERN = Pattern.compile("\\.\\w+$"); + + public static final DecimalFormat SIMPLE_DECIMAL_FORMAT = new DecimalFormat("0.##"); + public static final DecimalFormat SECOND_DECIMAL_FORMAT = new DecimalFormat("0.00"); + + /** + * Initializes the EPSG database, generating it if not yet present on the computer. + * + * @throws FactoryException + * @throws IOException + */ + public static void initEpsgDB() throws FactoryException, IOException { + initEpsgDB(true); + } + + /** + * Initialize properties needed by EPSG database. If input boolean is true, + * we'll fully load the database. + * + * @param checkInit Set to true if you want to ensure EPSG database is fully + * initialized. + * + * @throws FactoryException + * @throws IOException + */ + public static void initEpsgDB(final boolean checkInit) throws FactoryException, IOException { + // create a database in user directory + Files.createDirectories(RhomeoCore.EPSG_PATH); + + // work in lazy mode, do your best for lenient datum shift + Hints.putSystemDefault(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE); + + String derbyProp = null; + try { + derbyProp = System.getProperty(DERBY_HOME_KEY); + } finally { + if (derbyProp == null) { + System.setProperty(DERBY_HOME_KEY, EPSG_PATH.toAbsolutePath().toString()); + } + } + + // force loading epsg + if (checkInit) { + getSiteCRS(); + } + } + + public static CoordinateReferenceSystem getSiteCRS() throws FactoryException { + if (SITES_CRS == null) { + SITES_CRS = CRS.forCode("EPSG:"+ SRID); + } + + return SITES_CRS; + } + + /** + * Get current application version. + * + * @return The application version, may be empty or null if not found in the application packaging. + */ + public static String getVersion() { + return RhomeoCore.class.getPackage().getImplementationVersion(); + } + + /** + * Verify the presence of a new update for the application. + * + * @return The new version to install if an update exists, {@code null} if no updates found + * or no need to update. + */ + public static UpdateInfo existingUpdate() { + final String version = getVersion(); + if (version == null || version.isEmpty()) { + LOGGER.log(Level.INFO, "Invalid or not found version for this application"); + return null; + } + final Version localVersion = new Version(version); + final NetPreferences netPrefs = new NetPreferences(); + final String updateUrl = netPrefs.getPreference(NetPreferences.UPDATE_URL); + try (final InputStream stream = netPrefs.openConnection(netPrefs.getUpdateURL()).getInputStream()) { + final UpdateInfo update = new ObjectMapper().readValue(stream, UpdateInfo.class); + if (update.getVersion() != null && update.getVersion().compareTo(localVersion) > 0) + return update; + } catch (IOException|GeneralSecurityException e) { + LOGGER.log(Level.WARNING, "Cannot access update service : ".concat(updateUrl), e); + } + + return null; + } + + /** + * Convert byte number given in parameter in a human readable string. It tries + * to fit the best unit. Ex : if you've got a number higher than a thousand, + * input byte number will be expressed in kB. If you've got more than a million, + * you've got it as MB + * @param byteNumber Byte quantity to display + * @return A string displaying byte number converted in fitting unit, along with unit symbol. + */ + public static String toReadableSize(final long byteNumber) { + final NumberFormat format = NumberFormat.getNumberInstance(); + format.setMinimumFractionDigits(2); + format.setMaximumFractionDigits(2); + if (byteNumber < 0) { + return "inconnu"; + } else if (byteNumber < 1e3) { + return "" + byteNumber + " octets"; + } else if (byteNumber < 1e6) { + return "" + format.format(byteNumber / 1e3) + " ko"; + } else if (byteNumber < 1e9) { + return "" + format.format(byteNumber / 1e6) + " Mo"; + } else if (byteNumber < 1e12) { + return "" + format.format(byteNumber / 1e9) + " Go"; + } else { + return "" + (byteNumber / 1e12) + " To"; + } + } + + /** + * + * @param first First string to test. + * @param second Second string to test. + * @return True if both strings are null or blank. False if at least one of + * them contains something else than spaces. + */ + public static boolean bothEmpty(final String first, final String second) { + return (first == null || first.trim().isEmpty()) && (second == null || second.trim().isEmpty()); + } + + /** + * + * @param first First string to test. + * @param second Second string to test. + * @return True if both strings are equal or blank. False if one of them is + * not blank, or they're not equal. + */ + public static boolean equivalent(final String first, final String second) { + if (bothEmpty(first, second)) { + return true; + } + + return first == null || second == null? false : first.equals(second); + } + + /** + * Try to find an object of given type with the given code. + * Note : Works only with enums. + * + * @param Type of target coded list. + * @param dataType Type of enum to search into + * @param code Code of the object to return. + * @return An object of the queried type with queried code, or nothing if we + * cannot find any object matching. + */ + public static Optional getByCode(final Class dataType, Object code) { + if (dataType.isEnum() && code != null) { + for (final T candid : dataType.getEnumConstants()) { + if (candid.getCode().equals(code)) { + return Optional.of(candid); + } + } + } + + return Optional.empty(); + } + + /** + * Compute the quantile of a given data suite. For more details, see + * https://en.wikipedia.org/wiki/Quantile + * + * Note : we differ from above wiki definition on even computing. Instead of + * arbitrary ceiling computed position, we get the average of its two + * ascending value in the data suite. + * + * @param data Data suite to find a quantile for. If input array is null or + * empty, the method will miserably fail, so be careful.IMPORTANT : IT MUST + * BE SORTED BY ASCENDING ORDER ! + * @param factor Quantile factor. It must be between 0 and 1 (otherwise, the + * algorithm will end up very badly) representing which portion of the data + * we want. + * @return The queried quantile + */ + public static float quantile(final float[] data, final float factor) { + final float position = (data.length -1) * factor; + final double floor = StrictMath.floor(position); + final int floorInt = (int)floor; + if (position == floor) { + // We take directly the pointed value. + return data[floorInt]; + } + + final double ratio = position - floor; + final double extraValue = (data[floorInt + 1] - data[floorInt]) * ratio; + + return (float) (data[floorInt] + extraValue); + } + + /** + * Compute the quantile of a given data suite. For more details, see + * https://en.wikipedia.org/wiki/Quantile + * + * Note : we differ from above wiki definition on even computing. Instead of + * arbitrary ceiling computed position, we get the average of its two + * ascending value in the data suite. + * + * @param data Data suite to find a quantile for. If input array is null or + * empty, the method will miserably fail, so be careful.IMPORTANT : IT MUST + * BE SORTED BY ASCENDING ORDER ! + * @param factor Quantile factor. It must be between 0 and 1 (otherwise, the + * algorithm will end up very badly) representing which portion of the data + * we want. + * @return The queried quantile + */ + public static double quantile(final double[] data, final float factor) { + final float position = (data.length -1) * factor; + final double floor = StrictMath.floor(position); + final int floorInt = (int) floor; + if (position == floor) { + // We take directly the pointed value. + return data[floorInt]; + } + + final double ratio = position - floor; + final double extraValue = (data[floorInt + 1] - data[floorInt]) * ratio; + + return data[floorInt] + extraValue; + } + + /** + * Create a new file system, allowing to view the content of a zip file as + * a file system. + * /!\ Close the returned {@link FileSystem} once you're done with it ! + * + * @param zipPath A path pointing on a zip file. If it does not exist, it + * will be created. + * @return A file system pointing on given zip file. + * @throws URISyntaxException If we cannot create a valid URI from given + * path. + * @throws IOException If the file occurs while accessing zip file. + */ + public static FileSystem toZipFileSystem(final Path zipPath) throws URISyntaxException, IOException { + // Don't call FileSystems.newFileSystems(URI, Map), because of a bug which + // encode twice URIs in jar file system : https://bugs.openjdk.java.net/browse/JDK-8131067 + final List installedProviders = FileSystemProvider.installedProviders(); + for (final FileSystemProvider fsp : installedProviders) { + if ("jar".equals(fsp.getScheme())) { + Map properties = new HashMap<>(); + properties.put("encoding", StandardCharsets.UTF_8.name()); + properties.put("create", "true"); + return fsp.newFileSystem(zipPath, properties); + } + } + + throw new IOException("No file system found for ZIP encoding."); + } + + /** + * Ensure input dates are either strictly equals, or represent the same moment. + * @param first + * @param second + * @return True if the 2 dates are same reference, or respect {@link LocalDate#isEqual(java.time.chrono.ChronoLocalDate) }. + */ + public static boolean checkEquality(final LocalDate first, final LocalDate second) { + return first == second || first != null && second != null && first.isEqual(second); + } + + /** + * Write given local date into the specified object output. If input is null, + * a special flag is written, so a null value will be returned by {@link #readDate(java.io.ObjectInput) }. + * @param toWrite Local date to serialize. + * @param destination Output to write into. + * @throws IOException If an error occcurs while writing in output stream. + */ + public static void writeDate(final LocalDate toWrite, final ObjectOutput destination) throws IOException { + if (toWrite == null) { + destination.writeBoolean(true); + } else { + destination.writeBoolean(false); + destination.writeShort(toWrite.getYear()); + destination.write(toWrite.getMonthValue()); + destination.write(toWrite.getDayOfMonth()); + } + } + + /** + * Read date contained in given source at current position. + * IMPORTANT : Only read dates written using {@link #writeDate(java.time.LocalDate, java.io.ObjectOutput) } + * method. + * @param source Object stream to read from. + * @return Read date, or null. + * @throws IOException + */ + public static LocalDate readDate(final ObjectInput source) throws IOException { + if (source.readBoolean()) + return null; + return LocalDate.of(source.readShort(), source.readByte(), source.readByte()); + } + + public static final Callback ROUNDING_CELL_FACTORY = value -> { + return new TableCell() { + @Override + protected void updateItem(Number item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + setText(SIMPLE_DECIMAL_FORMAT.format(item)); + } + } + }; + }; + + public static final StringConverter DOUBLE_CONVERTER = new StringConverter() { + @Override + public String toString(Double object) { + if (object == null) + return ""; + else + return SECOND_DECIMAL_FORMAT.format(object); + } + + @Override + public Double fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return 0d; + } else + try { + return SECOND_DECIMAL_FORMAT.parse(string).doubleValue(); + } catch (ParseException ex) { + try { + return Double.valueOf(string); + } catch (Exception ex2) { + ex.addSuppressed(ex2); + RhomeoCore.LOGGER.log(Level.FINE, "Unconvertible string.", ex); + } + } + + return 0d; + } + }; +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/RhomeoRuntimeException.java b/core/src/main/java/fr/cenra/rhomeo/core/RhomeoRuntimeException.java new file mode 100644 index 0000000..5705d16 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/RhomeoRuntimeException.java @@ -0,0 +1,58 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +/** + * @author Cédric Briançon (Geomatys) + */ +public class RhomeoRuntimeException extends RuntimeException { + public RhomeoRuntimeException() {} + + public RhomeoRuntimeException(String message) { + super(message); + } + + public RhomeoRuntimeException(String message, Throwable cause) { + super(message, cause); + } + + public RhomeoRuntimeException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/Session.java b/core/src/main/java/fr/cenra/rhomeo/core/Session.java new file mode 100644 index 0000000..6dcbcce --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/Session.java @@ -0,0 +1,533 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.ObjectWrapper; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.AdditionalValue; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.data.DashboardResultsManager; +import fr.cenra.rhomeo.core.data.DataContextManager; +import fr.cenra.rhomeo.core.data.ReferenceManager; +import fr.cenra.rhomeo.core.preferences.ftp.FTPPreferences; + +import java.beans.IntrospectionException; +import java.io.IOException; +import java.net.UnknownHostException; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import fr.cenra.rhomeo.core.util.SerializableDataContext; +import java.util.HashMap; +import java.util.HashSet; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javax.validation.constraints.NotNull; + +import org.apache.commons.net.ftp.FTPClient; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.stereotype.Component; + +/** + * Represents the application session for a user. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class Session implements ApplicationContextAware { + + private static Session INSTANCE; + private ApplicationContext ctx; + + private Dataset dataset; + private DataContext dataContext; + private ProcessContext processContext; + private Set results; + private final Set additionalValues = new HashSet<>(); + + @Autowired + private List protocols; + + @Autowired + private List indicators; + + @Autowired + private DataContextManager contextManager; + + @Autowired + private DashboardResultsManager resultsManager; + + private final ReadOnlyObjectWrapper workflowStep = new ReadOnlyObjectWrapper<>(); + + public static Session getInstance() throws IllegalStateException { + if (INSTANCE == null) { + throw new IllegalStateException("Application context not initialized !"); + } + + return INSTANCE; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + if (applicationContext != null) { + ctx = applicationContext; + INSTANCE = this; + } else { + INSTANCE = null; + } + } + + /** + * Close the session, releasing all resources. + */ + public void close() { + if (ctx instanceof AbstractApplicationContext) { + ((AbstractApplicationContext)ctx).close(); + } + + INSTANCE = null; + } + + /** + * Try to create a new instance for given bean name. + * + * @param Type of the wanted bean. + * @param qualifier Name of the wanted prototype. + * @param beanClass The class to use to create Spring prototype. + * @return A new bean corresponding to the prototype registered for given qualifier. + * @throws IllegalArgumentException If given bean definition is not a prototype. + */ + public T createPrototype(final String qualifier, final Class beanClass) throws IllegalArgumentException { + if (ctx.getAutowireCapableBeanFactory().isPrototype(qualifier)) { + return ctx.getBean(qualifier, beanClass); + } else { + throw new IllegalArgumentException("Given qualifier does not represent a prototype object !"); + } + } + + /** + * Try to create a new instance for given bean name. + * + * @param qualifier Name of the wanted prototype. + * @return A new bean corresponding to the prototype registered for given qualifier. + * @throws IllegalArgumentException If given bean definition is not a prototype. + */ + public Object createPrototype(final String qualifier) throws IllegalArgumentException { + if (ctx.getAutowireCapableBeanFactory().isPrototype(qualifier)) { + return ctx.getBean(qualifier); + } else { + throw new IllegalArgumentException("Given qualifier does not represent a prototype object !"); + } + } + + /** + * Try to create a new instance of given type. + * + * @param Type of the wanted bean. + * @param beanClass The class to use to find Spring prototype definition. + * @param constructorArgs Possible list of constructor arguments for the requiered prototype. + * @return A new bean of given type. + * @throws IllegalArgumentException If given bean definition is not a prototype. + */ + public T createPrototype(final Class beanClass, final Object... constructorArgs) throws BeansException { + return ctx.getBean(beanClass, constructorArgs); + } + + /** + * Create a new connection over the FTP service providing {@link Reference} + * data. The provided {@link FTPClient} must be closed using {@link FTPClient#disconnect() } + * once you've finished your work. + * + * @return A client to exchange data with FTP service which contains reference files. + * + * @throws IOException If we cannot connect to distant service. + * @throws java.security.GeneralSecurityException If an authentication is + * required, but we cannot access to password. + */ + @NotNull + public FTPClient connectReferenceFTP() throws IOException, GeneralSecurityException { + return ctx.getBean(FTPPreferences.class).getAccess(FTPPreferences.ACCESS_POINTS.REFERENCES).createClient(); + } + + /** + * Create a new connection over the FTP service providing result data. + * data. The provided {@link FTPClient} must be closed using {@link FTPClient#disconnect() } + * once you've finished your work. + * + * @return A client to exchange data with FTP service which contains result files. + * + * @throws UnknownHostException If we cannot connect to distant service. + * @throws java.security.GeneralSecurityException If an authentication is + * required, but we cannot access to password. + */ + @NotNull + public FTPClient connectResultFTP() throws IOException, GeneralSecurityException { + return ctx.getBean(FTPPreferences.class).getAccess(FTPPreferences.ACCESS_POINTS.RESULTS).createClient(); + } + + /** + * Try to find a singleton or prototype for the given class. + * + * @param Type of the wanted bean. + * @param beanClass The class to use to find Spring prototype definition. + * @return A new bean of given type. + */ + public T getBean(final Class beanClass) { + return ctx.getBean(beanClass); + } + /** + * Try to find a singleton or prototype for the given class. + * + * @param beanName The name of a bean to find Spring prototype definition. + * @return A new bean of given type. + */ + public Object getBean(final String beanName) { + return ctx.getBean(beanName); + } + + + public DataContext getDataContext() { + return dataContext; + } + + public Dataset getDataset() { + return dataset; + } + + /** + * Request a new edition context for given site and protocol. + * + * /!\ Use with care ! Any call to this method will reset previously seized + * data. + * + * @param site The site to start edition for. + * @param protocol The protocol defining data type to seize. + */ + public void startEdition(final Site site, final Protocol protocol) { + ArgumentChecks.ensureNonNull("Site", site); + ArgumentChecks.ensureNonNull("Protocol", protocol); + if (!protocol.isCompatible(site)) { + throw new IllegalArgumentException("Target site is not compatible with chosen protocol !"); + } + + final SerializableDataContext srDataContext; + try { + srDataContext = contextManager.loadContext(site, protocol); + } catch (IOException ex) { + throw new RhomeoRuntimeException(ex); + } + + if (srDataContext != null) { + final DataContext context = srDataContext.toDataContext(this); + if (context != null) { + this.dataContext = context; + } else { + this.dataContext = new DataContext(site, protocol); + } + } else { + this.dataContext = new DataContext(site, protocol); + } + + // Now that we've got the data context, we'll check that its reference + // versions are installed. If not, we'll remove them from data context. + checkDataContext(dataContext); + + this.dataset = new Dataset(protocol); + } + + private static void checkDataContext(final DataContext ctx) { + for (final ReferenceDescription description : ctx.getProtocol().getReferenceTypes()) { + final ReferenceManager manager; + try { + manager = ReferenceManager.getOrCreate(description); + } catch (IntrospectionException ex) { + throw new RhomeoRuntimeException(ex); + } + if (!manager.getInstalledVersions().contains(ctx.getReferences().get(description.getReferenceType()))) { + ctx.getReferences().remove(description.getReferenceType()); + } + } + } + + /** + * A site name has been updated so update this old name in all serialized files + * working on the old name. + * + * @param site New site containing new name. + * @param oldName Old site name. + */ + public void serializeUpdateSiteName(Site site, String oldName) throws IOException, IntrospectionException, ReflectiveOperationException { + resultsManager.updateDashboardResultsSiteName(site, oldName); + contextManager.updateReferencesSiteName(oldName, site.getName()); + } + + /** + * A site has been deleted so delete all references of this site name in previously + * serialized files for this site. + * + * @param siteName + */ + public void serializeDeleteSite(final String siteName) throws IOException, IntrospectionException, ReflectiveOperationException { + resultsManager.removeDashboardResultsForSite(siteName); + contextManager.removeReferencesForSite(siteName); + } + + /** + * Get results for the current {@linkplain DataContext data context}. + * + * @return A set of results, may be {@code null} or empty. + */ + public Set getResults() { + return results; + } + + /** + * Set results for the current {@linkplain DataContext data context}. + * + * @param results a set of results. + */ + public void setResults(final Set results) { + this.results = results; + } + + public Set getAdditionalValues() { + return additionalValues; + } + + /** + * Get the process context. + * + * @return process context, may be {@code null}. + */ + public ProcessContext getProcessContext() { + return processContext; + } + + public void setProcessContext(ProcessContext processContext) { + this.processContext = processContext; + } + + /** + * + * @return Observable property describing current application state. + */ + public ReadOnlyObjectProperty workflowStepProperty() { + return workflowStep.getReadOnlyProperty(); + } + + /** + * + * @return application current working step. If null, it means application is + * waiting for the user to choose a protocol to operate on. + */ + public WorkflowStep getWorkflowStep() { + return workflowStep.get(); + } + + /** + * Request application to focus on given workflow step. This method will + * first ensure that all needed data can be found. If not, the step is left + * unchanged. + * + * @param newStep The step we want to go to. + * @return True if session succeeded to update the application mode. False + * if it cannot perform queried operation (due to missing data). + */ + public boolean requestWorkflowStep(final WorkflowStep newStep) { + if (newStep == null) { + clearWorkflow(); + + } else { + switch (newStep) { + case DATASET: + if (dataContext != null) + workflowStep.set(newStep); + break; + case PROCESS: + if (processContext != null && dataContext != null) { + checkDataContext(dataContext); + if (dataContext.getReferences().size() >= dataContext.getProtocol().getReferenceTypes().size()) { + workflowStep.set(newStep); + } + } + break; + case RESULT: + case FINALIZATION: + if (results != null) + workflowStep.set(newStep); + } + } + + return newStep == workflowStep.get(); + } + + private void clearWorkflow() { + dataContext = null; + dataset = null; + processContext = null; + results = null; + additionalValues.clear(); + + workflowStep.set(null); + } + + /** + * Get all protocols found. + * + * @return All protocols. + */ + public List getProtocols() { + return protocols; + } + + /** + * Get all indicators found. + * + * @return All indicators. + */ + public List getIndicators() { + return indicators; + } + + /** + * Return the description object corresponding to given {@link Reference} class. + * @param Type of reference to get a description for. + * @param refType Type of reference to get a description for. + * @return Description object for requested reference class. + */ + public ReferenceDescription getDescription(final Class refType) { + final Map beans = ctx.getBeansOfType(ReferenceDescription.class); + for (final ReferenceDescription desc : beans.values()) { + if (desc.getReferenceType().equals(refType)) { + return desc; + } + } + + return null; + } + + /** + * Analyze the given object, and inject autowired fields according to the + * current application context content. + * @param o The object to inject dependencies into. + */ + public void injectDependencies(Object o) { + ctx.getAutowireCapableBeanFactory().autowireBean(o); + } + + public Optional findEditor(Class dataType) { + ArgumentChecks.ensureNonNull("Data type", dataType); + final Map beans = ctx.getBeansOfType(StatementEditorSpi.class); + StatementEditorSpi fallback = null; + Class editorType; + for (final StatementEditorSpi spi : beans.values()) { + editorType = spi.getDataType(); + if (dataType.equals(editorType)) { + return Optional.of(spi.createEditor()); + } else if (editorType.isAssignableFrom(dataType)) { + if (fallback == null || fallback.getDataType().isAssignableFrom(editorType)) { + fallback = spi; + } + } + } + + return fallback == null? Optional.empty() : Optional.of(fallback.createEditor()); + } + + /** + * Find and return the first found wrapper for given data type, if any. + * + * @param Data type to wrap. + * @param dataType The type we want to export. + * @return First found wrapper for input class, or an empty optional if no + * wrapper can be found. + */ + public Optional> getWrapper(final Class dataType) { + if (dataType == null) + return Optional.empty(); + final Map beans = ctx.getBeansOfType(ObjectWrapper.class); + for (final ObjectWrapper wrapper : beans.values()) { + if (wrapper.getInputType().equals(dataType)) + return Optional.of(wrapper); + } + + return Optional.empty(); + } + + /** + * Create a new map whose keys are description for given reference type. + * Values are not changed. + * + * We need this to convert reference versions provided by data context into + * something usable for metadata export. + * + * @param input The map to get values and keys to transform from. + * @return Copy of input map, with transformed keys. + */ + public Map transform(final Map, Version> input) { + if (input == null || input.isEmpty()) + return (Map)input; + + final Map result = new HashMap<>(input.size()); + for (final Map.Entry, Version> entry : ((Map, Version>) input).entrySet()) { + result.put(getDescription(entry.getKey()), entry.getValue()); + } + + return result; + } +} \ No newline at end of file diff --git a/core/src/main/java/fr/cenra/rhomeo/core/TypedCSVMappingBuilder.java b/core/src/main/java/fr/cenra/rhomeo/core/TypedCSVMappingBuilder.java new file mode 100644 index 0000000..1e3025b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/TypedCSVMappingBuilder.java @@ -0,0 +1,100 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +import java.beans.IntrospectionException; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.HashSet; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class TypedCSVMappingBuilder extends CSVMappingBuilder { + + private final Class type; + + private final HashSet ignorable = new HashSet<>(); + + public TypedCSVMappingBuilder(final CSVMappingBuilder builder, final Class type) { + this.target = builder.target; + this.charset = builder.charset; + this.separator = builder.separator; + + this.type = type; + } + + public TypedCSVMappingBuilder setIgnorable(String propertyName) { + if (propertyName != null && !(propertyName = propertyName.trim()).isEmpty()) { + ignorable.add(propertyName); + } + return this; + } + + public final CSVEncoder acquireEncoder() throws IOException, IntrospectionException { + return new CSVEncoder<>(target, type, separator, charset, ignorable); + } + + + public final CSVDecoder acquireDecoder() throws IOException, IntrospectionException { + return new CSVDecoder<>(target, type, separator, charset, ignorable); + } + + @Override + public TypedCSVMappingBuilder withSeparator(char separator) { + super.withSeparator(separator); + return this; + } + + @Override + public TypedCSVMappingBuilder withEncoding(Charset charset) { + super.withEncoding(charset); + return this; + } + + @Override + public TypedCSVMappingBuilder forPath(Path target) { + super.forPath(target); + return this; + } + + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/WorkflowStep.java b/core/src/main/java/fr/cenra/rhomeo/core/WorkflowStep.java new file mode 100644 index 0000000..7791f47 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/WorkflowStep.java @@ -0,0 +1,55 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core; + +/** + * Describes possible states for the application. More accurately, it represents + * each step of the working process a user must go through to extract / publish + * results. In apparition order it is : + * + * DATASET : Data seizure / import / edition. + * PROCESS : Process parameterisation and execution. + * RESULT : Result compilation and consultation. + * FINALIZATION : Options of publication / extraction of the computed results. + * + * @author Alexis Manin (Geomatys) + */ +public enum WorkflowStep { + DATASET, PROCESS, RESULT, FINALIZATION; +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/DashboardResultsManager.java b/core/src/main/java/fr/cenra/rhomeo/core/data/DashboardResultsManager.java new file mode 100644 index 0000000..06a4b23 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/DashboardResultsManager.java @@ -0,0 +1,560 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import com.fasterxml.jackson.core.JsonEncoding; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.result.ResultStorage; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import fr.cenra.rhomeo.core.data.site.SiteRepositoryImpl; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.sis.storage.DataStoreException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.beans.IntrospectionException; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.GeneralSecurityException; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.stream.Collectors; +import org.geotoolkit.data.geojson.GeoJSONStreamWriter; +import org.geotoolkit.nio.IOUtilities; + +/** + * Handle results displayed on the dashboard. + * + * @author Cédric Briançon (Geomatys) + */ +@Component +public class DashboardResultsManager { + + protected static final String HISTORY_LOG = "history.log"; + + @Autowired + private Session session; + + @Autowired + private ResultStorage resultStorage; + + /** + * Get all previously-published result items in the dashboard. + * + * @return A list of results items. + * @throws IOException + * @throws ReflectiveOperationException + * @throws IntrospectionException + */ + private List getDashboardResultsItems() throws IOException, ReflectiveOperationException, IntrospectionException { + if (!Files.exists(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS)) { + return new ArrayList<>(); + } + final CSVDecoder decoder = new CSVDecoder<>(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS, DashboardResultItem.class); + return decoder.decode(); + } + + + /** + * Regroups results for indicator by year. + * + * @param site Site to use. Should not be {@code null}. + * @return Results, never {@code null} + * @throws IntrospectionException + * @throws ReflectiveOperationException + * @throws IOException + */ + public ObservableList getDashboardResultsByYear(final Site site) throws IntrospectionException, ReflectiveOperationException, IOException { + final ObservableList items = getDashboardResultsForSite(site); + final Map indicatorsByYear = new HashMap<>(); + for (final DashboardResultItem item : items) { + final int year = item.getYear(); + if (indicatorsByYear.containsKey(year)) { + final IndicatorValuesByYearItem itemToUpdate = indicatorsByYear.get(year); + itemToUpdate.addValueForIndicator(item.getIndicator(), item.getValue(), item.isPublished()); + } else { + final IndicatorValuesByYearItem itemToAdd = new IndicatorValuesByYearItem(year); + itemToAdd.addValueForIndicator(item.getIndicator(), item.getValue(), item.isPublished()); + indicatorsByYear.put(year, itemToAdd); + } + } + return FXCollections.observableArrayList(indicatorsByYear.values()); + } + + /** + * Get previously-published result items for the specified site. + * + * @param site the selected site. + * @return A list of results items, never {@code null} + * @throws IOException + * @throws ReflectiveOperationException + * @throws IntrospectionException + */ + public ObservableList getDashboardResultsForSite(final Site site) throws IOException, ReflectiveOperationException, IntrospectionException { + final List allRes = getDashboardResultsItems(); + final ObservableList resForSite = FXCollections.observableArrayList(); + resForSite.addAll(allRes.stream() + .filter(item -> site.getName() != null && site.getName().equals(item.getSite())) + .collect(Collectors.toList())); + return resForSite; + } + + /** + * Add or replace a list of result items. + * + * @param itemsToAdd A list of items to serialize. + * @throws IOException + * @throws ReflectiveOperationException + * @throws IntrospectionException + */ + public void addDashboardResultsItems(final List itemsToAdd) throws IOException, ReflectiveOperationException, IntrospectionException { + // Ensures the CSV file to write will exist + if (!Files.exists(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS)) { + Files.createFile(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS); + } + + // First get existing results + final List existingResults = getDashboardResultsItems(); + + // Merge new / updates results into existing results. + for (final DashboardResultItem itemToAdd : itemsToAdd) { + boolean toAdd = true; + for (final DashboardResultItem existing : existingResults) { + if (existing.getSite().equals(itemToAdd.getSite()) && + existing.getIndicator().equals(itemToAdd.getIndicator()) && + existing.getYear() == itemToAdd.getYear()) + { + // Same site, indicator and year, so just update the value + existing.setValue(itemToAdd.getValue()); + existing.setPublished(itemToAdd.isPublished()); + toAdd = false; + break; + } + } + + // Not found for update, so just add it. + if (toAdd) { + existingResults.add(itemToAdd); + } + } + + // Serialize them + final CSVEncoder encoder = new CSVEncoder<>(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS, DashboardResultItem.class); + encoder.encode(existingResults, true); + } + + /** + * Rename site name in dashboard results. Use it when a site is renamed. + * + * @param site New site containing new name. + * @param oldName Old site name. + * @throws IntrospectionException + * @throws ReflectiveOperationException + * @throws IOException + */ + public void updateDashboardResultsSiteName(final Site site, final String oldName) throws IntrospectionException, ReflectiveOperationException, IOException { + if (!Files.exists(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS)) { + return; + } + // First get existing results + final List existingResults = getDashboardResultsItems(); + existingResults.stream().filter(item -> item.getSite().equals(oldName)) + .forEach(item -> item.setSite(site.getName())); + + // Serialize them + final CSVEncoder encoder = new CSVEncoder<>(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS, DashboardResultItem.class); + encoder.encode(existingResults, true); + } + + /** + * Update existing results, for published and value properties. + * + * @param itemsToUpdate + * @throws IntrospectionException + * @throws ReflectiveOperationException + * @throws IOException + */ + public void updateResults(final List itemsToUpdate) throws IntrospectionException, ReflectiveOperationException, IOException { + if (itemsToUpdate == null || itemsToUpdate.isEmpty()) { + return; + } + + final CSVEncoder encoder = new CSVEncoder<>(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS, DashboardResultItem.class); + // Ensures the CSV file to write will exist. If not, create it and directly serialize them + if (!Files.exists(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS)) { + Files.createFile(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS); + // Serialize them + encoder.encode(itemsToUpdate, true); + return; + } + + // First get existing results + final List existingResults = getDashboardResultsItems(); + for (final DashboardResultItem itemToUp : itemsToUpdate) { + final int index = existingResults.indexOf(itemToUp); + if (index > -1) { + existingResults.set(index, itemToUp); + } + } + + encoder.encode(existingResults, true); + } + + /** + * Remove results entries for the specified site name. + * + * @param siteName + * @throws IntrospectionException + * @throws ReflectiveOperationException + * @throws IOException + */ + public void removeDashboardResultsForSite(final String siteName) throws IntrospectionException, ReflectiveOperationException, IOException { + if (siteName == null) { + return; + } + if (!Files.exists(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS)) { + return; + } + + // First get existing results + final List existingResults = getDashboardResultsItems(); + final List filteredList = existingResults.stream() + .filter(item -> !item.getSite().equals(siteName)) + .collect(Collectors.toList()); + + // Delete all result files associated to the site + resultStorage.deleteIfExists(siteName); + + // Serialize them + final CSVEncoder encoder = new CSVEncoder<>(RhomeoCore.DASHBOARD_PUBLISHED_RESULTS, DashboardResultItem.class); + encoder.encode(filteredList, true); + + } + + /** + * Send results currently contained in the session to the FTP service for + * result publication. + * + * Structure on the FTP is: + *
+     * results
+     * |- county code
+     * |--|- SHA1 geometry site
+     * |--|--|- protocol
+     * |--|--|--|- UUID
+     * |--|--|--|--|- site.geojson
+     * |--|--|--|--|- metadata.json
+     * |--|--|--|--|- result.csv
+     * ...
+     * 
+ */ + public void publishResults() throws IOException, DataStoreException, IntrospectionException, GeneralSecurityException { + // Move to the right directory + final AtomicReference dirRef = new AtomicReference(); + resultStorage.publish(() -> { + FTPClient ftpClient = null; + try { + ftpClient = session.connectResultFTP(); + final String rootDir = ftpClient.printWorkingDirectory(); + prepareSiteDirectory(session.getDataContext().getSite(), ftpClient); + prepareResultDirectory(session.getDataContext().getProtocol(), ftpClient); + dirRef.set(ftpClient.printWorkingDirectory().replace(rootDir, "")); + return ftpClient; + } catch (Exception e) { + if (ftpClient != null) + try { + ftpClient.disconnect(); + } catch (IOException ex) { + e.addSuppressed(ex); + } + throw new RhomeoRuntimeException(e); + } + }); + + // log publication + final Object bundlePath = dirRef.get(); + if (bundlePath != null) { + Optional log = fromSession(session, bundlePath.toString()); + if (log.isPresent()) { + log(session.connectResultFTP(), log.get()); + } + } + } + + /** + * Publish site results on the FTP. + * Structure on the FTP is: + *
+     * results
+     * |- county code
+     * |--|- SHA1 geometry site
+     * |--|--|- protocol
+     * |--|--|--|- UUID
+     * |--|--|--|--|- site.geojson
+     * |--|--|--|--|- metadata.json
+     * |--|--|--|--|- result.csv
+     * ...
+     * 
+ * + * @param site + * @param resultsForSite + * @param ftpClient + * @throws IOException + * @throws IntrospectionException + * @throws ReflectiveOperationException + */ + public void publishResults(final Site site, final List resultsForSite, final FTPClient ftpClient) + throws IOException, IntrospectionException, ReflectiveOperationException, DataStoreException + { + final Path tempFolder = Files.createTempDirectory("site"); + final Path siteJsonPath = ExportUtils.writeGeoJson(site, tempFolder); + final ArrayList reallyPublished = new ArrayList<>(resultsForSite.size()); + final ArrayList logs = new ArrayList<>(resultsForSite.size()); + final String rootDir = ftpClient.printWorkingDirectory(); + try { + prepareSiteDirectory(site, ftpClient); + for (final DashboardResultItem item : resultsForSite) { + final Object prototypeIndic = session.getBean(item.getIndicator()); + if (prototypeIndic instanceof Indicator) { + final Indicator indicator = (Indicator) prototypeIndic; + prepareResultDirectory(indicator.getProtocol(), ftpClient); + + try (final InputStream in = Files.newInputStream(siteJsonPath, StandardOpenOption.READ)) { + ftpClient.storeFile("site.geojson", in); + } + + resultStorage.publish(item, ftpClient); + item.setPublished(true); + reallyPublished.add(item); + + logs.add(new Log(site, ftpClient.printWorkingDirectory().replace(rootDir, ""), new int[]{item.getYear()}, indicator)); + + if (!ftpClient.changeToParentDirectory()) { + throw new IOException("Unable to change to parent directory"); + } + if (!ftpClient.changeToParentDirectory()) { + throw new IOException("Unable to change to parent directory"); + } + } + } + + } finally { + try { + IOUtilities.deleteRecursively(tempFolder); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot clear temporary files.", e); + } + + if (!logs.isEmpty() && ftpClient.changeToParentDirectory() && ftpClient.changeToParentDirectory()) { + log(ftpClient, logs.toArray(new Log[logs.size()])); + } + + // Update results publication state. + updateResults(reallyPublished); + } + } + + /** + * Create (if needed) folders in which input site results will be put. + * Working directory is changed to the created/detected directory. + * @param site The site we want to post results for. + * @param ftpClient The FTP connection to work with. + */ + private static void prepareSiteDirectory(final Site site, final FTPClient ftpClient) throws IOException, DataStoreException { + // Move to county code ftp folder + String department = site.getCountyCode(); + if (department == null || department.trim().isEmpty()) + department = "00"; + ftpClient.makeDirectory(department); + if (!ftpClient.changeWorkingDirectory(department)) { + throw new IOException("Unable to change of directory to " + department); + } + + // Move to geom SHA1 ftp folder + final String sha1geom = GeometryUtils.getSha1(site.getGeometry()); + ftpClient.makeDirectory(sha1geom); + if (!ftpClient.changeWorkingDirectory(sha1geom)) { + throw new IOException("Unable to change of directory to " + sha1geom); + } + + if (ftpClient.listFiles(ExportUtils.SITE_JSON).length < 1) { + try (final OutputStream out = ftpClient.storeFileStream(ExportUtils.SITE_JSON)) { + GeoJSONStreamWriter.writeSingleFeature(out, SiteRepositoryImpl.toFeature(site), JsonEncoding.UTF8, 2, true); + } + ftpClient.completePendingCommand(); + } + } + + /** + * Check directories for result publication for a specific protocol. It will + * find or create, and then move to a folder named as input protocol, and + * then create and move to a folder whose name is an UUID. + * + * Note : For publication, you should already be in the site folder, i.e + * have called {@link #prepareSiteDirectory(fr.cenra.rhomeo.api.data.Site, org.apache.commons.net.ftp.FTPClient) }. + * + * @param p The protocol to post results for. + * @param ftpClient The FTP connection to work with. + */ + public static void prepareResultDirectory(final Protocol p, final FTPClient ftpClient) throws IOException { + final String protocolName = p.getName(); + ftpClient.makeDirectory(protocolName); + if (!ftpClient.changeWorkingDirectory(protocolName)) { + throw new IOException("Unable to change of directory to " + protocolName); + } + + final String uuid = UUID.randomUUID().toString(); + ftpClient.makeDirectory(uuid); + if (!ftpClient.changeWorkingDirectory(uuid)) { + throw new IOException("Unable to change of directory to " + uuid); + } + } + + private static void log(final FTPClient ftpClient, Log... toLog) throws IOException { + try (OutputStream out = ftpClient.appendFileStream(HISTORY_LOG); + final OutputStreamWriter outWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8); + final BufferedWriter writer = new BufferedWriter(outWriter)) { + for (final Log log : toLog) { + writer.newLine(); + writer.write(log.toString()); + } + } finally { + ftpClient.completePendingCommand(); + } + } + + private static Optional fromSession(final Session session, final String bundlePath) { + if (session.getDataContext() == null || session.getResults() == null || session.getResults().isEmpty()) + return Optional.empty(); + + final Set years = new HashSet<>(); + final Set indicators = new HashSet<>(); + + for (final Index i : session.getResults()) { + years.add(i.getYear()); + indicators.add(i.getSpi().getIndicator()); + } + + if (years.isEmpty() || indicators.isEmpty()) + return Optional.empty(); + + final int[] primYears = new int[years.size()]; + int count = 0; + for (Integer year : years) + primYears[count++] = year; + + return Optional.of(new Log(session.getDataContext().getSite(), bundlePath, primYears, indicators.toArray(new Indicator[indicators.size()]))); + } + + private static class Log { + final String siteName; + final String referent; + final String organisation; + final String targetBundle; + final String indicateurs; + final String annees; + + public Log(final Site site, final String targetBundle, final int[] years, final Indicator... indicators) { + this(targetBundle, site.getName(), site.getReferent(), site.getOrganization(), years, indicators); + } + + public Log(final String target, final String siteName, final String ref, final String org, final int[] years, final Indicator... indicators) { + this.siteName = siteName; + referent = ref; + organisation = org; + targetBundle = target; + + Arrays.sort(years); + StringJoiner joiner = new StringJoiner(", "); + for (final int year : years) + joiner.add(String.valueOf(year)); + this.annees = joiner.toString(); + + Arrays.sort(indicators); + joiner = new StringJoiner(", "); + for (final Indicator i : indicators) + joiner.add(i.getName()); + indicateurs = joiner.toString(); + } + + @Override + public String toString() { + return new StringBuilder(256) + .append(ZonedDateTime.now().toString()).append(" : ") + .append(siteName).append(", ") + .append(referent == null || referent.isEmpty()? "referent inconnu" : referent) + .append(" (").append(organisation == null || organisation.isEmpty()? "organisation inconnue" : organisation).append(")") + .append(" publie ").append(targetBundle) + .append(" pour ").append(indicateurs) + .append(" en ").append(annees) + .toString(); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/DataContextManager.java b/core/src/main/java/fr/cenra/rhomeo/core/data/DataContextManager.java new file mode 100644 index 0000000..83e8db6 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/DataContextManager.java @@ -0,0 +1,178 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.util.SerializableDataContext; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Handle data context serialization. + * + * @author Cédric Briançon (Geomatys) + */ +@Component +public class DataContextManager { + private final ObjectMapper jsonMapper; + + public DataContextManager() { + jsonMapper = new ObjectMapper(); + } + + /** + * Write data context + * @param dc Data context to serialize. + * @throws IOException + */ + public void writeDataContext(final DataContext dc) throws IOException { + SerializableDataContext[] contexts = getContexts(); + final SerializableDataContext srDataContext = SerializableDataContext.fromDataContext(dc); + boolean found = false; + for (int i=0; i finalContexts = + Stream.of(contexts).filter(context -> !context.siteName.equals(siteName)) + .collect(Collectors.toList()); + // Something to serialize if we have less contexts in the end + if (contexts.length != finalContexts.size()) { + final Path contextsPath = RhomeoCore.DATA_CONTEXT_SITE_PATH; + try (final BufferedWriter writer = Files.newBufferedWriter(contextsPath, StandardCharsets.UTF_8)) { + jsonMapper.writeValue(writer, finalContexts.toArray()); + } + } + } + + public void updateReferencesSiteName(final String oldName, final String newName) throws IOException { + final SerializableDataContext[] contexts = getContexts(); + if (contexts.length == 0) { + return; + } + + boolean aChange = false; + final List finalContexts = new ArrayList<>(); + for (final SerializableDataContext context : contexts) { + if (context.siteName.equals(oldName)) { + aChange = true; + context.siteName = newName; + } + finalContexts.add(context); + } + + // Something to serialize if we have less contexts in the end + if (aChange) { + final Path contextsPath = RhomeoCore.DATA_CONTEXT_SITE_PATH; + try (final BufferedWriter writer = Files.newBufferedWriter(contextsPath, StandardCharsets.UTF_8)) { + jsonMapper.writeValue(writer, finalContexts.toArray()); + } + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/ReferenceManager.java b/core/src/main/java/fr/cenra/rhomeo/core/data/ReferenceManager.java new file mode 100644 index 0000000..abb8270 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/ReferenceManager.java @@ -0,0 +1,508 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.CSVMapper; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import javafx.concurrent.Task; +import javax.validation.constraints.NotNull; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.collection.Cache; + +/** + * Contains the list of available and installed version of a specific reference + * type. Also allow to get data contained in each installed version. + * + * @author Alexis Manin (Geomatys) + * @param Type of reference managed. + */ +public class ReferenceManager { + + /** + * A regex to detect CSV files containing a version number composed of dot separated digits . + */ + private static final Pattern REF_FILE_PATTERN = Pattern.compile("(?i)(\\d+(?:\\.\\d+)*)(.csv)?$"); + + /** + * We register created managers here. + */ + private static final Cache INSTANCES = new Cache<>(13, 13, true); + + /** + * + * @param The type of reference to get a manager for. + * @param descriptor A descriptor for the reference list to check. + * @return The manager corresponding to the given reference description. + * @throws java.beans.IntrospectionException If we cannot analyze given reference type. + */ + public static ReferenceManager getOrCreate(final ReferenceDescription descriptor) throws IntrospectionException { + try { + return INSTANCES.getOrCreate(descriptor.getName(), () -> new ReferenceManager<>(descriptor)); + } catch (IntrospectionException | RuntimeException ex) { + throw ex; + } catch (Exception e) { + throw new RhomeoRuntimeException(e); + } + } + + /** + * Target (managed) reference type. + */ + private final ReferenceDescription refType; + /** + * Information about reference pojo (properties, methods, etc.). + */ + private final BeanInfo refInfo; + + /** + * Directory where installed versions should be located. + */ + private final Path refDir; + + /** + * A map containing installed versions we found on {@link #refresh() }. + */ + private final ObservableMap installed; + /** + * A map containing versions found in FTP server on {@link #refresh() }. + */ + private final ObservableMap distant; + + private final Cache> loadedVersions = new Cache(2, 0, false); + + private WeakReference> lastLoaded; + + private final Set operations; + + private Task refreshTask; + + private ReferenceManager(@NotNull ReferenceDescription refType) throws IntrospectionException { + this.refType = refType; + final String refName = refType.getName(); + ArgumentChecks.ensureNonEmpty("Reference type name", refName); + + refInfo = Introspector.getBeanInfo(refType.getReferenceType(), Object.class); + refDir = RhomeoCore.REFERENCE_PATH.resolve(refName); + + installed = FXCollections.observableMap(new HashMap<>()); + distant = FXCollections.observableMap(new HashMap<>()); + + operations = new HashSet<>(); + } + + /** + * Force this manager to rebuild its data from scratch. The local files and + * FTP service will be scanned anew to find available and installed reference + * lists. + * + * Note : local data is checked first, so if the distant service does not + * provide correct answers and the task fails, we can still access installed + * versions. + * @return A task which refresh data. If a refresh is ready to run or + * already running, the corresponding task is returned. Otherwise, a new + * task is returned which must be launched by the caller. + */ + public synchronized Task refresh() { + if (refreshTask == null || refreshTask.isDone()) { + refreshTask = new Task() { + + @Override + protected Object call() throws Exception { + updateTitle("Recherche de versions : ".concat(refType.getTitle())); + + updateMessage("Vérification des versions installées"); + installed.clear(); + Files.createDirectories(refDir); + + Files.walkFileTree(refDir, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final Matcher matcher = REF_FILE_PATTERN.matcher(file.getFileName().toString()); + if (matcher.find()) { + if (isValid(file)) { + installed.put(new Version(matcher.group(1)), file); + } + } + return super.visitFile(file, attrs); + } + }); + + updateMessage("Vérification des versions disponibles sur le FTP"); + distant.clear(); + final FTPClient ftp = Session.getInstance().connectReferenceFTP(); + try { + if (ftp.changeWorkingDirectory(refType.getName())) { + ftp.listFiles(".", file -> { + if (file.isFile() && file.getSize() > 0) { + final Matcher matcher = REF_FILE_PATTERN.matcher(file.getName()); + if (matcher.find()) { + try (final InputStream retStream = ftp.retrieveFileStream(file.getName())) { + if (isValid(retStream)) { + distant.put(new Version(matcher.group(1)), file); + return true; + } + } catch (IOException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "An error occurred while reading an FTP file : ".concat(file.getName()), e); + } finally { + try { + // Ensures pending command are finished + if (!ftp.completePendingCommand()) { + final int replyCode = ftp.getReplyCode(); + final String replyMessage = ftp.getReplyString(); + final Supplier msgSupplier = () -> { + final StringBuilder builder = new StringBuilder("Error on pending command completion."); + builder.append(System.lineSeparator()).append("File : ").append(file.getName()); + builder.append(System.lineSeparator()).append("Reply code : ").append(replyCode); + builder.append(System.lineSeparator()).append("Reply message : ").append(replyMessage); + return builder.toString(); + }; + RhomeoCore.LOGGER.log(Level.INFO, msgSupplier); + } + } catch (IOException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "An error occurred while trying to complete pending command for FTP file : ".concat(file.getName()), ex); + } + } + } + } + return false; + }); + } + } finally { + try { + ftp.disconnect(); + } catch (IOException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "An FTP client cannot be disconnected : References.", e); + } + } + + return this; + } + }; + } + + return refreshTask; + } + + /** + * + * @return Set of versions available on FTP server. Unmodifiable. The Set is + * automatically updated when the manager is refreshed. + */ + public Set getDistantVersions() { + return distant.keySet(); + } + + /** + * + * @return Set of versions installed locally. Unmodifiable. The Set is + * automatically updated when the manager is refreshed. + */ + public Set getInstalledVersions() { + return installed.keySet(); + } + + /** + * Create a task in charge of the installation of a new reference version. + * @param toInstall Version of the reference list to install. + * @return A task (not submitted yet) to run to install given version. + * @throws IllegalStateException If an installation or uninstallation procedure + * is already running for the version. + */ + public Task install(@NotNull final Version toInstall) throws IllegalStateException { + synchronized (operations) { + if (operations.contains(toInstall)) { + throw new IllegalStateException( + new StringBuilder("An operation is already running for version ") + .append(toInstall.toString()) + .append(" of list ") + .append(refType.getName()) + .toString() + ); + } + } + + return new Task() { + + @Override + protected Object call() throws Exception { + final boolean alreadyRunning; + synchronized (operations) { + alreadyRunning = !operations.add(toInstall); + } + try { + if (alreadyRunning) { + updateMessage( + new StringBuilder("An operation is already running for version ") + .append(toInstall.toString()) + .append(" of list ") + .append(refType.getName()) + .toString() + ); + cancel(); + + } else if (installed.containsKey(toInstall)) { + updateMessage("Queried version is already installed : ".concat(toInstall.toString())); + cancel(); + + } else { + updateTitle("Téléchargement ".concat(toInstall.toString())); + FTPFile ftpFile = distant.get(toInstall); + if (distant == null) { + throw new IllegalArgumentException("Queried version is not available : ".concat(toInstall.toString())); + } + + final Path newRef = refDir.resolve(toInstall.toString().concat(".csv")); + final FTPClient ftp = Session.getInstance().connectReferenceFTP(); + ftp.changeWorkingDirectory(refType.getName()); + try (final OutputStream out = Files.newOutputStream(newRef)) { + ftp.retrieveFile(ftpFile.getName(), out); + } finally { + try { + ftp.disconnect(); + } catch (IOException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "An FTP client cannot be disconnected : References.", e); + } + } + + installed.put(toInstall, newRef); + return newRef; + } + } finally { + synchronized (operations) { + if (!alreadyRunning) { + operations.remove(toInstall); + } + } + } + + return false; + } + }; + } + + /** + * Create a task in charge of removing a local reference version. + * @param toUninstall Version of the reference list to uninstall. + * @return A task (not submitted yet) to run to uninstall given version. + * @throws IllegalStateException If an installation or uninstallation procedure + * is already running for the version. + */ + public Task uninstall(@NotNull final Version toUninstall) throws IllegalStateException { + synchronized (operations) { + if (operations.contains(toUninstall)) { + throw new IllegalStateException( + new StringBuilder("An operation is already running for version ") + .append(toUninstall.toString()) + .append(" of list ") + .append(refType.getName()) + .toString() + ); + } + } + + return new Task() { + + @Override + protected Object call() throws Exception { + final boolean alreadyRunning; + synchronized (operations) { + alreadyRunning = !operations.add(toUninstall); + } + try { + if (alreadyRunning) { + updateMessage( + new StringBuilder("An operation is already running for version ") + .append(toUninstall.toString()) + .append(" of list ") + .append(refType.getName()) + .toString() + ); + cancel(); + + } else if (!installed.containsKey(toUninstall)) { + updateMessage("Queried version is already uninstalled : ".concat(toUninstall.toString())); + cancel(); + + } else { + updateTitle("Suppression ".concat(toUninstall.toString())); + final Path toDelete = installed.get(toUninstall); + Files.delete(toDelete); + return installed.remove(toUninstall); + } + } finally { + synchronized (operations) { + if (!alreadyRunning) { + operations.remove(toUninstall); + } + } + } + + return false; + } + }; + } + + /** + * Check if given file header lists all properties defined in reference + * type. + * + * @param file The file to check. + * @return True if the file header describes the current reference type, + * false otherwise. + * @throws IOException If we cannot read the file. + * @throws IntrospectionException If we cannot extract property description + * from current reference type. + */ + private boolean isValid(@NotNull final Path file) throws IOException { + final String line; + try (final BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + line = reader.readLine(); + } + + return isValidHeader(line); + } + + /** + * Check if given file header lists all properties defined in reference + * type. + * + * @param file The file to check. + * @return True if the file header describes the current reference type, + * false otherwise. + * @throws IOException If we cannot read the file. + * @throws IntrospectionException If we cannot extract property description + * from current reference type. + */ + private boolean isValid(@NotNull final InputStream file) throws IOException { + final String line; + try (final InputStreamReader in = new InputStreamReader(file, StandardCharsets.UTF_8); + final BufferedReader reader = new BufferedReader(in)) { + line = reader.readLine(); + } + + return isValidHeader(line); + } + + /** + * Check if given header lists all properties defined in reference type. + * + * @param firstLine The header to check. + * @return True if the file header describes the current reference type, + * false otherwise. + */ + private boolean isValidHeader(@NotNull final String firstLine) { + return CSVMapper.isValidHeader(firstLine, CSVMapper.DEFAULT_SEPARATOR, refInfo); + } + + /** + * Try to load reference values for the given version. Before calling this + * method, you MUST have called + * {@link #refresh() } at least once, and checked the queried version is + * already installed. + * @param toLoad The version of the list to load. + * @return A read-only list containing all values for the queried references. + * @throws IllegalArgumentException If the queried version is not installed, + * or the manager has not been refreshed yet. + * @throws RhomeoRuntimeException If an error occurs while reading list file. + */ + public synchronized List getValues(final Version toLoad) throws IllegalArgumentException, RhomeoRuntimeException { + try { + final List result = loadedVersions.getOrCreate(toLoad, () -> { + final Path p = installed.get(toLoad); + if (p == null) { + throw new IllegalArgumentException("Queried version is not installed, or the manager has not been refreshed yet !"); + } + + return Collections.unmodifiableList( + new CSVDecoder<>(p, refType.getReferenceType(), StandardCharsets.UTF_8).decode()); + }); + lastLoaded = new WeakReference(result); + return result; + } catch (Exception ex) { + throw new RhomeoRuntimeException(ex); + } + } + + /** + * + * @return The list of references which have been loaded the last time {@link #getValues(fr.cenra.rhomeo.api.Version) } + * has been called, if any. + */ + public synchronized Optional> getLastLoaded() { + if (lastLoaded == null) + return Optional.empty(); + else return Optional.ofNullable(lastLoaded.get()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/TrackingPointValue.java b/core/src/main/java/fr/cenra/rhomeo/core/data/TrackingPointValue.java new file mode 100644 index 0000000..5234d84 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/TrackingPointValue.java @@ -0,0 +1,75 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.data.TrackingPoint; + +/** + * @author Cédric Briançon (Geomatys) + */ +public class TrackingPointValue implements Comparable { + private final TrackingPoint trackingPoint; + private final Double value; + + public TrackingPointValue(final TrackingPoint trackingPoint, final Double value) { + this.trackingPoint = trackingPoint; + this.value = value; + } + + public TrackingPoint getTrackingPoint() { + return trackingPoint; + } + + public Double getValue() { + return value; + } + + @Override + public int compareTo(TrackingPointValue o) { + if (value == null) { + return 1; + } + + if (o == null || o.getValue() == null) { + return -1; + } + + return getValue().compareTo(o.getValue()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/UpdateInfo.java b/core/src/main/java/fr/cenra/rhomeo/core/data/UpdateInfo.java new file mode 100644 index 0000000..0c47686 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/UpdateInfo.java @@ -0,0 +1,163 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.core.util.VersionDeserializer; +import fr.cenra.rhomeo.core.util.VersionSerializer; +import fr.cenra.rhomeo.core.util.ZonedDateTimeDeserializer; +import fr.cenra.rhomeo.core.util.ZonedDateTimeSerializer; +import java.net.URL; +import java.time.ZonedDateTime; + +/** + * A simple pojo which contains information about application available package + * + * @author Alexis Manin (Geomatys) + */ +public class UpdateInfo { + + private Version version; + private ZonedDateTime date; + private String[] releaseNote; + private URL win32; + private String win32MD5; + private URL deb64; + private String deb64MD5; + private URL rpm64; + private String rpm64MD5; + private URL macOS64; + private String macOS64MD5; + + @JsonSerialize(using = VersionSerializer.class) + public Version getVersion() { + return version; + } + + @JsonDeserialize(using = VersionDeserializer.class) + public void setVersion(Version version) { + this.version = version; + } + + public URL getWin32() { + return win32; + } + + @JsonSerialize(using = ZonedDateTimeSerializer.class) + public ZonedDateTime getDate() { + return date; + } + + @JsonDeserialize(using = ZonedDateTimeDeserializer.class) + public void setDate(ZonedDateTime date) { + this.date = date; + } + + public String[] getReleaseNote() { + return releaseNote; + } + + public void setReleaseNote(String[] releaseNote) { + this.releaseNote = releaseNote; + } + + public void setWin32(URL win32) { + this.win32 = win32; + } + + public URL getDeb64() { + return deb64; + } + + public void setDeb64(URL deb64) { + this.deb64 = deb64; + } + + public URL getRpm64() { + return rpm64; + } + + public void setRpm64(URL rpm64) { + this.rpm64 = rpm64; + } + + public URL getMacOS64() { + return macOS64; + } + + public void setMacOS64(URL macOS64) { + this.macOS64 = macOS64; + } + + public String getWin32MD5() { + return win32MD5; + } + + public void setWin32MD5(String win32MD5) { + this.win32MD5 = win32MD5; + } + + public String getDeb64MD5() { + return deb64MD5; + } + + public void setDeb64MD5(String deb64MD5) { + this.deb64MD5 = deb64MD5; + } + + public String getRpm64MD5() { + return rpm64MD5; + } + + public void setRpm64MD5(String rpm64MD5) { + this.rpm64MD5 = rpm64MD5; + } + + public String getMacOS64MD5() { + return macOS64MD5; + } + + public void setMacOS64MD5(String macOS64MD5) { + this.macOS64MD5 = macOS64MD5; + } + + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/county/County.java b/core/src/main/java/fr/cenra/rhomeo/core/data/county/County.java new file mode 100644 index 0000000..22c9a88 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/county/County.java @@ -0,0 +1,89 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.county; + +import org.apache.sis.util.ArgumentChecks; + +/** + * A simple POJO to display the name and code of a county. + * + * @author Alexis Manin (Geomatys) + */ +public class County implements Comparable { + + /** + * Code of the county. An Integer most of the time, except for Corse (2A, 2B). + */ + protected final String code; + /** + * Name of the county (Corse, Isère, etc.). + */ + protected final String name; + + /** + * Build a new county object. + * @param code The code of the county. Not null + * @param name The name of the county. Not null. + */ + protected County(final String code, final String name) { + ArgumentChecks.ensureNonNull("County code", code); + ArgumentChecks.ensureNonNull("County name", name); + + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return new StringBuilder(code).append(' ').append(name).toString(); + } + + @Override + public int compareTo(County o) { + return o == null? -1 : code.compareTo(o.code); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/county/CountyRepository.java b/core/src/main/java/fr/cenra/rhomeo/core/data/county/CountyRepository.java new file mode 100644 index 0000000..68e1ac6 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/county/CountyRepository.java @@ -0,0 +1,255 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.county; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.regex.Matcher; +import javafx.util.StringConverter; +import javax.annotation.PreDestroy; +import org.apache.sis.storage.DataStoreException; +import org.geotoolkit.data.FeatureCollection; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.factory.FactoryFinder; +import org.geotoolkit.factory.Hints; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.nio.IOUtilities; +import org.geotoolkit.referencing.CRS; +import org.opengis.filter.FilterFactory2; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.util.FactoryException; +import org.opengis.util.GenericName; +import org.springframework.stereotype.Component; + +/** + * A repository to load / search for counties. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public final class CountyRepository implements AutoCloseable { + + /** + * List of resources to load to read County data. + */ + private static final String[] RESOURCES = new String[]{ + "DEPARTEMENT.shp", + "DEPARTEMENT.shx", + "DEPARTEMENT.dbf", + "DEPARTEMENT.prj" + }; + + /** + * Attributes to read into the shapefile embedding county data. + */ + private static final String COUNTY_CODE = "CODE_DEPT"; + private static final String COUNTY_NAME = "NOM_DEPT"; + + private final ShapefileFeatureStore store; + private final GenericName name; + private final StringConverter converter; + private final CoordinateReferenceSystem siteCRS; + private final FilterFactory2 ff; + + private final Map counties; + + private final Path dataDir; + + CountyRepository() throws IOException, URISyntaxException, DataStoreException, FactoryException { + // Copy resources on local filesystem + dataDir = Files.createTempDirectory("countyShape"); + for (final String str : RESOURCES) { + try (final InputStream tmpStream = County.class.getResourceAsStream(str)) { + Files.copy(tmpStream, dataDir.resolve(str)); + } + } + + siteCRS = RhomeoCore.getSiteCRS(); + store = new ShapefileFeatureStore(dataDir.resolve(RESOURCES[0]).toUri()); + name = store.getName(); + converter = new Converter(); + + final Hints hints = new Hints(); + hints.put(Hints.FILTER_FACTORY, FilterFactory2.class); + ff = (FilterFactory2) FactoryFinder.getFilterFactory(hints); + + counties = readCounties(); + } + + /** + * Find a county for the given code. + * @param code The code of the county. Ex : 34 for Hérault. + * @return The matching county, or null if we cannot find any. + */ + public County get(final String code) { + return code == null? null : counties.get(code); + } + + /** + * + * @param geom The geometry to find counties for. + * @return List of counties intersecting the input geometry. Never null, but + * can be empty. + */ + public List get(final Geometry geom) { + final ArrayList result = new ArrayList<>(); + if (geom == null || geom.isEmpty()) + return result; + try { + final QueryBuilder builder = new QueryBuilder(name); + final CoordinateReferenceSystem fCRS = store.getFeatureType(name).getCoordinateReferenceSystem(); + if (fCRS != null && !CRS.equalsApproximatively(fCRS, siteCRS)) { + builder.setCRS(siteCRS); + } + + final GenericName geomName = store.getFeatureType().getGeometryDescriptor().getName(); + builder.setProperties(new String[]{COUNTY_CODE, geomName.tip().toString()}); + + builder.setFilter(ff.intersects(ff.property(geomName), ff.literal(geom))); + try (FeatureReader reader = store.getFeatureReader(builder.buildQuery())) { + while (reader.hasNext()) { + result.add(counties.get(reader.next().getPropertyValue(COUNTY_CODE))); + } + } + + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.FINE, "Cannot perform intersection on counties !", e); + } + + return result; + } + + /** + * + * @return Complete list of metropolitan France counties, indexed by code. + */ + public Map getAll() { + return counties; + } + + public FeatureCollection getFeatures() { + return store.createSession(false).getFeatureCollection(QueryBuilder.all(name)); + } + + /** + * Load all counties in memory. + * @return An unmodifiable map of available counties. Key is code, value is + * county. + * @throws DataStoreException + * @throws FactoryException + */ + private Map readCounties() throws DataStoreException, FactoryException { + final QueryBuilder builder = new QueryBuilder(name); + final CoordinateReferenceSystem fCRS = store.getFeatureType(name).getCoordinateReferenceSystem(); + if (fCRS != null && !CRS.equalsApproximatively(fCRS, siteCRS)) { + builder.setCRS(siteCRS); + } + + builder.setProperties(new String[]{COUNTY_CODE, COUNTY_NAME}); + + // read and convert data. + Map tmpCounties = new HashMap(); + try (FeatureReader reader = store.getFeatureReader(builder.buildQuery())) { + Feature dep; + County county; + while (reader.hasNext()) { + dep = reader.next(); + county = new County( + dep.getPropertyValue(COUNTY_CODE).toString(), + dep.getPropertyValue(COUNTY_NAME).toString() + ); + tmpCounties.put(county.code, county); + } + } + + return Collections.unmodifiableMap(tmpCounties); + } + + @PreDestroy + @Override + public void close() throws Exception { + try { + store.close(); + } finally { + IOUtilities.deleteRecursively(dataDir); + } + } + + public StringConverter getStringConverter() { + return converter; + } + + private class Converter extends StringConverter { + + @Override + public String toString(County object) { + if (object != null) + return object.toString(); + return null; + + } + + @Override + public County fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + final Matcher matcher = RhomeoCore.CODE_PATTERN.matcher(string); + if (matcher.find()) { + return get(matcher.group()); + } + + return null; + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReference.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReference.java new file mode 100644 index 0000000..9e13d89 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReference.java @@ -0,0 +1,140 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.Reference; + +import javax.validation.constraints.NotNull; + +/** + * @author Cédric Briançon (Geomatys) + */ +public class FloreBassinReference implements Reference { + /** + * Primary key. + */ + private int cd_nom; + + private int cd_ref; + + private String nom; + private Integer nutriment; + private Integer humidite; + private Integer cc; + + public FloreBassinReference() { + } + + public FloreBassinReference(@NotNull int cdNom, String nom, Integer nutriment, Integer humidite, Integer cc) { + this.cd_nom = cdNom; + this.nom = nom; + this.nutriment = nutriment; + this.humidite = humidite; + this.cc = cc; + } + + public @NotNull int getCd_nom() { + return cd_nom; + } + + public void setCd_nom(@NotNull int cd_nom) { + this.cd_nom = cd_nom; + } + + public @NotNull int getCd_ref() { + return cd_ref; + } + + public void setCd_ref(@NotNull int cd_ref) { + this.cd_ref = cd_ref; + } + + public String getNom() { + return nom; + } + + public void setNom(String nom) { + this.nom = nom; + } + + public Integer getNutriment() { + return nutriment; + } + + public void setNutriment(Integer nutriment) { + this.nutriment = nutriment; + } + + public Integer getHumidite() { + return humidite; + } + + public void setHumidite(Integer humidite) { + this.humidite = humidite; + } + + public Integer getCc() { + return cc; + } + + public void setCc(Integer cc) { + this.cc = cc; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FloreBassinReference that = (FloreBassinReference) o; + + return cd_nom == that.cd_nom; + + } + + @Override + public int hashCode() { + return cd_nom; + } + + @Override + public String toString() { + return nom; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReferenceDescription.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReferenceDescription.java new file mode 100644 index 0000000..ec39465 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/FloreBassinReferenceDescription.java @@ -0,0 +1,72 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import org.springframework.stereotype.Component; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author Cédric Briançon (Geomatys) + */ +@Component +public class FloreBassinReferenceDescription implements ReferenceDescription { + + @Override + public Class getReferenceType() { + return FloreBassinReference.class; + } + + @Override + public String getName() { + return "flore_bassin"; + } + + @Override + public Collection getAlias() { + return Collections.singletonList("Valeurs indicatrices des espèces floristiques"); + } + + @Override + public String getRemarks() { + return "Référentiel flore agrégeant l'ensemble des taxons du bassin RMC (cf. Gilles PACHE)"; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferential.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferential.java new file mode 100644 index 0000000..716516b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferential.java @@ -0,0 +1,325 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javafx.beans.binding.BooleanExpression; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import org.apache.sis.internal.system.DefaultFactories; +import org.apache.sis.storage.DataStoreException; +import org.geotoolkit.data.FeatureCollection; +import org.geotoolkit.data.FeatureIterator; +import org.geotoolkit.data.FeatureStore; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.session.Session; +import org.geotoolkit.filter.DefaultPropertyName; +import org.geotoolkit.geometry.jts.JTS; +import org.geotoolkit.storage.DataStores; +import org.opengis.filter.FilterFactory; +import org.opengis.filter.FilterFactory2; +import org.opengis.util.GenericName; + +/** + * + * @author Johann Sorel (Geomatys) + */ +public class GeoReferential implements Comparable, AutoCloseable{ + + private final Site site; + private final int year; + private final SimpleBooleanProperty zoneHydro = new SimpleBooleanProperty(); + private final SimpleBooleanProperty tacheUrbaine = new SimpleBooleanProperty(); + private final SimpleBooleanProperty tacheArtif = new SimpleBooleanProperty(); + private final SimpleBooleanProperty rpg = new SimpleBooleanProperty(); + private final SimpleBooleanProperty wfszoneHydro = new SimpleBooleanProperty(); + private final SimpleBooleanProperty wfstacheUrbaine = new SimpleBooleanProperty(); + private final SimpleBooleanProperty wfstacheArtif = new SimpleBooleanProperty(); + private final SimpleBooleanProperty wfsrpg = new SimpleBooleanProperty(); + private final SimpleBooleanProperty download = new SimpleBooleanProperty(); + + private FeatureStore store; + + public GeoReferential(Site site, int year) { + this.site = site; + this.year = year; + } + + public int getYear() { + return year; + } + + public BooleanProperty zoneHydroLocal() { + return zoneHydro; + } + + public BooleanProperty tacheUrbaineLocal() { + return tacheUrbaine; + } + + public BooleanProperty tacheArtifLocal() { + return tacheArtif; + } + + public BooleanProperty rpgLocal() { + return rpg; + } + + public BooleanProperty zoneHydroWFS() { + return wfszoneHydro; + } + + public BooleanProperty tacheUrbaineWFS() { + return wfstacheUrbaine; + } + + public BooleanProperty tacheArtifWFS() { + return wfstacheArtif; + } + + public BooleanProperty rpgWFS() { + return wfsrpg; + } + + public BooleanProperty toDownloadProperty() { + return download; + } + + public boolean hasLocalTypes(String ... types){ + for(String type : types){ + switch(type.toLowerCase()){ + case GeoReferentials.TYPE_RPG: + if(!rpg.get()) return false; + break; + case GeoReferentials.TYPE_ZONE_HYDRO: + if(!zoneHydro.get()) return false; + break; + case GeoReferentials.TYPE_TACHE_ARTIF: + if(!tacheArtif.get()) return false; + break; + case GeoReferentials.TYPE_TACHE_URBAINE: + if(!tacheUrbaine.get()) return false; + break; + } + } + return true; + } + + public boolean hasWFSTypes(String ... types){ + for(String type : types){ + switch(type.toLowerCase()){ + case GeoReferentials.TYPE_RPG: + if(!wfsrpg.get()) return false; + break; + case GeoReferentials.TYPE_ZONE_HYDRO: + if(!wfszoneHydro.get()) return false; + break; + case GeoReferentials.TYPE_TACHE_ARTIF: + if(!wfstacheArtif.get()) return false; + break; + case GeoReferentials.TYPE_TACHE_URBAINE: + if(!wfstacheUrbaine.get()) return false; + break; + } + } + return true; + } + + /** + * Check if given types are available either locally or on configured WFS service. + * @param types Name of the types to check presence. If null, empty, or if + * no valid name is present, we consider that predicate cannot be tested, so + * we return false. + * @return True if all given names can be found locally or on WFS service. + * False otherwise, or if input data is null or empty. + */ + public boolean areAvailable(final String... types) { + if (types == null || types.length < 1) + return false; + boolean validNameFound = false; + for (final String type : types) { + if (type != null && !type.isEmpty()) { + validNameFound = true; + break; + } + } + + if (!validNameFound) + return false; // No valid name to test. + + for (final String type : types) { + if (type == null || type.isEmpty()) + continue; + if (!(hasLocalTypes(type) || hasWFSTypes(type))) + return false; // One of the given names is not available. + } + + return true; + } + + /** + * + * @param typeName + * @return + * @throws DataStoreException + */ + public FeatureCollection getFeatureCollection(String typeName, Geometry intersectGeom) throws DataStoreException{ + if (store == null) { + Path folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(GeometryUtils.getSha1(site.getGeometry())).resolve(String.valueOf(year)); + if (!Files.isDirectory(folder)) { + folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(site.getName()).resolve(String.valueOf(year)); + } + final Map parameters = new HashMap(); + parameters.put("identifier", "shapefile-folder"); + parameters.put("path", folder.toUri()); + parameters.put("namespace", "no namespace"); + store = (FeatureStore) DataStores.open(parameters); + } + + Set names = store.getNames(); + if (names == null || names.isEmpty()) + throw new DataStoreException("No geo-reference available !"); + GenericName name = null; + final String lcTypeName = typeName.toLowerCase(); + for (final GenericName tmpName : names) { + if (tmpName.tip().toString().toLowerCase().startsWith(lcTypeName)) { + name = tmpName; + } + } + + if (name == null) + throw new IllegalArgumentException("No reference available for name ".concat(typeName)); + + final Session session = store.createSession(true); + final QueryBuilder qb = new QueryBuilder(); + qb.setTypeName(name); + + if(intersectGeom!=null){ + final FilterFactory2 ff = ((FilterFactory2)DefaultFactories.forBuildin(FilterFactory.class)); + qb.setFilter(ff.intersects(new DefaultPropertyName("the_geom"), ff.literal(intersectGeom))); + } + + return session.getFeatureCollection(qb.buildQuery()); + } + + /** + * Compute a geometry which is the union of all geometries in specified + * referential which intersect the geometry given as input. + * @param typeName Name of the geo-referential to use to get geometies to merge. + * @param source Filter geometry. + * @return Computed union, or nothing if we cannot find any geometry intercepting given source. + * @throws IllegalArgumentException If given type name is not available locally. + * @throws DataStoreException If an error occurs while accessing reference data. + */ + public Optional unionIntersecting(final String typeName, final Geometry source) throws DataStoreException { + final FeatureCollection refData = getFeatureCollection(GeoReferentials.TYPE_ZONE_HYDRO, source); + Geometry union = null; + try (FeatureIterator ite = refData.iterator()) { + if (ite.hasNext()) + while (ite.hasNext()) { + final Geometry tmp = (Geometry) ite.next().getDefaultGeometryProperty().getValue(); + union = union==null ? tmp : tmp.union(union); + } + } + + if (union != null) { + JTS.setCRS(union, refData.getFeatureType().getCoordinateReferenceSystem()); + } + return Optional.ofNullable(union); + } + + @Override + public int compareTo(GeoReferential o) { + return year - o.year; + } + + @Override + public void close() throws Exception { + if(store!=null){ + store.close(); + } + } + + public BooleanExpression completed(final String... types) { + if (types == null || types.length < 1) + return new ReadOnlyBooleanWrapper(false).getReadOnlyProperty(); + + final ArrayList deps = new ArrayList<>(types.length); + for (final String type : types) { + if (type == null || type.isEmpty()) + continue; + switch (type.toLowerCase()) { + case GeoReferentials.TYPE_RPG: + deps.add(rpg); + break; + case GeoReferentials.TYPE_ZONE_HYDRO: + deps.add(zoneHydro); + break; + case GeoReferentials.TYPE_TACHE_ARTIF: + deps.add(tacheArtif); + break; + case GeoReferentials.TYPE_TACHE_URBAINE: + deps.add(tacheUrbaine); + break; + } + } + + if (deps.isEmpty()) + return new ReadOnlyBooleanWrapper(false).getReadOnlyProperty(); + + BooleanExpression result = deps.get(0); + for (int i = 1 ; i < deps.size(); i++) { + result = result.and(deps.get(i)); + } + + return result; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferentials.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferentials.java new file mode 100644 index 0000000..c5f74ef --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/GeoReferentials.java @@ -0,0 +1,531 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.preferences.net.NetPreferences; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import org.apache.sis.storage.DataStoreException; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.FeatureStoreRuntimeException; +import org.geotoolkit.data.FeatureWriter; +import org.geotoolkit.data.query.Query; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.data.wfs.GetFeatureRequest; +import org.geotoolkit.data.wfs.WebFeatureClient; +import org.geotoolkit.data.wfs.WebFeatureException; +import org.geotoolkit.display2d.GO2Utilities; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.feature.FeatureTypeBuilder; +import org.geotoolkit.feature.FeatureUtilities; +import org.geotoolkit.feature.type.AttributeDescriptor; +import org.geotoolkit.feature.type.FeatureType; +import org.geotoolkit.feature.type.PropertyDescriptor; +import org.geotoolkit.feature.xml.XmlFeatureReader; +import org.geotoolkit.feature.xml.jaxp.JAXPStreamFeatureReader; +import org.geotoolkit.filter.DefaultPropertyName; +import org.geotoolkit.map.FeatureMapLayer; +import org.geotoolkit.map.MapBuilder; +import org.geotoolkit.map.MapItem; +import org.geotoolkit.nio.IOUtilities; +import org.geotoolkit.wfs.xml.WFSVersion; +import org.opengis.filter.Filter; +import org.opengis.util.FactoryException; +import org.opengis.util.GenericName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Manage geographic referential. + * + * @author Johann Sorel (Geomatys) + */ +@Component +public class GeoReferentials { + + public static final String TYPE_ZONE_HYDRO = "zonehydro"; + public static final String TYPE_RPG = "rpg"; + public static final String TYPE_TACHE_ARTIF = "tacheartif"; + public static final String TYPE_TACHE_URBAINE = "tacheurbaine"; + private static final List NAMES = Arrays.asList(TYPE_ZONE_HYDRO,TYPE_RPG,TYPE_TACHE_ARTIF,TYPE_TACHE_URBAINE); + + @Autowired + private NetPreferences prefs; + + //cache WFS client + private WebFeatureClient wfs; + + private synchronized WebFeatureClient getWFSClient() throws MalformedURLException, DataStoreException, WebFeatureException{ + if(wfs==null){ + String url = prefs.getPreference(NetPreferences.WFS_URL); + if (url == null || (url = url.trim()).isEmpty()) + throw new IllegalStateException("Aucune URL disponible pour les référentiels géographiques"); + wfs = new WebFeatureClient(new URL(url), null, WFSVersion.v110, true); + wfs.getNames(); + } + return wfs; + } + + /** + * Combine local and server referentials. + * + * @param site + * @return + */ + public ObservableList getReferentials(Site site) throws IllegalArgumentException, IOException, DataStoreException{ + final ObservableList refs = getLocalReferentials(site); + + try { + ObservableList wfsReferentials = getWFSReferentials(); + loop: + for(GeoReferential gr : wfsReferentials){ + for(GeoReferential r : refs){ + if(r.compareTo(gr)==0){ + r.tacheArtifWFS().set(gr.tacheArtifWFS().get()); + r.tacheUrbaineWFS().set(gr.tacheUrbaineWFS().get()); + r.zoneHydroWFS().set(gr.zoneHydroWFS().get()); + r.rpgWFS().set(gr.rpgWFS().get()); + continue loop; + } + } + refs.add(gr); + } + } catch (Exception ex) { + if(refs.isEmpty()){ + throw ex; + }else{ + RhomeoCore.LOGGER.log(Level.WARNING, null, ex); + } + } + + //sort by year + Collections.sort(refs); + + return refs; + } + + /** + * List available local referentials for given site. + * + * @param site + * @param year + * @return + */ + public GeoReferential getLocalReferential(Site site, int year) throws IllegalArgumentException, IOException { + Path folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(GeometryUtils.getSha1(site.getGeometry())); + if (!Files.isDirectory(folder)) { + folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(site.getName()); + } + final String strYear = String.valueOf(year); + for(Path p : IOUtilities.listChildren(folder)){ + if(Files.isDirectory(p)){ + if(p.getFileName().toString().equals(strYear)){ + final GeoReferential ref = new GeoReferential(site, year); + for(Path tp : IOUtilities.listChildren(p)){ + final String tName = tp.getFileName().toString().toLowerCase(); + if(tName.startsWith(TYPE_ZONE_HYDRO)){ + ref.zoneHydroLocal().set(true); + }else if(tName.startsWith(TYPE_TACHE_URBAINE)){ + ref.tacheUrbaineLocal().set(true); + }else if(tName.startsWith(TYPE_TACHE_ARTIF)){ + ref.tacheArtifLocal().set(true); + }else if(tName.startsWith(TYPE_RPG)){ + ref.rpgLocal().set(true); + } + } + return ref; + } + } + } + throw new IOException("Referential for year "+year+" not found"); + } + + /** + * List available local referentials for given site. + * + * @param site + * @return + */ + public ObservableList getLocalReferentials(Site site) throws IllegalArgumentException, IOException { + final ObservableList lst = FXCollections.observableArrayList(); + Path folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(GeometryUtils.getSha1(site.getGeometry())); + if (!Files.isDirectory(folder)) { + folder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(site.getName()); + } + if(Files.isDirectory(folder)){ + for(Path p : IOUtilities.listChildren(folder)) { + if (Files.isDirectory(p)) { + final int year; + // Ignore directories not matching year names. + try { + year = Integer.valueOf(p.getFileName().toString()); + } catch (NumberFormatException e) { + continue; + } + + final GeoReferential ref = new GeoReferential(site,year); + for(Path tp : IOUtilities.listChildren(p)){ + final String tName = tp.getFileName().toString().toLowerCase(); + if(tName.startsWith(TYPE_ZONE_HYDRO)){ + ref.zoneHydroLocal().set(true); + }else if(tName.startsWith(TYPE_TACHE_URBAINE)){ + ref.tacheUrbaineLocal().set(true); + }else if(tName.startsWith(TYPE_TACHE_ARTIF)){ + ref.tacheArtifLocal().set(true); + }else if(tName.startsWith(TYPE_RPG)){ + ref.rpgLocal().set(true); + } + } + lst.add(ref); + } + } + } + return lst; + } + + /** + * List server referentials. + * + * @return + * @throws DataStoreException + * @throws MalformedURLException + */ + public ObservableList getWFSReferentials() throws DataStoreException, MalformedURLException, WebFeatureException{ + + final Map count = new HashMap<>(); + try (WebFeatureClient client = getWFSClient()) { + final Set names = client.getNames(); + for(GenericName name : names){ + final String[] parts = name.tip().toString().toLowerCase().split("_"); + if(parts.length==2 && NAMES.contains(parts[0])){ + GeoReferential ref = count.get(parts[1]); + if(ref==null) ref = new GeoReferential(null, Integer.valueOf(parts[1])); + switch(parts[0]){ + case TYPE_ZONE_HYDRO : ref.zoneHydroWFS().set(true); break; + case TYPE_RPG : ref.rpgWFS().set(true); break; + case TYPE_TACHE_URBAINE : ref.tacheUrbaineWFS().set(true); break; + case TYPE_TACHE_ARTIF : ref.tacheArtifWFS().set(true); break; + } + count.put(parts[1], ref); + } + } + } + + return FXCollections.observableArrayList(count.values()); + } + + public MapItem getWFSLayers(final Optional filter, final String... referentialNames) throws DataStoreException, MalformedURLException { + MapItem result = MapBuilder.createItem(); + result.setName("WFS layers"); + try (WebFeatureClient client = getWFSClient()) { + final Set names = client.getNames().stream() + .filter(name -> { + if (referentialNames == null || referentialNames.length < 1) + return true; + for (final String refName : referentialNames) { + if (name.tip().toString().toLowerCase().startsWith(refName.toLowerCase())) + return true; + } + return false; + }) + .collect(Collectors.toSet()); + for (GenericName name : names) { + Query q = filter + .map(f -> { + final QueryBuilder builder = new QueryBuilder(name); + builder.setFilter(f); + builder.setProperties(new String[]{"geom"}); + return builder.buildQuery(); + }) + .orElseGet(() -> QueryBuilder.all(name)); + final FeatureMapLayer layer = MapBuilder.createFeatureLayer( + client.createSession(false).getFeatureCollection(q) + ); + layer.setName(name.tip().toString()); + layer.setVisible(false); + result.items().add(layer); + } + } + return result; + } + + /** + * Download from WFS service the referential for specified year. + * + * @param site analyze site + * @param year georeferential year + * @throws IllegalArgumentException If we cannot find any hydrographic zone + * intersecting site buffer. + * @throws Exception Any other exception is a read/write error. + */ + public void downloadReferential(Site site, int year) throws Exception { + + final Path localFolder = RhomeoCore.REFERENCIELS_GEO_PATH.resolve(GeometryUtils.getSha1(site.getGeometry())).resolve(String.valueOf(year)); + + //extract only data on site envelope + //not : this will be replace by hydro area after it has been downloaded + final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry()); + + //connect to WFS service + try (WebFeatureClient client = getWFSClient()) { + // First, we download hydrology data, because there's a special treatment over it. + downloadReferential(TYPE_ZONE_HYDRO + '_' + year, localFolder, buffer, false, client); + Geometry[] zhs = getLocalReferential(site, year).getFeatureCollection(TYPE_ZONE_HYDRO, null).stream() + .map(feat -> feat.getDefaultGeometryProperty().getValue()) + .filter(geom -> geom instanceof Geometry) + .toArray(size -> new Geometry[size]); + Geometry zhUnion = GO2Utilities.JTS_FACTORY.createGeometryCollection(zhs).union(); + + // Get other referentials + final Set names = new HashSet<>(NAMES); + names.remove(TYPE_ZONE_HYDRO); + for (String name : names) { + //some layers may be missing, P08 and P09 use different refentials. + try { + downloadReferential(name + '_' + year, localFolder, zhUnion, true, client); + } catch (IllegalArgumentException e) { + RhomeoCore.LOGGER.log(Level.FINE, "Cannot download referential", e); + } + } + } catch (Exception ex) { + //erase local folder if an error occurs + IOUtilities.deleteRecursively(localFolder); + throw ex; + } + } + + /** + * Download a single referential using given parameters. + * @param refName Name of the layer to request. + * @param targetDir Folder to put downloaded data into. + * @param zone Geometry delimiting the area to download (intersection filter performed). + * @param cut + * @param client Client to use to query WFS service. + * @throws DataStoreException Error on reading / writing. + * @throws IOException Error on reading / writing. + * @throws FactoryException Error on reading / writing. + * @throws XMLStreamException Error on reading / writing. + * @throws IllegalArgumentException If we cannot find given layer name on + * the server, or no data intersects given zone. + */ + protected void downloadReferential(final String refName, final Path targetDir, final Geometry zone, final boolean cut, final WebFeatureClient client) + throws DataStoreException, IOException, FactoryException, XMLStreamException { + //find feature type full name + GenericName fullName = null; + for (GenericName n : client.getNames()) { + if (n.tip().toString().equalsIgnoreCase(refName)) { + fullName = n; + break; + } + } + + if (fullName == null) { // TODO : throw illegalArgumentException managed above. + throw new IllegalArgumentException("No layer found for name ".concat(refName)); + } + + //prepare query + final FeatureType featureType = client.getFeatureType(fullName); + final FeatureTypeBuilder outputBuilder = new FeatureTypeBuilder(); + outputBuilder.setName(fullName); + final QueryBuilder qb = new QueryBuilder(fullName); + // We are only able to store simple features, so we remove complex + // structure from WFS data. + final List props = new ArrayList<>(); + for (PropertyDescriptor desc : featureType.getDescriptors()) { + if (isSimpleAttribute(desc)) { + props.add(desc.getName().toString()); + outputBuilder.add(desc); + } + } + + qb.setProperties(props.toArray(new String[0])); + qb.setCRS(RhomeoCore.getSiteCRS()); + //extract only data on site envelope + qb.setFilter(GO2Utilities.FILTER_FACTORY.intersects( + new DefaultPropertyName(featureType.getGeometryDescriptor().getName().tip().toString()), + GO2Utilities.FILTER_FACTORY.literal(zone) + )); + + //copy data to local folder + Files.createDirectories(targetDir); + try (final FeatureReader reader = getStreamingReader(client, qb.buildQuery()); + final ShapefileFeatureStore outStore = new ShapefileFeatureStore(targetDir.resolve(refName + ".shp").toUri())) { + if (!reader.hasNext()) + throw new IllegalArgumentException("No data intersects given zone for layer ".concat(fullName.toString())); + outStore.createFeatureType(fullName, outputBuilder.buildSimpleFeatureType()); + try (final FeatureWriter writer = outStore.getFeatureWriterAppend(fullName)) { + if (cut) { + while (reader.hasNext()) { + final Feature next = reader.next(); + Object value = next.getDefaultGeometryProperty().getValue(); + if (value instanceof Geometry) + next.getDefaultGeometryProperty().setValue(zone.intersection((Geometry) value)); + FeatureUtilities.copy(next, writer.next(), false); + } + } else { + while (reader.hasNext()) { + FeatureUtilities.copy(reader.next(), writer.next(), false); + } + } + } + } + } + + private boolean isSimpleAttribute(final PropertyDescriptor property) { + return property.getMinOccurs() == 1 && + property.getMaxOccurs() == 1 && + property instanceof AttributeDescriptor; + } + + /** + * Perform a getFeature on the distant server, browing its content lazily. + * @param client The client to use for request. + * @param query The query which contains reading parameters. + * @return A reader to stream service response. + * @throws DataStoreException + * @throws IOException + * @throws XMLStreamException + */ + private FeatureReader getStreamingReader(final WebFeatureClient client, final Query query) throws DataStoreException, IOException, XMLStreamException { + + final GetFeatureRequest request = client.createGetFeature(); + request.setTypeName(new QName(query.getTypeName().tip().toString())); + + final Filter filter = query.getFilter(); + if (filter == null) { + request.setFilter(Filter.INCLUDE); + } else { + request.setFilter(filter); + } + + final Integer max = query.getMaxFeatures(); + if (max != null) { + request.setMaxFeatures(max); + } + + request.setPropertyNames(query.getPropertyNames()); + + XmlFeatureReader reader = null; + reader = new JAXPStreamFeatureReader(client.getFeatureType(query.getTypeName())); + reader.getProperties().put(JAXPStreamFeatureReader.SKIP_UNEXPECTED_PROPERTY_TAGS, true); + final InputStream stream; + if (client.getUsePost()) { + stream = request.getResponseStream(); + } else { + final URL url = request.getURL(); + stream = url.openStream(); + } + + // If reader creation fails, stream isn't hanging on. + try { + return new StreamingFeatureReader(reader.readAsStream(stream), stream); + } catch (Throwable t) { + try { + stream.close(); + } catch (Throwable t1) { + t.addSuppressed(t1); + } + throw t; + } + } + + private static class StreamingFeatureReader implements FeatureReader { + + final FeatureReader source; + final InputStream dataSource; + + public StreamingFeatureReader(final FeatureReader source, final InputStream dataSource) { + this.source = source; + this.dataSource = dataSource; + } + + @Override + public FeatureType getFeatureType() { + return source.getFeatureType(); + } + + @Override + public Feature next() throws FeatureStoreRuntimeException { + return source.next(); + } + + @Override + public boolean hasNext() throws FeatureStoreRuntimeException { + return source.hasNext(); + } + + @Override + public void close() { + try { + source.close(); + } finally { + try { + dataSource.close(); + } catch (IOException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "An error occurred while closing data stream.", ex); + } + } + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReference.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReference.java new file mode 100644 index 0000000..58f0819 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReference.java @@ -0,0 +1,218 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.Reference; +import java.util.Objects; + +/** + * Odonata presence by department. + * + * Colonne | Type | Modificateurs + * ---------+------------------------+--------------- + * espece | character varying(255) | + * dept | character varying(255) | + * valeur | double precision | + * id_dept | character varying(2) | + * cd_nom | integer | + * cd_ref | integer | + * + * (Source : base issue du script RhoMéO_Geomatys.backup) + * + * Note : There is no explicit primary key, but it seems logic for the key to be + * (cd_nom, id_dept). + * + * Remarques : + * + * L'absence de contrainte d'intégrité rend l'analyse sémantique de la table non + * triviale. + * + * Sémantique + * ========== + * + * Même si logiquement la clef devrait être (cd_nom, id_dept), il + * semble que les combinaisons (cd_ref, id_dept) soient également "sémantiquement" uniques + * car en nombre identique. + * + * select count(*) from (select distinct cd_ref, id_dept from odo_especes_par_dept) as tab; + * count + *------- + * 2436 + * + * select count(*) from (select distinct cd_nom, id_dept from odo_especes_par_dept) as tab; + * count + *------- + * 2436 + * + * Toutefois, on ne peut pas en déduire l'identité entre cd_nom et cd_ref dans la + * table (ce qui est logique). Nous avons d'ailleurs : + * + * select distinct cd_nom, cd_ref from odo_especes_par_dept where cd_nom<>cd_ref; + * cd_nom | cd_ref + *--------+-------- + * 820003 | 645873 + * + * + * Creation of the reference list from the database + * ================================================ + * + * Export csv + * ---------- + * copy ( + * select * from odo_especes_par_dept + * ) + * to '/…/odo_especes_par_dept.csv' delimiter ';' csv header; + * + * Dumps + * ----- + * pg_dump -t referentiels_non_geo.odo_especes_par_dept rhomeo > /…/odo_especes_par_dept.copy.sql + * pg_dump --inserts -t referentiels_non_geo.odo_especes_par_dept rhomeo > /…/odo_especes_par_dept.inserts.sql + * + * + * + * @author Samuel Andrés (Geomatys) + */ +public class OdonataDepartmentReference implements Reference { + + // META-INFORMATION : ATTRIBUTE NAMES + // MUST BE CONSISTENT WITH CLASS ATTRIBUTES + public static final String ATT_CD_NOM = "cd_nom"; + public static final String ATT_CD_REF = "cd_ref"; + public static final String ATT_ESPECE = "espece"; + public static final String ATT_ID_DEPT = "id_dept"; + public static final String ATT_DEPT = "dept"; + public static final String ATT_VALEUR = "valeur"; + + // Species attributes + private int cd_nom; + private int cd_ref; + private String espece; + + // Department attributes + private String id_dept; + private String dept; + + private double valeur; + + public int getCd_nom() { + return cd_nom; + } + + public void setCd_nom(int cd_nom) { + this.cd_nom = cd_nom; + } + + public int getCd_ref() { + return cd_ref; + } + + public void setCd_ref(int cd_ref) { + this.cd_ref = cd_ref; + } + + public String getEspece() { + return espece; + } + + public void setEspece(String espece) { + this.espece = espece; + } + + public String getId_dept() { + return id_dept; + } + + public void setId_dept(String id_dept) { + this.id_dept = id_dept; + } + + public String getDept() { + return dept; + } + + public void setDept(String dept) { + this.dept = dept; + } + + public double getValeur() { + return valeur; + } + + public void setValeur(double valeur) { + this.valeur = valeur; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + this.cd_nom; // same cd_nom => same cd_ref and same espece (interpretation) + hash = 89 * hash + Objects.hashCode(this.id_dept); // same id_dept => same dept + hash = 89 * hash + (int) (Double.doubleToLongBits(this.valeur) ^ (Double.doubleToLongBits(this.valeur) >>> 32)); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OdonataDepartmentReference other = (OdonataDepartmentReference) obj; + if (this.cd_nom != other.cd_nom) { + return false; + } // same cd_nom => same cd_ref and same espece (interpretation) + if (Double.doubleToLongBits(this.valeur) != Double.doubleToLongBits(other.valeur)) { + return false; + } + if (!Objects.equals(this.id_dept, other.id_dept)) { + return false; + } // same id_dept => same dept + return true; + } + + @Override + public String toString() { + return new StringBuilder(Integer.toString(cd_nom)).append('|').append(espece).append('|').append(dept).append('|').append(valeur).toString(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReferenceDescription.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReferenceDescription.java new file mode 100644 index 0000000..619dcf5 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataDepartmentReferenceDescription.java @@ -0,0 +1,75 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import java.util.Collection; +import java.util.Collections; +import org.springframework.stereotype.Component; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +@Component +public class OdonataDepartmentReferenceDescription implements ReferenceDescription { + + public static final String ODONATA_DEPARTMENT_DESCRIPTION_NAME = "odo_especes_par_dept"; + + @Override + public Class getReferenceType() { + return OdonataDepartmentReference.class; + } + + @Override + public String getName() { + return ODONATA_DEPARTMENT_DESCRIPTION_NAME; + } + + @Override + public Collection getAlias() { + return Collections.singleton("Répartition des odonates par département"); + } + + @Override + public String getRemarks() { + return null; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReference.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReference.java new file mode 100644 index 0000000..b7994f8 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReference.java @@ -0,0 +1,208 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.Reference; +import java.util.Objects; + +/** + * + * Affinity of Odonata species with different kinds of odonatologic habitat. + * + * Value range is from 1 to 4. + * + * Colonne | Type | Modificateurs + *---------+------------------------+--------------- + * espece | character varying(255) | + * biogeo | character varying(255) | + * habitat | character varying(255) | + * valeur | character varying(255) | + * zbio | character varying(255) | + * cd_nom | integer | + * cd_ref | integer | + * + * (Source : base issue du script RhoMéO_Geomatys.backup) + * + * Note : There is no explicit primary key, but it seems logic for the key to be + * (cd_nom, zbio, habitat). + * + * Creation of the reference list from the database + * ================================================ + * + * Export csv + * ---------- + * copy ( + * select * from odo_dependance_habitat + * ) + * to '/…/odo_dependance_habitat.csv' delimiter ';' csv header; + * + * Dumps + * ----- + * pg_dump -t referentiels_non_geo.odo_dependance_habitat rhomeo > /…/odo_dependance_habitat.copy.sql + * pg_dump --inserts -t referentiels_non_geo.odo_dependance_habitat rhomeo > /…/odo_dependance_habitat.inserts.sql + * + * @author Samuel Andrés (Geomatys) + */ +public class OdonataHabitatReference implements Reference { + + // META-INFORMATION : ATTRIBUTE NAMES + // MUST BE CONSISTENT WITH CLASS ATTRIBUTES + public static final String ATT_CD_NOM = "cd_nom"; + public static final String ATT_CD_REF = "cd_ref"; + public static final String ATT_ESPECE = "espece"; + public static final String ATT_ZBIO = "zbio"; + public static final String ATT_BIOGEO = "biogeo"; + public static final String ATT_HABITAT = "habitat"; + public static final String ATT_VALEUR = "valeur"; + + // Species attributes + private int cd_nom; + private int cd_ref; + private String espece; + + // biozone attributes + private String zbio; + private String biogeo; + + // Habitat + private String habitat; + + // Value for a association between a species, a biozone and an habitat. + private int valeur; + + public int getCd_nom() { + return cd_nom; + } + + public void setCd_nom(int cd_nom) { + this.cd_nom = cd_nom; + } + + public int getCd_ref() { + return cd_ref; + } + + public void setCd_ref(int cd_ref) { + this.cd_ref = cd_ref; + } + + public String getEspece() { + return espece; + } + + public void setEspece(String espece) { + this.espece = espece; + } + + public String getZbio() { + return zbio; + } + + public void setZbio(String zbio) { + this.zbio = zbio; + } + + public String getBiogeo() { + return biogeo; + } + + public void setBiogeo(String biogeo) { + this.biogeo = biogeo; + } + + public String getHabitat() { + return habitat; + } + + public void setHabitat(String habitat) { + if (habitat != null && habitat.startsWith("0")) + habitat = habitat.substring(1); + this.habitat = habitat; + } + + public int getValeur() { + return valeur; + } + + public void setValeur(int valeur) { + this.valeur = valeur; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + this.cd_nom; // same cd_nom => same cd_ref and same espece (interpretation) + hash = 59 * hash + Objects.hashCode(this.zbio); // same zbio => same biogeo (interpretation) + hash = 59 * hash + Objects.hashCode(this.habitat); + hash = 61 * hash + this.valeur; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OdonataHabitatReference other = (OdonataHabitatReference) obj; + if (this.cd_nom != other.cd_nom) { + return false; + } // same cd_nom => same cd_ref and same espece (interpretation) + if (!Objects.equals(this.zbio, other.zbio)) { + return false; + } // same zbio => same biogeo (interpretation) + if (!Objects.equals(this.habitat, other.habitat)) { + return false; + } + if (this.valeur != other.valeur) { + return false; + } + return true; + } + + @Override + public String toString() { + return new StringBuilder(Integer.toString(cd_nom)).append('|').append(cd_ref).append('|').append(espece).append('|').append(habitat).append('|').append(valeur).toString(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReferenceDescription.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReferenceDescription.java new file mode 100644 index 0000000..d191838 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OdonataHabitatReferenceDescription.java @@ -0,0 +1,75 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import java.util.Collection; +import java.util.Collections; +import org.springframework.stereotype.Component; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +@Component +public class OdonataHabitatReferenceDescription implements ReferenceDescription { + + public static final String ODONATA_HABITAT_DESCRIPTION_NAME = "odo_dependance_habitat"; + + @Override + public Class getReferenceType() { + return OdonataHabitatReference.class; + } + + @Override + public String getName() { + return ODONATA_HABITAT_DESCRIPTION_NAME; + } + + @Override + public Collection getAlias() { + return Collections.singleton("Liste des affinités des odonates par habitat odonatologique"); + } + + @Override + public String getRemarks() { + return null; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReference.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReference.java new file mode 100644 index 0000000..43914ad --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReference.java @@ -0,0 +1,245 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.Reference; + +/** + * Orthopterans indicator reference representation. + * + * Colonne | Type | Modificateurs + * -------------------+------------------------+--------------- + * cd_nom | integer | non NULL + * cd_ref | integer | + * lb_nom | character varying(255) | + * pres_lr | integer | + * pres_ra | integer | + * pres_paca | integer | + * hs_code | integer | + * hs_val | integer | + * dm_code | integer | + * dm_val | integer | + * mediterraneen1_ml | integer | + * mediterraneen1_ma | integer | + * mediterraneen2_ma | integer | + * mediterraneen2_tb | integer | + * alpin_ma | integer | + * alpin_tb | integer | + * continental_ma | integer | + * continental_tb | integer | + * + * (Source : base issue du script RhoMéO_Geomatys.backup) + * + * @author Samuel Andrés (Geomatys) + */ +public class OrthopteraIndicatorReference implements Reference { + + /** + * CD_REF of the reference taxon. + */ + private int cd_ref; + + private int mediterraneen1_ml;// Pourquoi pas booleen ? + private int mediterraneen1_ma;// Pourquoi pas booleen ? + private int mediterraneen2_ma;// Pourquoi pas booleen ? + private int mediterraneen2_tb;// Pourquoi pas booleen ? + private int alpin_ma;// Pourquoi pas booleen ? + private int alpin_tb;// Pourquoi pas booleen ? + private int continental_ma;// Pourquoi pas booleen ? + private int continental_tb;// Pourquoi pas booleen ? + + /** + * Name of the taxon. + */ + private String lb_nom; + + private int hs_code; + + /** + * Environment humidity + */ + private int hs_val; + + private int dm_code; + + /** + * Sedimentary dynamics + */ + private int dm_val; + + public int getCd_ref() { + return cd_ref; + } + + public void setCd_ref(int cd_ref) { + this.cd_ref = cd_ref; + } + + public int getMediterraneen1_ml() { + return mediterraneen1_ml; + } + + public void setMediterraneen1_ml(int mediterraneen1_ml) { + this.mediterraneen1_ml = mediterraneen1_ml; + } + + public int getMediterraneen1_ma() { + return mediterraneen1_ma; + } + + public void setMediterraneen1_ma(int mediterraneen1_ma) { + this.mediterraneen1_ma = mediterraneen1_ma; + } + + public int getMediterraneen2_ma() { + return mediterraneen2_ma; + } + + public void setMediterraneen2_ma(int mediterraneen2_ma) { + this.mediterraneen2_ma = mediterraneen2_ma; + } + + public int getMediterraneen2_tb() { + return mediterraneen2_tb; + } + + public void setMediterraneen2_tb(int mediterraneen2_tb) { + this.mediterraneen2_tb = mediterraneen2_tb; + } + + public int getAlpin_ma() { + return alpin_ma; + } + + public void setAlpin_ma(int alpin_ma) { + this.alpin_ma = alpin_ma; + } + + public int getAlpin_tb() { + return alpin_tb; + } + + public void setAlpin_tb(int alpin_tb) { + this.alpin_tb = alpin_tb; + } + + public int getContinental_ma() { + return continental_ma; + } + + public void setContinental_ma(int continental_ma) { + this.continental_ma = continental_ma; + } + + public int getContinental_tb() { + return continental_tb; + } + + public void setContinental_tb(int continental_tb) { + this.continental_tb = continental_tb; + } + + public String getLb_nom() { + return lb_nom; + } + + public void setLb_nom(String lb_nom) { + this.lb_nom = lb_nom; + } + + public int getHs_code() { + return hs_code; + } + + public void setHs_code(int hs_code) { + this.hs_code = hs_code; + } + + public int getHs_val() { + return hs_val; + } + + public void setHs_val(int hs_val) { + this.hs_val = hs_val; + } + + public int getDm_code() { + return dm_code; + } + + public void setDm_code(int dm_code) { + this.dm_code = dm_code; + } + + public int getDm_val() { + return dm_val; + } + + public void setDm_val(int dm_val) { + this.dm_val = dm_val; + } + + @Override + public int hashCode() { + return cd_ref; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final OrthopteraIndicatorReference other = (OrthopteraIndicatorReference) obj; + return getCd_ref() == other.getCd_ref() + && getHs_val() == other.getHs_val() + && getDm_val() == other.getDm_val(); + } + + @Override + public String toString() { + return new StringBuilder().append(cd_ref).append(' ').append(lb_nom).toString(); + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReferenceDescription.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReferenceDescription.java new file mode 100644 index 0000000..2d57add --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/OrthopteraIndicatorReferenceDescription.java @@ -0,0 +1,75 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import java.util.Collection; +import java.util.Collections; +import org.springframework.stereotype.Component; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +@Component +public class OrthopteraIndicatorReferenceDescription implements ReferenceDescription { + + public static final String ORTHOPTERA_INDICATOR_DESCRIPTION_NAME = "ortho_indicateurs"; + + @Override + public Class getReferenceType() { + return OrthopteraIndicatorReference.class; + } + + @Override + public String getName() { + return ORTHOPTERA_INDICATOR_DESCRIPTION_NAME; + } + + @Override + public Collection getAlias() { + return Collections.singleton("Liste des valeurs indicatrices des orthoptères"); + } + + @Override + public String getRemarks() { + return null; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/Taxref.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/Taxref.java new file mode 100644 index 0000000..50b32ab --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/Taxref.java @@ -0,0 +1,202 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.Reference; +import java.util.Objects; + +/** + * Taxref taxonomic referential representation. + * + * + * Colonne | Type | Modificateurs + * --------------+----------------------+--------------- + * regne | text | + * phylum | text | + * classe | text | + * ordre | text | + * famille | text | + * cd_nom | character varying(6) | non NULL + * cd_taxsup | character varying(6) | + * cd_ref | character varying(6) | + * rang | text | + * lb_nom | text | + * lb_auteur | text | + * nom_complet | text | + * nom_valide | text | + * nom_vern | text | + * nom_vern_eng | text | + * habitat | text | + * fr | text | + * gf | text | + * mar | text | + * gua | text | + * sm | text | + * sb | text | + * spm | text | + * may | text | + * epa | text | + * reu | text | + * taaf | text | + * pf | text | + * nc | text | + * wf | text | + * cli | text | + * aphia_id | text | + * + * (Source : base issue du script RhoMéO_Geomatys.backup) + * + * @author Samuel Andrés + * + * @see TaxrefDescription + */ +public class Taxref implements Reference { + + // META-INFORMATION : ATTRIBUTE NAMES + // MUST BE CONSISTENT WITH CLASS ATTRIBUTES + public static final String ATT_CD_NOM = "cd_nom"; + public static final String ATT_CD_TAXSUP = "cd_taxsup"; + public static final String ATT_CD_REF = "cd_ref"; + public static final String ATT_RANG = "rang"; + public static final String ATT_NOM_COMPLET = "nomComplet"; + + // Other constants + public static final String RANG_SSES = "SSES"; + + /** + * Primary key. + */ + private int cd_nom; + + private int cd_taxsup; + + private int cd_ref; + + private String rang; + + private String lb_nom; + + private String ordre; + + private String classe; + + public int getCd_nom() { + return cd_nom; + } + + public void setCd_nom(int cd_nom) { + this.cd_nom = cd_nom; + } + + public int getCd_taxsup() { + return cd_taxsup; + } + + public void setCd_taxsup(int cd_taxsup) { + this.cd_taxsup = cd_taxsup; + } + + public int getCd_ref() { + return cd_ref; + } + + public void setCd_ref(int cd_ref) { + this.cd_ref = cd_ref; + } + + public String getRang() { + return rang; + } + + public void setRang(String rang) { + this.rang = rang; + } + + public String getLb_nom() { + return lb_nom; + } + + public void setLb_nom(String nomComplet) { + this.lb_nom = nomComplet; + } + + public String getOrdre() { + return ordre; + } + + public void setOrdre(String ordre) { + this.ordre = ordre; + } + + public String getClasse() { + return classe; + } + + public void setClasse(String classe) { + this.classe = classe; + } + + @Override + public int hashCode() { + return cd_nom; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Taxref other = (Taxref) obj; + if (!Objects.equals(this.cd_nom, other.cd_nom)) { + return false; + } + return true; + } + + @Override + public String toString() { + return lb_nom; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/reference/TaxrefDescription.java b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/TaxrefDescription.java new file mode 100644 index 0000000..0b7fa40 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/reference/TaxrefDescription.java @@ -0,0 +1,76 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.reference; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import java.util.Collection; +import java.util.Collections; +import org.springframework.stereotype.Component; + +/** + * Description of the {@link Taxref} reference type. + * + * @author Samuel Andrés (Geomatys) + */ +@Component +public class TaxrefDescription implements ReferenceDescription { + + private static final String TAXREF_DESCRIPTION_NAME = "taxref"; + + @Override + public Class getReferenceType() { + return Taxref.class; + } + + @Override + public String getName() { + return TAXREF_DESCRIPTION_NAME; + } + + @Override + public Collection getAlias() { + return Collections.singletonList("Référentiel taxonomique"); + } + + @Override + public String getRemarks() { + return null; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/site/DuplicatedKeyException.java b/core/src/main/java/fr/cenra/rhomeo/core/data/site/DuplicatedKeyException.java new file mode 100644 index 0000000..d230d98 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/site/DuplicatedKeyException.java @@ -0,0 +1,55 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +/** + * Exception to launch when trying to use the same key for several items in the same collection. + * + * @author Cédric Briançon (Geomatys) + */ +public class DuplicatedKeyException extends Exception { + + public DuplicatedKeyException() { + super(); + } + + public DuplicatedKeyException(final String message) { + super(message); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/site/RhomeoRepository.java b/core/src/main/java/fr/cenra/rhomeo/core/data/site/RhomeoRepository.java new file mode 100644 index 0000000..6cdb58b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/site/RhomeoRepository.java @@ -0,0 +1,103 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +import fr.cenra.rhomeo.api.IdentifiedObject; +import javafx.collections.ObservableList; +import javax.validation.constraints.NotNull; +import org.hibernate.validator.constraints.NotBlank; + + +/** + * Generic data access methods all project repositories should have. + * + * @author Cédric Briançon (Geomatys) + * @param + */ +public interface RhomeoRepository { + /** + * Find the item for the given key. + * + * @param key An item key + * @return The matching item, or {@code null} if not found. + */ + I findOne(@NotBlank String key); + + /** + * Create an item in the data source if the item key is not already present in the store, + * otherwise throws {@link DuplicatedKeyException}. + * + * @param object The new item to create, should not be {@code null} + * @throws DuplicatedKeyException if an object already exists with the same key + */ + void create(@NotNull I object) throws DuplicatedKeyException; + + /** + * Find all items in the data source. + * + * @return A list of items stored, eventually an empty list, never {@code null} + */ + ObservableList findAll(); + + /** + * Update an existing item in the data source. Its key should be the same in the + * given object and in the stored version in order to know which item has to be updated. + * If the given object key is not known in the store, then do nothing. + * + * @param object Item to update, with new values. Should not be {@code null}. + */ + void update(@NotNull I object); + + /** + * Update an existing item in the data source. The given object may have a new key specified, + * so use the old key parameter to know which item should be updated. + * + * @param object Item to update, with new values. Should not be {@code null}. + * @param oldKey Existing item key to update. + * @throws DuplicatedKeyException if the given key in the object parameter is already used. + */ + void update(@NotNull I object, String oldKey) throws DuplicatedKeyException; + + /** + * Delete an existing item in the data source. + * + * @param key Key of the item to delete. + */ + void delete(@NotBlank String key); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteImpl.java b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteImpl.java new file mode 100644 index 0000000..e6af6f1 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteImpl.java @@ -0,0 +1,229 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +import com.vividsolutions.jts.geom.MultiPolygon; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.annotations.RefersTo; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.list.HumidZoneType; +import fr.cenra.rhomeo.core.list.OdonateZoneBio; +import fr.cenra.rhomeo.core.list.OrthoptereZoneBio; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** + * POJO representing a site. No intelligence in here. + * + * @author Cédric Briançon (Geomatys) + */ +public class SiteImpl implements Site, Cloneable, InternationalResource { + + private MultiPolygon geometry; + private String countyCode; + private String referent; + private String organization; + private String zoneType; + private String odonateType; + private String orthoptereType; + private String name; + private String remarks; + + private SiteImpl() {} + + public SiteImpl(MultiPolygon geometry) { + this.geometry = geometry; + } + + public SiteImpl(MultiPolygon geometry, String countyCode, String referent, String organization, String zoneType, + String odonateType, String orthoptereType, String name, String remarks) { + this.geometry = geometry; + this.countyCode = countyCode; + this.referent = referent; + this.organization = organization; + this.zoneType = zoneType; + this.odonateType = odonateType; + this.orthoptereType = orthoptereType; + this.name = name; + this.remarks = remarks; + } + + @Override + public MultiPolygon getGeometry() { + return geometry; + } + + public void setGeometry(MultiPolygon geometry) { + this.geometry = geometry; + } + + @Override + public String getCountyCode() { + return countyCode; + } + + public void setCountyCode(final String county) { + this.countyCode = county; + } + + @Override + public String getReferent() { + return referent; + } + + public void setReferent(String referent) { + this.referent = referent; + } + + @Override + public String getOrganization() { + return organization; + } + + public void setOrganization(String organization) { + this.organization = organization; + } + + @RefersTo(type = HumidZoneType.class, property = "code") + @Override + public String getZoneType() { + return zoneType; + } + + public void setZoneType(String zoneType) { + this.zoneType = zoneType; + } + + @RefersTo(type = OdonateZoneBio.class, property = "code") + @Override + public String getOdonateType() { + return odonateType; + } + + public void setOdonateType(String odonateType) { + this.odonateType = odonateType; + } + + @Override + public SiteImpl clone() { + final SiteImpl newSite = new SiteImpl(geometry); + newSite.setCountyCode(countyCode); + newSite.setName(name); + newSite.setOdonateType(odonateType); + newSite.setOrthoptereType(orthoptereType); + newSite.setOrganization(organization); + newSite.setReferent(referent); + newSite.setRemarks(remarks); + newSite.setZoneType(zoneType); + + return newSite; + } + + @RefersTo(type= OrthoptereZoneBio.class, property = "code") + @Override + public String getOrthoptereType() { + return orthoptereType; + } + + public void setOrthoptereType(String orthoptereType) { + this.orthoptereType = orthoptereType; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + @Override + public Collection getAlias() { + return Collections.singleton(name); + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object o) { + if (o == null) + return false; + if (getClass() != o.getClass()) + return false; + final SiteImpl other = (SiteImpl) o; + + return Objects.equals(this.geometry, other.geometry) && + RhomeoCore.equivalent(this.countyCode, other.countyCode) && + RhomeoCore.equivalent(this.referent, other.referent) && + RhomeoCore.equivalent(this.organization, other.organization) && + RhomeoCore.equivalent(this.zoneType, other.zoneType) && + RhomeoCore.equivalent(this.odonateType, other.odonateType) && + RhomeoCore.equivalent(this.orthoptereType, other.orthoptereType) && + RhomeoCore.equivalent(this.name, other.name) && + RhomeoCore.equivalent(this.remarks, other.remarks); + } + + @Override + public int hashCode() { + int result = geometry.hashCode(); + result = 31 * result + name.hashCode(); + return result; + } + + @Override + public int compareTo(Site o) { + return o == null ? -1 : getName().compareTo(o.getName()); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepository.java b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepository.java new file mode 100644 index 0000000..a3bca63 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepository.java @@ -0,0 +1,52 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +import fr.cenra.rhomeo.api.data.Site; +import javafx.collections.ObservableList; + +/** + * For spring injection. + * + * @author Cédric Briançon (Geomatys) + */ +public interface SiteRepository extends RhomeoRepository, AutoCloseable { + + ObservableList findNames(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryImpl.java b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryImpl.java new file mode 100644 index 0000000..3ee9443 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryImpl.java @@ -0,0 +1,483 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +import com.vividsolutions.jts.geom.MultiPolygon; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import static fr.cenra.rhomeo.core.RhomeoCore.SITES; +import static fr.cenra.rhomeo.core.RhomeoCore.SITES_SHP_PATH; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.data.site.SiteImpl; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javax.annotation.PreDestroy; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.collection.Cache; +import org.apache.sis.util.iso.Names; +import org.geotoolkit.data.FeatureCollection; +import org.geotoolkit.data.FeatureIterator; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.FeatureWriter; +import org.geotoolkit.data.query.Query; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.data.shapefile.ShapefileFeatureStoreFactory; +import org.geotoolkit.factory.FactoryFinder; +import org.geotoolkit.factory.Hints; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.feature.FeatureBuilder; +import org.geotoolkit.feature.FeatureTypeBuilder; +import org.geotoolkit.feature.type.FeatureType; +import org.geotoolkit.parameter.Parameters; +import org.opengis.filter.Filter; +import org.opengis.filter.FilterFactory2; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.util.FactoryException; +import org.opengis.util.LocalName; +import org.springframework.stereotype.Repository; +import org.springframework.validation.annotation.Validated; + + +/** + * Access point to sites stored in the file system. + * An {@linkplain ObservableList observable list} can be followed for listening + * changes via the {@link #findAll()} method. + * + * @author Cédric Briançon (Geomatys) + */ +@Repository +@Validated +public class SiteRepositoryImpl implements SiteRepository { + + private static final LocalName NAME = Names.createLocalName(null, ":", SITES); + + private static FeatureType SITE_TYPE; + + private final FilterFactory2 ff; + + /** + * Cache result of {@link #findNames() ()} method, to avoid reading the + * shapefile each time a site is updated, created or deleted. + */ + private WeakReference> allNames; + + /** + * Cache loaded sites, to ensure their uniqueness in memory. + */ + private final Cache cache; + + /** + * Feature store to use. + */ + private final ShapefileFeatureStore store; + + /** + * Get or create the {@linkplain #store feature store}, creating all folders needed + * if necessary. + * @throws java.lang.Exception If an error occurs while initializing shapefile. + */ + public SiteRepositoryImpl() throws Exception { + Files.createDirectories(RhomeoCore.SITES_PATH); + + final ParameterValueGroup parameters = ShapefileFeatureStoreFactory.PARAMETERS_DESCRIPTOR.createValue(); + Parameters.getOrCreate(ShapefileFeatureStoreFactory.PATH, parameters) + .setValue(SITES_SHP_PATH.toUri()); + Parameters.getOrCreate(ShapefileFeatureStoreFactory.NAMESPACE, parameters) + .setValue("no namespace"); + + final ShapefileFeatureStoreFactory fact = new ShapefileFeatureStoreFactory(); + + if (Files.exists(SITES_SHP_PATH)) { + store = (ShapefileFeatureStore) fact.open(parameters); + } else { + store = (ShapefileFeatureStore) fact.create(parameters); + store.createFeatureType(NAME, getSiteType()); + } + + // Empty or corrupted file + if (store.getName() == null || !store.getName().equals(NAME)) { + store.createFeatureType(NAME, getSiteType()); + } + + final Hints hints = new Hints(); + hints.put(Hints.FILTER_FACTORY, FilterFactory2.class); + ff = (FilterFactory2) FactoryFinder.getFilterFactory(hints); + + cache = new Cache(4, 0, false); + } + + /** + * + * @return Cached data, or null if cache has not been initialized yet. + */ + private synchronized ObservableList getCachedNames() { + return allNames == null ? null : allNames.get(); + } + + /** + * Create a new filter which keeps only sites whose name is equal to given one. + * @param nameValue The name to filter against. + * @return A new filter. + */ + private Filter newNameFilter(final String nameValue) { + return ff.equals(ff.property(RhomeoCore.FT_PROPERTIES.NAME.name()), ff.literal(nameValue)); + } + + /** + * + * @param name The name to filter against. + * @return A collection of sites whose names are equal to given one. + */ + private FeatureCollection filteredByName(final String name) { + ArgumentChecks.ensureNonEmpty("Name to search", name.trim()); + return store.createSession(false).getFeatureCollection( + QueryBuilder.filtered(this.NAME, newNameFilter(name)) + ); + } + + /** + * + * @param siteName A name to find sites for. + * @return True if at least one site with the same name already exists. + */ + private synchronized boolean exists(final String siteName) { + ArgumentChecks.ensureNonEmpty("Name to search", siteName.trim()); + // Do not use cache here, because if an existing site name has been modified, + // it would appear in cache. + return !filteredByName(siteName).isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized Site findOne(final String name) { + if (name == null || name.isEmpty()) + return null; + + try { + return cache.getOrCreate(name, () -> { + final FeatureCollection col = filteredByName(name); + if (col.isEmpty()) { + return null; + } + + try (final FeatureIterator it = col.iterator()) { + return toSite(it.next()); + } + }); + } catch (Exception e) { + throw new RhomeoRuntimeException("Cannot load site named ".concat(name), e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void create(final Site site) throws DuplicatedKeyException { + ArgumentChecks.ensureNonNull("Site", site); + + if (exists(site.getName())) { + throw new DuplicatedKeyException(); + } + + try { + final Feature toAdd = toFeature(site); + store.addFeatures(NAME, Collections.singleton(toAdd)); + } catch (DataStoreException e) { + throw new RhomeoRuntimeException(e); + } + + ObservableList cached = getCachedNames(); + if (cached != null) { + cached.add(site.getName()); + Collections.sort(cached); + } + + cache.put(site.getName(), site); + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized ObservableList findAll() { + throw new UnsupportedOperationException("Cannot load full site list in memory !"); + } + + public synchronized ObservableList findNames() { + ObservableList result = getCachedNames(); + if (result == null) { + final List sites = new ArrayList<>(); + final Query query; + try { + final QueryBuilder builder = new QueryBuilder(store.getName()); + builder.setProperties(new String[]{RhomeoCore.FT_PROPERTIES.NAME.name()}); + query = builder.buildQuery(); + try (final FeatureReader reader = store.getFeatureReader(query)) { + while (reader.hasNext()) { + sites.add(reader.next().getPropertyValue(RhomeoCore.FT_PROPERTIES.NAME.name()).toString()); + } + } + } catch (DataStoreException ex) { + throw new RhomeoRuntimeException(ex); + } + Collections.sort(sites); + result = FXCollections.observableList(sites); + allNames = new WeakReference<>(result); + } + + return FXCollections.unmodifiableObservableList(result); + } + + /** + * Update the first encountered feature matching given filter. + * + * @param filter + * @param site + * @throws DataStoreException + */ + private synchronized void updateFeature(final Filter filter, final Site site) throws DataStoreException { + // There is a bug in the shp method getFeatureWriter(name, filter), it never uses the filter parameter! + // Consequently we have to test each filter afterwards, via filter.evaluate(feature) + boolean found = false; + try(final FeatureWriter writer = store.getFeatureWriter(NAME, Filter.INCLUDE)) { + while (writer.hasNext()) { + final Feature f = writer.next(); + if (filter.evaluate(f)) { + toFeature(f, site); + writer.write(); + found = true; + } + } + } + + if (!found) { + // If we've got until here, no element has been found for given filter. + throw new IllegalArgumentException("Cannot update unexisting site !"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void update(final Site site) { + try { + update(site, null); + } catch (DuplicatedKeyException ex) { + throw new RhomeoRuntimeException(ex); // should not happen, as key is not modified. + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void update(final Site site, String oldName) throws DuplicatedKeyException { + ArgumentChecks.ensureNonNull("Site", site); + + // Defines if we need to sort the sites list in cache or not. + // If user updated the site name, then it is required to ensure keeping this list sorted. + boolean needsToSort = false; + if (oldName == null || oldName.trim().isEmpty() || oldName.equals(site.getName())) { + oldName = site.getName(); + } else { + // Here we want to rename the current site name, and its position in the list might be impacted. + needsToSort = true; + } + + ArgumentChecks.ensureNonNull("Name to update", oldName); + if (!oldName.equals(site.getName()) && exists(site.getName())) { + throw new DuplicatedKeyException("Cannot change site name because another one already has the same name !"); + } + + // Persist change + try { + // Ok not already used key, we can update the matching feature + updateFeature(newNameFilter(oldName), site); + } catch (Exception e) { + throw new RhomeoRuntimeException(e); + } + + // update caches + if (needsToSort) { + final ObservableList cached = getCachedNames(); + if (cached != null) + cached.set(cached.indexOf(oldName), site.getName()); + Collections.sort(cached); + } + + if (needsToSort) { + cache.remove(oldName); + cache.put(site.getName(), site); + } else { + cache.replace(oldName, site); + } + } + + /** + * {@inheritDoc} + */ + @Override + public synchronized void delete(final String name) { + ArgumentChecks.ensureNonEmpty("Site name", name.trim()); + // There is a bug in the shp method getFeatureWriter(name, filter), it never uses the filter parameter! + // Consequently we have to test each filter afterwards, via filter.evaluate(feature) + final Filter filter = newNameFilter(name); + try (final FeatureWriter writer = store.getFeatureWriter(this.NAME, Filter.INCLUDE)) { + while (writer.hasNext()) { + final Feature f = writer.next(); + if (filter.evaluate(f)) { + writer.remove(); + } + } + } catch (DataStoreException e) { + throw new RhomeoRuntimeException(e); + } + + ObservableList cached = getCachedNames(); + if (cached != null) { + cached.removeAll(Collections.singleton(name)); + } + + cache.remove(name); + } + + /** + * {@inheritDoc} + */ + @PreDestroy + @Override + public synchronized void close() throws Exception { + allNames = null; + store.close(); + } + + public static final FeatureType getSiteType() { + if (SITE_TYPE == null) { + final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); + ftb.setName(NAME); + try { + ftb.add(RhomeoCore.FT_PROPERTIES.the_geom.name(), MultiPolygon.class, RhomeoCore.getSiteCRS()); + } catch (FactoryException ex) { + throw new RhomeoRuntimeException(ex); + } + ftb.add(RhomeoCore.FT_PROPERTIES.NAME.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.REMARKS.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.COUNTY.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.REFERENT.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.ORG.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.TYPE.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.ODONATE.name(), String.class); + ftb.add(RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name(), String.class); + ftb.setDefaultGeometry(RhomeoCore.FT_PROPERTIES.the_geom.name()); + SITE_TYPE = ftb.buildFeatureType(); + } + + return SITE_TYPE; + } + + /** + * Converts a {@link Site} into a {@link Feature}. + * + * @param site Site to convert. + * @return A new feature matching this site, never {@code null} + * @throws DataStoreException + */ + public static Feature toFeature(final Site site) throws DataStoreException { + final FeatureBuilder sfb = new FeatureBuilder(getSiteType()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.the_geom.name(), site.getGeometry()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.COUNTY.name(), site.getCountyCode()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.NAME.name(), site.getName()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.ODONATE.name(), site.getOdonateType()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name(), site.getOrthoptereType()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.ORG.name(), site.getOrganization()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.REFERENT.name(), site.getReferent()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.REMARKS.name(), site.getRemarks()); + sfb.setPropertyValue(RhomeoCore.FT_PROPERTIES.TYPE.name(), site.getZoneType()); + return sfb.buildFeature(site.getName()); + } + + /** + * Apply site values into an existing feature. + * + * @param feature existing feature to update + * @param site new values to extract + * @throws DataStoreException + */ + public static void toFeature(final Feature feature, final Site site) throws DataStoreException { + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.the_geom.name(), site.getGeometry()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.COUNTY.name(), site.getCountyCode()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.NAME.name(), site.getName()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.ODONATE.name(), site.getOdonateType()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name(), site.getOrthoptereType()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.ORG.name(), site.getOrganization()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.REFERENT.name(), site.getReferent()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.REMARKS.name(), site.getRemarks()); + feature.setPropertyValue(RhomeoCore.FT_PROPERTIES.TYPE.name(), site.getZoneType()); + } + + /** + * Converts a {@link Feature} into a {@link Site}. + * + * @param feature Feature to convert into a site. + * @return A new instance of site, never {@code null} + */ + public static Site toSite(final Feature feature) { + final SiteImpl newSite = new SiteImpl((MultiPolygon)feature.getProperty(RhomeoCore.FT_PROPERTIES.the_geom.name()).getValue()); + newSite.setCountyCode(feature.getProperty(RhomeoCore.FT_PROPERTIES.COUNTY.name()).getValue().toString()); + newSite.setName(feature.getProperty(RhomeoCore.FT_PROPERTIES.NAME.name()).getValue().toString()); + newSite.setOdonateType(feature.getProperty(RhomeoCore.FT_PROPERTIES.ODONATE.name()).getValue().toString()); + newSite.setOrthoptereType(feature.getProperty(RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name()).getValue().toString()); + newSite.setOrganization(feature.getProperty(RhomeoCore.FT_PROPERTIES.ORG.name()).getValue().toString()); + newSite.setReferent(feature.getProperty(RhomeoCore.FT_PROPERTIES.REFERENT.name()).getValue().toString()); + newSite.setRemarks(feature.getProperty(RhomeoCore.FT_PROPERTIES.REMARKS.name()).getValue().toString()); + newSite.setZoneType(feature.getProperty(RhomeoCore.FT_PROPERTIES.TYPE.name()).getValue().toString()); + return newSite; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/list/CodeValue.java b/core/src/main/java/fr/cenra/rhomeo/core/list/CodeValue.java new file mode 100644 index 0000000..bed82ad --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/list/CodeValue.java @@ -0,0 +1,49 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.list; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public interface CodeValue { + + Object getCode(); + Object getValue(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/list/HumidZoneType.java b/core/src/main/java/fr/cenra/rhomeo/core/list/HumidZoneType.java new file mode 100644 index 0000000..17d07bd --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/list/HumidZoneType.java @@ -0,0 +1,103 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.list; + +/** + * @author Cédric Briançon (Geomatys) + */ +public enum HumidZoneType implements CodeValue { + + GRANDS_ESTUAIRES ("1", "Grands estuaires"), // Indicateurs : 1, 2, 3, 5, 6, 8, 12, 13 + BAIES_ESTUAIRES ("2", "Baies et estuaires moyens plats"), // Indicateurs : 1, 2, 3, 5, 6, 8, 12, 13 + MARAIS_LAGUNES ("3.1", "Marais et lagunes côtiers - lagunes"), // Indicateurs : 1, 2, 3, 5, 6, 8, 9, 10, 11, 12, 13 + MARAIS_LAGUNES_PERI_LAGUN ("3.2", "Marais et lagunes côtiers - péri-lagunaire"), // Indicateurs : 1, 2, 3, 5, 6, 8, 9, 10, 11, 12, 13 + MARAIS_LAGUNES_PERI_LAGUN_EAU("3.3", "Marais et lagunes côtiers - péri-lagunaire avec rapport d'eau"), // Indicateurs : 1, 2, 3, 5, 6, 8, 9, 10, 11, 12, 13 + MARAIS_SAUMATRES ("4", "Marais saumâtres aménagés"), // Indicateurs : 1, 2, 3, 6, 8, 9, 10, 11, 12, 13 + BORDURES_COURS_EAU ("5", "Bordures de cours d'eau"), // Indicateurs : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 + PLAINES_ALLUVIALES ("6", "Plaines alluviales"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_BAS_FOND_ALTITUDE ("7.1", "Zones humides de bas-fonds en tête de BV - altitude"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13 + ZH_BAS_FOND_TOURB_ACIDE ("7.2", "Zones humides de bas-fonds en tête de BV - tourbière acide"), // Indicateurs : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_BAS_FOND_TOURB_ALCALINE ("7.3", "Zones humides de bas-fonds en tête de BV - tourbière alcaline"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_BAS_FOND_PENTES ("7.4", "Zones humides de bas-fonds en tête de BV - pentes et sources"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_BAS_FOND_COMBES ("7.5", "Zones humides de bas-fonds en tête de BV - combes et bordure de ruisseau"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + REGIONS_ETANGS ("8", "Régions d'étangs"), // Indicateurs : 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 + BORDURES_PLAN_EAU_ACIDE ("9.1", "Bordures de plan d'eau (lac) - ZH acide"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + BORDURES_PLAN_EAU_ALCALINE ("9.2", "Bordures de plan d'eau (lac) - ZH alcaline"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + MARAIS_LANDES_TOURB ("10.1", "Marais et landes humides de plaine - tourbière de plaine"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + MARAIS_LANDES_PRAIRIES ("10.2", "Marais et landes humides de plaine - prairies humides"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + MARAIS_LANDES_PRES ("10.3", "Marais et landes humides de plaine - Prés salés continentaux"), // Indicateurs : 1, 2, 3, 6, 8, 9, 10, 11, 12, 13 + ZH_PONCT_MARE_SAUMATRE ("11.11", "Zones humides ponctuelles - Mare temporaire saumâtre"), // Indicateurs : 1, 2, 3, 6, 8, 9, 10, 11, 12, 13 + ZH_PONCT_MARE_ALCALINE ("11.12", "Zones humides ponctuelles - Mare temporaire alcaline"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_PONCT_MARE_ACIDE ("11.13", "Zones humides ponctuelles - Mare temporaire acide"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_PONCT_MARE_PERMANENTE ("11.2", "Zones humides ponctuelles - Mare permanente"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + MARAIS_AMENAGES_AGRICOLE ("12", "Marais aménagés dans un but agricole"), // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + ZH_ARTIFICELLES ("13", "Zones humides artificielles"); // Indicateurs : 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13 + + private final String code; + private final String value; + + HumidZoneType(final String code, final String value) { + this.code = code; + this.value = value; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getValue() { + return value; + } + + public static HumidZoneType getByCode(final String code) { + for (final HumidZoneType candid : values()) { + if (candid.getCode().equals(code)) { + return candid; + } + } + return null; + } + + @Override + public String toString() { + return code +" "+ value; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/list/OdonateZoneBio.java b/core/src/main/java/fr/cenra/rhomeo/core/list/OdonateZoneBio.java new file mode 100644 index 0000000..61c34cf --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/list/OdonateZoneBio.java @@ -0,0 +1,130 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.list; + +import fr.cenra.rhomeo.core.data.reference.OdonataHabitatReference; + +/** + * L_zone_bio_odonate. + * + * id_zone_bio_odo nom_zone_bio_odo + * ------------------------------------- + * 1 Alpin + * 2 Continental + * 3 Méditerranéen occidental + * 4 Méditerranéen oriental + * 5 Pyrénéen + * + * (Source : Champs_et_Listes.xlsx) + * + * We add the column "zbio" to join {@link OdonataHabitatReference} and a new + * entry to represent 'zglobal' value. + * + * select distinct zbio, biogeo from odo_dependance_habitat; + * + * zbio | biogeo + *--------------------------+-------------------------- + * mediterraneen oriental | Méditerranéen oriental + * pyreneen | pyreneen + * continental | Continental + * alpin | Alpin + * mediterraneen occidental | Méditerranéen occidental + * zglobal | Z Global + * + * (Source : RhoMéO_Geomatys.backup) + * + * @author Cédric Briançon (Geomatys) + * @author Samuel Andrés (Geomatys) + */ +public enum OdonateZoneBio implements CodeValue { + ALPIN ("1", "Alpin", "alpin"), + CONTINENTAL ("2", "Continental", "continental"), + MED_OCCIDENTAL("3", "Méditerranéen occidental", "mediterraneen occidental"), + MED_ORIENTAL ("4", "Méditerranéen oriental", "mediterraneen oriental"), + PYRENEEN ("5", "Pyrénéen", "pyreneen"), + ZGLOBAL ("", "Z Global", "zglobal"); + + private final String code; + private final String value; + private final String zbio; + + + OdonateZoneBio(final String code, final String value, final String zbio) { + this.code = code; + this.value = value; + this.zbio = zbio; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getValue() { + return value; + } + + public String getZbio() { + return zbio; + } + + public static OdonateZoneBio getByCode(final String code) { + for (final OdonateZoneBio candid : values()) { + if (candid.getCode().equals(code)) { + return candid; + } + } + return null; + } + + public static OdonateZoneBio getByZbio(final String code) { + for (final OdonateZoneBio candid : values()) { + if (candid.getZbio().equals(code)) { + return candid; + } + } + return null; + } + + @Override + public String toString() { + return code +" "+ value; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/list/OrthoptereZoneBio.java b/core/src/main/java/fr/cenra/rhomeo/core/list/OrthoptereZoneBio.java new file mode 100644 index 0000000..621b938 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/list/OrthoptereZoneBio.java @@ -0,0 +1,97 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.list; + +/** + * L_zone_bio_orthoptere. + * + * id_zone_bio_ortho nom_zone_bio_ortho + * --------------------------------------- + * 1 Alpin_marais_alluvial + * 2 Alpin_tourbière + * 3 Mediterranen1_marais_alluvial + * 4 Mediterranen1_marais_littoral + * 5 Mediterranen2_tourbière + * 6 Mediterranen2_marais_alluvial + * + * (Source : Champs_et_Listes.xlsx) + * + * @author Cédric Briançon (Geomatys) + */ +public enum OrthoptereZoneBio implements CodeValue { + + ALPIN_MARAIS_ALLUVIAL("1", "Alpin_marais_alluvial"), + ALPIN_TOURBIERE ("2", "Alpin_tourbière"), + MED1_MARAIS_ALLUVIAL ("3", "Mediterranen1_marais_alluvial"), + MED1_MARAIS_LITTORAL ("4", "Mediterranen1_marais_littoral"), + MED2_TOURBIERE ("5", "Mediterranen2_tourbière"), + MED2_MARAIS_ALLUVIAL ("6", "Mediterranen2_marais_alluvial"); + + private final String code; + private final String value; + + OrthoptereZoneBio(final String code, final String value) { + this.code = code; + this.value = value; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getValue() { + return value; + } + + public static OrthoptereZoneBio getByCode(final String code) { + for (final OrthoptereZoneBio candid : values()) { + if (candid.getCode().equals(code)) { + return candid; + } + } + return null; + } + + @Override + public String toString() { + return code +" "+ value; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/list/VonPost.java b/core/src/main/java/fr/cenra/rhomeo/core/list/VonPost.java new file mode 100644 index 0000000..15b9b3b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/list/VonPost.java @@ -0,0 +1,76 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.list; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public enum VonPost implements CodeValue { + H1 ("Eau limpide"), + H2 ("eau peu colorée"), + H3 ("eau trouble pâle"), + H4 ("eau trouble foncée"), + H5 ("eau trouble et particules"), + H6 ("1/3 du matériel passe entre les doigts"), + H7 ("1/2 du matériel passe entre les doigts"), + H8 ("2/3 du matériel passe entre les doigts"), + H9 ("Presque tout le matériel"), + H10("Tout le matériel"); + + private final String value; + private VonPost(final String value) { + this.value = value; + } + + @Override + public String getCode() { + return name(); + } + + @Override + public String getValue() { + return value; + } + + @Override + public String toString() { + return new StringBuilder(name()).append(" : ").append(value).toString(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPAccess.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPAccess.java new file mode 100644 index 0000000..a75fba3 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPAccess.java @@ -0,0 +1,206 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import fr.cenra.rhomeo.core.util.EncryptionResult; +import fr.cenra.rhomeo.core.util.SecretGenerator; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.prefs.Preferences; +import javax.validation.constraints.NotNull; +import org.apache.commons.net.ftp.FTPClient; + +/** + * Holds connection information over a single FTP service. The service is defined + * by its host name, but also by a login, password and optionally a root directory. + * + * NOTE : If, for some reason, security context cannot initialize, passwords become + * unavailable. + * + * @author Alexis Manin (Geomatys) + */ +public class FTPAccess { + + private static enum PROPERTIES { + ADRESS, + PORT, + LOGIN, + PASSWORD, + WORKING_DIR + } + + private final Preferences node; + + /** + * Encryption manager. Can be null if an error occurred at initialisation. In + * this case, no password will be read or written. + */ + private final SecretGenerator cipher; + + protected FTPAccess(@NotNull final Preferences infoContainer) { + this.node = infoContainer; + cipher = SecretGenerator.getInstance().orElse(null); + } + + private String getProperty(final PROPERTIES p) { + return node.get(p.name(), null); + } + + private void setProperty(final PROPERTIES p, final String value) { + if (value == null || value.trim().isEmpty()) { + node.remove(p.name()); + } else { + node.put(p.name(), value); + } + } + + public String getAdress() { + return getProperty(PROPERTIES.ADRESS); + } + + public void setAdress(final String newAdress) { + setProperty(PROPERTIES.ADRESS, newAdress); + } + + public int getPort() { + return node.getInt(PROPERTIES.PORT.name(), 21); + } + + public void setPort(final int port) { + node.putInt(PROPERTIES.PORT.name(), port); + } + + public String getLogin() { + return getProperty(PROPERTIES.LOGIN); + } + + public void setLogin(final String newLogin) { + setProperty(PROPERTIES.LOGIN, newLogin); + } + + + public String getWorkingDir() { + return getProperty(PROPERTIES.WORKING_DIR); + } + + public void setWorkingDir(final String newDir) { + setProperty(PROPERTIES.WORKING_DIR, newDir); + } + + public synchronized String getPassword() throws GeneralSecurityException { + final String tmpValue = getProperty(PROPERTIES.PASSWORD); + if (tmpValue == null || tmpValue.isEmpty()) { // No pw stored. + return ""; + } + + if (cipher == null) { + throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences."); + } + + byte[] sk = node.getByteArray("ftp_access_skey", null); + byte[] sr = node.getByteArray("ftp_access_sran", null); + if (sk == null || sr == null) { + throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences."); + } + + return cipher.decrypt(tmpValue, sk, sr); + } + + public synchronized void setPassword(final String newPassword) throws GeneralSecurityException { + if (cipher == null) { + throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences."); + } + + // Empty value, no encryption needed. + if (newPassword == null || newPassword.isEmpty()) { + setProperty(PROPERTIES.PASSWORD, ""); + return; + } + + final EncryptionResult encrypted = cipher.encrypt(newPassword); + setProperty(PROPERTIES.PASSWORD, encrypted.output); + + node.putByteArray("ftp_access_skey", encrypted.getKey()); + node.putByteArray("ftp_access_sran", encrypted.getSecureRandom()); + } + + /** + * Create a new connection over the FTP service provided by holded information. + * The provided {@link FTPClient} must be closed using {@link FTPClient#disconnect() } + * once you've finished your work. + * + * @return A client to exchange data with FTP service which contains + * reference files. + * + * @throws IOException If we cannot connect to distant service. + * @throws java.security.GeneralSecurityException If an authentication is + * required, but we cannot access to password. + */ + public FTPClient createClient() throws IOException, GeneralSecurityException { + return createClient(getAdress(), getPort(), getWorkingDir(), getLogin(), getPassword()); + } + + public static FTPClient createClient(final String adress, final int port, final String workDir, final String login, final String password) throws IOException, GeneralSecurityException { + if (adress == null) { + throw new IllegalStateException("No adress configured for FTP access !"); + } + + final FTPClient client = new FTPClient(); + client.enterLocalActiveMode(); + if (port < 1) { + client.connect(adress); + } else { + client.connect(adress, port); + } + + if (login != null) { + if (!client.login(login, password)) { + throw new GeneralSecurityException("Unable to authenticate with the login "+ login); + } + } + + if (workDir != null) { + if (!client.changeWorkingDirectory(workDir)) { + throw new IOException("Unable to change of working directory to:"+ workDir); + } + } + + return client; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferences.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferences.java new file mode 100644 index 0000000..bb2d85b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferences.java @@ -0,0 +1,75 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; + +import javax.validation.constraints.NotNull; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public interface FTPPreferences extends PreferenceGroup { + + public static enum ACCESS_POINTS { + REFERENCES("Références"), + RESULTS("Résultats"); + + private final String title; + + ACCESS_POINTS(final String value) { + this.title = value; + } + + public String getTitle() { + return title; + } + } + + /** + * Give all information about a specific FTP end point. + * + * @param ap The FTP service to retrieve. + * + * @return an object capable to open a connection over the queried FTP service. + */ + @NotNull + FTPAccess getAccess(@NotNull final ACCESS_POINTS ap); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.java new file mode 100644 index 0000000..bfa138a --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.java @@ -0,0 +1,188 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.preferences.net.NetPreferences; +import fr.cenra.rhomeo.core.util.SecretGenerator; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import javax.annotation.PostConstruct; +import javax.validation.constraints.NotNull; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * Holds preferences over FTP connections. You can retrieve information about a + * specific FTP connection by using {@link #getAccess(fr.cenra.rhomeo.core.preferences.ftp.FTPPreferences.ACCESS_POINTS) + * }. + * + * Known connections are listed in {@link ACCESS_POINTS}. + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Qualifier(FTPPreferencesImpl.NAME) +@Validated +public class FTPPreferencesImpl implements FTPPreferences { + + public static final String NAME = "FTPPreferencesImpl"; + + private final Preferences root; + + public FTPPreferencesImpl() { + root = Preferences.userNodeForPackage(FTPPreferencesImpl.class); + } + + /** + * DO NOT USE IT ! + * + * @param key DO NOT USE IT ! + * @return DO NOT USE IT ! + */ + @Override + public String getPreference(Object key) { + throw new UnsupportedOperationException("No value stored on root."); + } + + /** + * DO NOT USE IT ! + * + * @param key DO NOT USE IT ! + * @param value DO NOT USE IT ! + */ + @Override + public void setPreference(Object key, String value) { + throw new UnsupportedOperationException("No value stored on root."); + } + + @Override + public List getKeys() { + return Collections.EMPTY_LIST; + } + + /** + * Give all information about a specific FTP end point. + * + * @param ap The FTP service to retrieve. + * + * @return an object capable to open a connection over the queried FTP + * service. + */ + @NotNull + @Override + public FTPAccess getAccess(@NotNull final ACCESS_POINTS ap) { + ArgumentChecks.ensureNonNull("Input access point", ap); + return new FTPAccess(root.node(ap.name())); + } + + @Override + public int getPriority() { + return 0; + } + + /** + * If no FTP account has been parameterised, a default one is set for result + * publication and reference data. + */ + @PostConstruct + private void checkDefaultAccesses() { + FTPAccess results = getAccess(FTPPreferences.ACCESS_POINTS.RESULTS); + if (setDefaultAccess(results)) { + results.setWorkingDir("results"); + } + + FTPAccess references = getAccess(FTPPreferences.ACCESS_POINTS.REFERENCES); + if (setDefaultAccess(references)) { + references.setWorkingDir("references"); + } + } + + private boolean setDefaultAccess(final FTPAccess access) { + if ((access.getAdress() == null || access.getAdress().trim().isEmpty()) + && (access.getLogin() == null || access.getLogin().trim().isEmpty()) + && (access.getWorkingDir() == null || access.getWorkingDir().trim().isEmpty())) { + + access.setAdress("constellation.cenra-outils.org"); + access.setLogin("rhomeoftp"); + final SecretGenerator gen = SecretGenerator.getInstance().orElse(null); + if (gen == null) + return false; + + try { + access.setPassword(gen.decrypt("4Iru5sD2uzixbWHhu7R72s658w/l3yCz", getDefaultKey(), getDefaultSR())); + return true; + } catch (IOException | GeneralSecurityException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, null, ex); + } + } + return false; + } + + private byte[] getDefaultKey() throws IOException { + final byte[] buf = new byte[16]; + int read = 0; + try (final InputStream stream = NetPreferences.class.getResourceAsStream("defaultKey")) { + do { + read += stream.read(buf, read, buf.length - read); + } while (read < buf.length - 1); + } + + return buf; + } + + private byte[] getDefaultSR() throws IOException { + final byte[] buf = new byte[8]; + int read = 0; + try (final InputStream stream = NetPreferences.class.getResourceAsStream("defaultSR")) { + do { + read += stream.read(buf, read, 8 - read); + } while (read < 7); + } + + return buf; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetKey.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetKey.java new file mode 100644 index 0000000..c8df1d6 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetKey.java @@ -0,0 +1,77 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.net; + +import fr.cenra.rhomeo.api.preferences.InternationalPreferenceKey; +import org.apache.sis.util.ArgumentChecks; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class NetKey implements InternationalPreferenceKey { + + private final String keyId; + private final String defaultValue; + + public NetKey(final String netKey) { + this(netKey, null); + } + + public NetKey(final String netKey, final String defaultValue) { + ArgumentChecks.ensureNonNull("Key", netKey); + this.keyId = netKey; + this.defaultValue = defaultValue; + } + + @Override + public String name() { + return keyId; + } + + @Override + public String getKey() { + return keyId; + } + + @Override + public String getDefaultValue() { + return defaultValue; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetPreferences.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetPreferences.java new file mode 100644 index 0000000..5475368 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/NetPreferences.java @@ -0,0 +1,172 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.net; + +import fr.cenra.rhomeo.api.preferences.UserPackagePreferenceGroup; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.util.EncryptionResult; +import fr.cenra.rhomeo.core.util.SecretGenerator; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class NetPreferences extends UserPackagePreferenceGroup { + + public static final NetKey UPDATE_URL = new NetKey("update_url", "http://updates-bao.cenra-outils.org/updates/update.json"); + public static final NetKey UPDATE_USER = new NetKey("update_user", "rhomeo"); + public static final NetKey UPDATE_PASSWORD = new PassNetKey("update_pass", null); + public static final NetKey WFS_URL = new NetKey("wfs_url", "http://constellation.cenra-outils.org/WS/wfs/referential"); + + private static final List KEYS = Collections.unmodifiableList(Arrays.asList(new NetKey[] { + UPDATE_URL, UPDATE_USER, UPDATE_PASSWORD, WFS_URL + })); + + private static final String UPDATE_KEY_PREF = "net_update_pass_key"; + private static final String UPDATE_SR_PREF = "net_update_pass_sr"; + + private final SecretGenerator gen; + + public NetPreferences() { + gen = SecretGenerator.getInstance().orElse(null); + // Default values for password and secret key. + if (getPreference(UPDATE_PASSWORD) == null) { + prefs.put(UPDATE_PASSWORD.getKey(), "4Iru5sD2uzixbWHhu7R72s658w/l3yCz"); + try { + final byte[] buf = new byte[16]; + int read = 0; + try (final InputStream stream = NetPreferences.class.getResourceAsStream("defaultKey")) { + do { + read += stream.read(buf, read, buf.length - read); + } while (read < buf.length -1); + prefs.putByteArray(UPDATE_KEY_PREF, buf); + } + read = 0; + try (final InputStream stream = NetPreferences.class.getResourceAsStream("defaultSR")) { + do { + read += stream.read(buf, read, 8 - read); + } while (read < 7); + prefs.putByteArray(UPDATE_SR_PREF, Arrays.copyOf(buf, 8)); + } + } catch (IOException ex) { + RhomeoCore.LOGGER.log(Level.FINE, "cannot set default values", ex); + } + + } + } + + @Override + public List getKeys() { + return KEYS; + } + + @Override + public void setPreference(NetKey key, String value) { + if (UPDATE_PASSWORD.equals(key) && value != null && !(value = value.trim()).isEmpty()) { + if (gen != null) { + try { + EncryptionResult encrypted = gen.encrypt(value); + prefs.putByteArray(UPDATE_KEY_PREF, encrypted.getKey()); + prefs.putByteArray(UPDATE_SR_PREF, encrypted.getSecureRandom()); + value = encrypted.getOutput(); + } catch (GeneralSecurityException ex) { + throw new RhomeoRuntimeException(ex); + } + } else { + throw new RhomeoRuntimeException("no security manager available. Passwords cannot be written into preferences."); + } + } + super.setPreference(key, value); + } + + @Override + public int getPriority() { + return 1; + } + + public URL getUpdateURL() throws MalformedURLException { + return new URL(getPreference(UPDATE_URL)); + } + + public URLConnection openConnection(final URL toReach) throws GeneralSecurityException, IOException { + final URL updateURL = getUpdateURL(); + String user = getPreference(UPDATE_USER); + if (toReach.getHost().equals(updateURL.getHost()) && toReach.getUserInfo() == null && user != null && !(user = user.trim()).isEmpty()) { + final String pass = getPreference(UPDATE_PASSWORD); + URLConnection con = toReach.openConnection(); + String dpw = null; + if (pass !=null && !pass.isEmpty()) { + final byte[] key = prefs.getByteArray(UPDATE_KEY_PREF, null); + final byte[] sr = prefs.getByteArray(UPDATE_SR_PREF, null); + if (key == null && sr == null) { // No encryption parameter + dpw = pass; + } else if (gen == null) { // No cipher + throw new RhomeoRuntimeException("no security manager available. Passwords cannot be read from preferences."); + } else { + dpw = gen.decrypt(pass, key, sr); + } + } + final StringBuilder authInfo = new StringBuilder(user); + if (dpw != null) { + authInfo.append(':').append(dpw); + } + final String b64 = Base64.getEncoder().withoutPadding().encodeToString( + authInfo.toString().getBytes() + ); + con.addRequestProperty("Authorization", "Basic ".concat(b64)); + return con; + } else { + return toReach.openConnection(); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/PassNetKey.java b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/PassNetKey.java new file mode 100644 index 0000000..6fcb849 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/preferences/net/PassNetKey.java @@ -0,0 +1,56 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.net; + +import fr.cenra.rhomeo.api.preferences.PasswordKey; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class PassNetKey extends NetKey implements PasswordKey { + + public PassNetKey(String netKey) { + super(netKey); + } + + public PassNetKey(String netKey, String defaultValue) { + super(netKey, defaultValue); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/result/ResultStorage.java b/core/src/main/java/fr/cenra/rhomeo/core/result/ResultStorage.java new file mode 100644 index 0000000..2cfee3b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/result/ResultStorage.java @@ -0,0 +1,341 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import fr.cenra.rhomeo.core.data.site.SiteRepository; +import fr.cenra.rhomeo.core.util.SerializableDataContext; +import java.beans.IntrospectionException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.logging.Level; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.util.collection.IntegerList; +import org.geotoolkit.nio.IOUtilities; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Component in charge of storing indicator results locally. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class ResultStorage { + + @Autowired + private ResultWriter writer; + + @Autowired + private Session session; + + @Autowired + private SiteRepository siteRepo; + + @Autowired + private ResultWriter resultWriter; + + final String[] expectedFiles = new String[]{ExportUtils.METADATA_JSON, ResultWriter.RESULT_FILE_NAME, ResultWriter.ADDITIONAL_RESULT_FILE_NAME}; + + private ResultStorage() { + Arrays.sort(expectedFiles); + } + + public void store() throws IOException, IntrospectionException { + if (session.getResults() == null || session.getResults().isEmpty() || session.getDataContext() == null) + return; + + final String sha1 = GeometryUtils.getSha1(session.getDataContext().getSite().getGeometry()); + final Path root = RhomeoCore.RESULT_PATH.resolve(sha1); + Files.createDirectories(root); + + /* Sort results by indicator and year, as publication / export via dashboard + * has to be able to export results only for a specific indicator at a + * specific year. We iterate on indices and not on the tracking points + * related to process context, because some protocols appear to use no + * tracking point at all. + */ + final IntegerList years = new IntegerList(10, Short.MAX_VALUE); + for (final Index i : session.getResults()) { + if (years.occurrence(i.getYear()) < 1) + years.add(i.getYear()); + } + + for (final Indicator indic : session.getProcessContext().getIndicators()) { + final Path indicPath = root.resolve(indic.getName()); + Files.createDirectories(indicPath); + for (final int year : years) { + final Path yearPath = indicPath.resolve(Integer.toString(year)); + IOUtilities.deleteRecursively(yearPath); + Files.createDirectory(yearPath); + writer.prepareWriting(yearPath) + .writeMetadataJSON() + .writeResults((i) -> i.getYear() == year && i.getSpi().getIndicator().equals(indic)); + } + } + } + + /** + * Notify the result storage manager that a site had its name changed. + * @param oldSiteName + * @param newSiteName + * @throws IOException + */ +// public void siteNameChanged(final String oldSiteName, final String newSiteName) throws IOException { +// final Base64.Encoder encoder = Base64.getEncoder().withoutPadding(); +// String oldName = encoder.encodeToString(oldSiteName.getBytes(StandardCharsets.UTF_8)); +// final Path oldPath = RhomeoCore.RESULT_PATH.resolve(oldName); +// if (!Files.exists(oldPath)) +// return; // Nothing to move +// +// final String newName = encoder.encodeToString(newSiteName.getBytes(StandardCharsets.UTF_8)); +// Files.move(oldPath, RhomeoCore.RESULT_PATH.resolve(newName)); +// } + + /** + * Remove results associated to the input site, If any. Otherwise, do nothing. + * @param siteName Name of the site to delete results for. + * + * @throws IOException If an error occurs while deleting files. + */ + public void deleteIfExists(String siteName) throws IOException { + if (siteName == null || (siteName = siteName.trim()).isEmpty()) + return; + deleteIfExists(siteRepo.findOne(siteName)); + } + + /** + * Remove results associated to the input site, If any. Otherwise, do nothing. + * @param site The site to delete results for. + * + * @throws IOException If an error occurs while deleting files. + */ + public void deleteIfExists(final Site site) throws IOException { + if (site == null) + return; + final String sha1 = GeometryUtils.getSha1(site.getGeometry()); + IOUtilities.deleteRecursively(RhomeoCore.RESULT_PATH.resolve(sha1)); + } + + /** + * Copy the results associated to the input dashboard item in the FTP pointed + * by given client. + * + * Note : All files are uploaded on the current working directory of the FTP client. + * + * @param target Dashboard item to upload results for. + * @param ftp Connection to the FTP. + * @return true if we succeeded + * @throws IOException + */ + public boolean publish(final DashboardResultItem target, final FTPClient ftp) throws IOException { + Optional directory = getDirectory(target); + if (!directory.isPresent()) + return false; + + final Path root = directory.get(); + try { + Files.list(root).forEach(p -> { + final String fileName = p.getFileName().toString(); + if (Files.isRegularFile(p) && Arrays.binarySearch(expectedFiles, fileName) >= 0) { + + try (final InputStream stream = Files.newInputStream(p)) { + ftp.storeFile(fileName, stream); + } catch (IOException e) { + throw new RhomeoRuntimeException(e); + } + } + }); + } catch (RhomeoRuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; + } + } + return true; + } + + /** + * Publish results currently contained in the session into the current + * working directory of the input client. + * + * @param ftpSupplier Provides an FTP client positioned in the directory we + * want to send data to. The FTP CLient is closed internally. + * + * @throws IOException + * @throws DataStoreException + * @throws IntrospectionException + */ + public void publish(final Supplier ftpSupplier) throws IOException, DataStoreException, IntrospectionException { + Path tmpDir = Files.createTempDirectory("results"); + try { + resultWriter.prepareWriting(tmpDir).writeMetadataJSON().writeResults().writeAdditionalValues(); + + final FTPClient ftp = ftpSupplier.get(); + try { + Files.list(tmpDir).forEach(p -> { + final String fileName = p.getFileName().toString(); + if (Files.isRegularFile(p)) { + + try (final InputStream stream = Files.newInputStream(p)) { + if (!ftp.storeFile(fileName, stream)) + throw new RhomeoRuntimeException("A file cannot be copied via FTP : ".concat(fileName)); + } catch (IOException e) { + throw new RhomeoRuntimeException(e); + } + } + }); + } finally { + ftp.disconnect(); + } + + } catch (RhomeoRuntimeException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } else { + throw e; + } + } finally { + // If the task fails, it does not halt publication process. + try { + IOUtilities.deleteRecursively(tmpDir); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot clear temporary data", e); + } + } + } + + /** + * Return a path to the CSV file containing main indices related to the + * given dashboard item. + * @param item + * @return A path to the requested results, or an empty optional if there's + * no result available, or they're not accessible. + * @throws java.io.IOException If an error occurs while checking result path. + */ + public Optional getMainResults(final DashboardResultItem item) throws IOException { + Optional directory = getDirectory(item); + if (directory.isPresent()) { + + final Path root = directory.get(); + // Read main results + Path results = root.resolve(ResultWriter.RESULT_FILE_NAME); + if (Files.isReadable(results)) { + return Optional.of(results); + } + } + + return Optional.empty(); + } + + /** + * Return a path to the CSV file containing additional indices related to + * the given dashboard item. + * @param item + * @return A path to the requested results, or an empty optional if there's + * no result available, or they're not accessible. + * @throws java.io.IOException If an error occurs while checking result path. + */ + public Optional getAdditionalResults(final DashboardResultItem item) throws IOException { + Optional directory = getDirectory(item); + if (directory.isPresent()) { + + final Path root = directory.get(); + // Read main results + Path results = root.resolve(ResultWriter.ADDITIONAL_RESULT_FILE_NAME); + if (Files.isReadable(results)) { + return Optional.of(results); + } + } + + return Optional.empty(); + } + + public Optional readMetadata(final DashboardResultItem item) throws IOException { + Optional directory = getDirectory(item); + if (directory.isPresent()) { + + final Path root = directory.get(); + // Read main results + Path results = root.resolve(ExportUtils.METADATA_JSON); + if (Files.isReadable(results)) { + try (final BufferedReader reader = Files.newBufferedReader(results, StandardCharsets.UTF_8)) { + return Optional.of(new ObjectMapper().readValue(reader, SerializableDataContext.class)); + } + } + } + + return Optional.empty(); + } + + private Optional getDirectory(final DashboardResultItem target) throws IOException { + if (target.getSite() == null || target.getSite().isEmpty() + || target.getIndicator() == null || target.getIndicator().isEmpty()) + return Optional.empty(); + + final Site site = siteRepo.findOne(target.getSite()); + if (site == null) + return Optional.empty(); + + final String sha1 = GeometryUtils.getSha1(site.getGeometry()); + final Path root = RhomeoCore.RESULT_PATH.resolve(sha1).resolve(target.getIndicator()).resolve(Integer.toString(target.getYear())); + if (!Files.isDirectory(root) || !Files.list(root).findAny().isPresent()) + return Optional.empty(); + + return Optional.of(root); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/result/ResultWriter.java b/core/src/main/java/fr/cenra/rhomeo/core/result/ResultWriter.java new file mode 100644 index 0000000..69bc206 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/result/ResultWriter.java @@ -0,0 +1,281 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.data.Warning; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.AdditionalValue; +import fr.cenra.rhomeo.api.result.AdditionalValueMapper; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.TrackingPointIndex; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.core.util.SerializableDataContext; +import java.beans.IntrospectionException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class ResultWriter { + + static final String ADDITIONAL_RESULT_FILE_NAME = "additionalResults.csv"; + static final String RESULT_FILE_NAME = "results.csv"; + + @Autowired + Session session; + + @Autowired(required = false) + Set avMappers; + + public WritingContext prepareWriting(final Path root) throws IOException { + return new WritingContext(root, session, avMappers); + } + + /** + * An object independent from {@link Session} variations, designed to write + * a snapshot of results stored in session at a precise time (creation of + * an instance of this class). + */ + public static class WritingContext { + final Path root; + final DataContext dataContext; + final Map referenceVersions; + final Set indicators; + final Set trackingPoints; + final List indices; + final Validator validator; + final Dataset ds; + + final Set additional; + final Set mappers; + + private WritingContext(final Path root, final Session session, final Set avMappers) throws IOException { + ArgumentChecks.ensureNonNull("Root path", root); + ArgumentChecks.ensureNonNull("Session", session); + if (!Files.isDirectory(root)) { + Files.createDirectories(root); + } + this.root = root; + + final DataContext dc = session.getDataContext(); + ArgumentChecks.ensureNonNull("Data context", dc); + ArgumentChecks.ensureNonNull("Site", dc.getSite()); + ArgumentChecks.ensureNonNull("Protocol", dc.getProtocol()); + dataContext = dc; + + referenceVersions = new HashMap<>(); + if (dc.getReferences() != null) { + for (final Map.Entry, Version> entry : dc.getReferences().entrySet()) { + for (final ReferenceDescription refDescriptor : dc.getProtocol().getReferenceTypes()) { + if (refDescriptor.getReferenceType().equals(entry.getKey())) + referenceVersions.put(refDescriptor, entry.getValue()); + } + } + } + + indicators = new HashSet<>(session.getProcessContext().getIndicators()); + + trackingPoints = new HashSet<>(session.getProcessContext().getTrackingPoints()); + + indices = new ArrayList<>(session.getResults()); + // #40 : Sort results. + indices.sort(new IndexComparator()); + + validator = session.getBean(Validator.class); + + ds = session.getDataset(); + + mappers = avMappers; + if (mappers == null || mappers.isEmpty()) // If no writer is available, there's no need to keep a reference of additional values. + additional = Collections.EMPTY_SET; + else additional = session.getAdditionalValues(); + } + + public WritingContext writeSite() throws IOException, DataStoreException { + ExportUtils.writeGeoJson(dataContext.getSite(), root); + return this; + } + + public WritingContext writeMetadataJSON() throws IOException { + ExportUtils.writeMetadata(SerializableDataContext.fromDataContext(dataContext), root); + return this; + } + + public WritingContext writeMetadataText() throws IOException { + final Set> datasetErrors = validator.validate(ds); + datasetErrors.removeIf(cv -> cv.getConstraintDescriptor().getPayload().contains(Warning.class)); + + ExportUtils.writeMetadataText(root.resolve("metadata.txt"), dataContext.getSite(), dataContext.getProtocol(), referenceVersions, indicators, dataContext.getUserData(), datasetErrors); + + return this; + } + + /** + * Write all results in CSV files (results.csv for main ones, + * additionalResults.csv for others). + * + * @return This context. + * @throws IOException + * @throws IntrospectionException + */ + public WritingContext writeResults() throws IOException, IntrospectionException { + return writeResults(null); + } + + /** + * Write results accepted by given predicate in CSV files (results.csv + * for main ones, additionalResults.csv for others). + * + * @param filter A predicate to filter results. + * @return This context. + * @throws IOException + * @throws IntrospectionException + */ + public WritingContext writeResults(final Predicate filter) throws IOException, IntrospectionException { + final ArrayList mainResults = new ArrayList(); + final ArrayList additionalResults = new ArrayList(); + + boolean primaryHasLocation = false; + boolean additionalHasLocation = false; + for (final Index i : indices) { + if (filter != null && !filter.test(i)) + continue; + + boolean isPrimary = i.getSpi().isPrimary(); + if (isPrimary) { + mainResults.add(new SimpleLocationIndex(i)); + if (i instanceof TrackingPointIndex) + primaryHasLocation = true; + } else { + additionalResults.add(new SimpleLocationIndex(i)); + if (i instanceof TrackingPointIndex) + additionalHasLocation = true; + } + } + + // Write main results + if (!mainResults.isEmpty()) { + final Path mainResultPath = root.resolve(RESULT_FILE_NAME); + Files.deleteIfExists(mainResultPath); + new CSVEncoder( + mainResultPath, + primaryHasLocation ? SimpleLocationIndex.class : SimpleYearIndex.class, + StandardCharsets.UTF_8 + ).encode(mainResults, true); + } + + // Write additional results. + if (!additionalResults.isEmpty()) { + final Path additionalResultPath = root.resolve(ADDITIONAL_RESULT_FILE_NAME); + Files.deleteIfExists(additionalResultPath); + new CSVEncoder( + additionalResultPath, + additionalHasLocation ? SimpleLocationIndex.class : SimpleYearIndex.class, + StandardCharsets.UTF_8 + ).encode(additionalResults, true); + } + + return this; + } + + public void writeAdditionalValues() throws IOException { + if (mappers == null || mappers.isEmpty()) + return; + for (final AdditionalValueMapper mapper : mappers) + if (mapper.writeValues(root, additional)) + return; + } + } + + /** + * Sort index by type, year, eventually tracking point, and finally by value. + */ + private static class IndexComparator implements Comparator { + + @Override + public int compare(Index o1, Index o2) { + + int comparison = o1.getSpi().compareTo(o2.getSpi()); + if (comparison == 0) { + comparison = o1.getYear() - o2.getYear(); + if (comparison == 0) { + if (o1 instanceof TrackingPointIndex) { + if (o2 instanceof TrackingPointIndex) { + comparison = ((TrackingPointIndex) o1).getPoint().getName().compareTo(((TrackingPointIndex) o2).getPoint().getName()); + } else { + comparison = 1; + } + } else if (o2 instanceof TrackingPointIndex) { + comparison = -1; + } + } + } + + return comparison == 0 ? Double.compare(o1.getValue().doubleValue(), o2.getValue().doubleValue()) : comparison; + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleLocationIndex.java b/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleLocationIndex.java new file mode 100644 index 0000000..e18001f --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleLocationIndex.java @@ -0,0 +1,71 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.TrackingPointIndex; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class SimpleLocationIndex extends SimpleYearIndex { + + private String location; + + public SimpleLocationIndex(){} + + public SimpleLocationIndex(Index source) { + super(source); + if (source instanceof TrackingPointIndex) + location = ((TrackingPointIndex)source).getPoint().getName(); + else location = null; + } + + public String getLocation() { + return location; + } + + public void setLocation(final String location){this.location=location;} + + @Override + public String toString() { + return "SimpleLocationIndex{" + "name=" + name + ", year=" + year + ", location=" + location + ", value=" + value +'}'; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleYearIndex.java b/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleYearIndex.java new file mode 100644 index 0000000..364db85 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/result/SimpleYearIndex.java @@ -0,0 +1,81 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import fr.cenra.rhomeo.api.result.Index; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class SimpleYearIndex { + + protected String name; + protected int year; + protected Number value; + + public SimpleYearIndex(){} + + protected SimpleYearIndex(final Index source) { + name = source.getSpi().getTitle(); + year = source.getYear(); + value = source.getValue(); + } + + public String getName() { + return name; + } + + public int getYear() { + return year; + } + + public Number getValue() { + return value; + } + + public void setName(final String name){this.name=name;} + public void setYear(final int year){this.year=year;} + public void setValue(final Number value){this.value=value;} + + @Override + public String toString() { + return "SimpleYearIndex{" + "name=" + name + ", year=" + year + ", value=" + value + '}'; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/DatasetManager.java b/core/src/main/java/fr/cenra/rhomeo/core/state/DatasetManager.java new file mode 100644 index 0000000..2f3f0e7 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/DatasetManager.java @@ -0,0 +1,282 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.util.SerializableDataContext; +import java.beans.IntrospectionException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.logging.Level; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javax.annotation.PreDestroy; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Listens given dataset and data context over time, to save them on change. + * + * @author Alexis Manin (Geomatys) + */ +@Component +class DatasetManager implements StepStateManager { + + private static final String CONTEXT_FILE = "context.json"; + private static final String DATASET_FILE = "dataset.csv"; + + @Autowired + private Session session; + + @Autowired + private StateManager manager; + + private Path target; + private DataContext context; + private Dataset dataset; + + private final ObjectMapper jsonMapper; + + private final InvalidationListener datasetListener; + private final InvalidationListener contextListener; + + private StateManager.Trigger contextTrigger; + private StateManager.Trigger datasetTrigger; + + + /** + * + */ + public DatasetManager() { + jsonMapper = new ObjectMapper(); + contextListener = this::contextChanged; + datasetListener = this::datasetChanged; + } + + @Override + public WorkflowStep getStep() { + return WorkflowStep.DATASET; + } + + @Override + public synchronized void setBackupFolder(Path toWriteInto) { + target = toWriteInto; + } + + @Override + public synchronized void startSaving() { + if (session.getDataContext() == context && session.getDataset() == dataset) + return; // Already listening on changes. + + if (context != null || dataset != null) + stopSaving(); + + context = session.getDataContext(); + context.getReferences().addListener(contextListener); + contextTrigger = manager.prepareTask(this::writeContext, "Data context state"); + // If data context does not exists, we create it now + if (target != null && !Files.exists(target.resolve(CONTEXT_FILE))) { + contextChanged(null); + } + + dataset = session.getDataset(); + dataset.getItems().addListener(datasetListener); + datasetTrigger = manager.prepareTask(this::writeDataset, "Dataset state"); + // If dataset does not exists, we create it now + if (target != null && !Files.exists(target.resolve(DATASET_FILE))) { + datasetChanged(null); + } + } + + @PreDestroy + @Override + public synchronized void stopSaving() { + if (dataset != null) { + datasetTrigger.close(); + dataset.getItems().removeListener(datasetListener); + datasetTrigger = null; + dataset = null; + } + + if (context != null) { + contextTrigger.close(); + context.getReferences().removeListener(contextListener); + contextTrigger = null; + context = null; + } + } + + @Override + public boolean restoreStep() { + if (dataset != null || context != null) { + throw new IllegalStateException("Cannot restore dataset while backup is running !"); + } + + if (target == null) { + return false; + } + + /* + * We start restoration with data context, as no dataset can be + * initialized without it. The dataset is read once we're sure we've + * loaded a valid context. + */ + try { + final SerializableDataContext ctx = readContext(); + if (ctx == null || !ctx.restoreDataContext(session)) + return false; + + final List data = readDataset(); + if (data != null) { + session.getDataset().getItems().addAll(data); + } + + return true; + + } catch (Exception e) { + StateManager.LOGGER.log(Level.WARNING, "Cannot restore dataset from folder ".concat(target.toString()), e); + return false; + } + } + + /** + * + * @return A data context loaded from backup folder. Null if no context is + * available. + * @throws IOException If a backup file is present, but something goes wrong + * at reading. + */ + private SerializableDataContext readContext() throws IOException { + final Path ctxFile = target.resolve(CONTEXT_FILE); + if (Files.isReadable(ctxFile)) { + try (final BufferedReader reader = Files.newBufferedReader(ctxFile, StandardCharsets.UTF_8)) { + return jsonMapper.readValue(reader, SerializableDataContext.class); + } + } + + return null; + } + + /** + * + * @return A dataset loaded from backup folder. Null if none is available. + * @throws IntrospectionException If a backup file is present, but something + * goes wrong at reading. + * @throws IOException If a backup file is present, but something goes wrong + * at reading. + * @throws ReflectiveOperationException If an error happens with the read + * datatype. + */ + private List readDataset() throws IntrospectionException, IOException, ReflectiveOperationException { + final Path datafile = target.resolve(DATASET_FILE); + if (Files.isReadable(datafile)) { + return new CSVDecoder(datafile, session.getDataContext().getProtocol().getDataType(), StandardCharsets.UTF_8).decode(); + } + + return null; + } + + /** + * + * @return + * @throws IOException + * @throws IntrospectionException + */ + private boolean writeDataset() throws IOException, IntrospectionException { + final Path tmpFile = Files.createTempFile("tmpDataset", ".csv"); + new CSVEncoder(tmpFile, context.getProtocol().getDataType(), StandardCharsets.UTF_8).encode(dataset.getItems(), true); + + Files.move(tmpFile, target.resolve(DATASET_FILE), StandardCopyOption.REPLACE_EXISTING); + return true; + } + + /** + * Save current data context. First, we write it in a temporary file. We replace + * the backup only once we're sure the context has been successfully written. + * @throws IOException + */ + private boolean writeContext() throws IOException { + final Path tmpFile = Files.createTempFile("tmpContext", ".json"); + // Prepare a serializable object. + final SerializableDataContext ctx = SerializableDataContext.fromDataContext(context); + try (final BufferedWriter writer = Files.newBufferedWriter(tmpFile, StandardCharsets.UTF_8)) { + jsonMapper.writeValue(writer, ctx); + } + + Files.move(tmpFile, target.resolve(CONTEXT_FILE), StandardCopyOption.REPLACE_EXISTING); + return true; + } + + /** + * Notify writer that we want it to store the current context state. We both + * set trigger value and notify it, so if the writer is waiting, it will + * be notified, and if it is already running, it will immediately be aware + * that a new saving is required. + * + * @param obs + */ + private void contextChanged(final Observable obs) { + contextTrigger.arm(); + } + + /** + * Notify writer that we want it to store the current dataset state. We both + * set trigger value and notify it, so if the writer is waiting, it will + * be notified, and if it is already running, it will immediately be aware + * that a new saving is required. + * + * @param obs + */ + private void datasetChanged(final Observable obs) { + datasetTrigger.arm(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/ProcessManager.java b/core/src/main/java/fr/cenra/rhomeo/core/state/ProcessManager.java new file mode 100644 index 0000000..4d5b4a2 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/ProcessManager.java @@ -0,0 +1,305 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import java.beans.IntrospectionException; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.logging.Level; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.MapChangeListener; +import javafx.collections.SetChangeListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class ProcessManager implements StepStateManager { + + private static final String INDICATOR_FILE = "indicators.txt"; + private static final String FILTER_FILE = "filter"; + private static final String USER_DATA_FILE = "userData"; + + @Autowired + private Session session; + + @Autowired + private StateManager manager; + + private Path target; + + private ProcessContext ctx; + + private StateManager.Trigger indicatorTrigger; + private StateManager.Trigger filterTrigger; + private StateManager.Trigger userDataTrigger; + + private final SetChangeListener indicatorListener = this::indicatorsChanged; + private final ChangeListener filterListener = this::filterChanged; + private final MapChangeListener userDataListener = this::userDataChanged; + + @Override + public void setBackupFolder(Path toWriteInto) { + target = toWriteInto; + } + + @Override + public WorkflowStep getStep() { + return WorkflowStep.PROCESS; + } + + @Override + public void startSaving() { + if (ctx != null) + stopSaving(); + + ctx = session.getProcessContext(); + + // Submit backup tasks. + filterTrigger = manager.prepareTask(this::writeFilter, "Process context filter"); + indicatorTrigger = manager.prepareTask(this::writeIndicators, "Process context indicators"); + userDataTrigger = manager.prepareTask(this::writeUserData, "Process context indicators"); + + // Force writing process context in current state. + filterTrigger.arm(); + indicatorTrigger.arm(); + userDataTrigger.arm(); + + // listens on process context change. + ctx.filterProperty().addListener(filterListener); + ctx.getIndicators().addListener(indicatorListener); + ctx.getDataCtx().getUserData().addListener(userDataListener); + } + + @Override + public void stopSaving() { + if (ctx != null) { + ctx.filterProperty().removeListener(filterListener); + filterTrigger.close(); + filterTrigger = null; + + ctx.getIndicators().removeListener(indicatorListener); + indicatorTrigger.close(); + indicatorTrigger = null; + + ctx.getDataCtx().getUserData().removeListener(userDataListener); + userDataTrigger.close(); + userDataTrigger = null; + + ctx = null; + } + } + + @Override + public boolean restoreStep() { + final ProcessContext ctx = readProcessContext(); + if (ctx != null) { + session.setProcessContext(ctx); + return true; + } + + return false; + } + + ProcessContext readProcessContext() { + // Cannot restore process context if no data has been prepared before hand. + if (target == null || session.getDataContext() == null || session.getDataset() == null) + return null; + + final Predicate filter; + final Collection indics; + final Map userData; + try { + filter = readFilter(); + indics = readIndicators(); + userData = readUserData(); + } catch (Exception e) { + StateManager.LOGGER.log(Level.WARNING, "Cannot restore process context.", e); + return null; + } + + final boolean noIndic = indics == null || indics.isEmpty(); + final boolean noUserData = userData == null || userData.isEmpty(); + if (filter == null && noIndic && noUserData) { + return null; + } + + final ProcessContext context = new ProcessContext(session.getDataset(), session.getDataContext()); + if (filter != null) { + context.setFilter(filter); + } + if (!noIndic) { + context.getIndicators().addAll(indics); + } + if (!noUserData) { + context.getDataCtx().getUserData().putAll(userData); + } + return context; + } + + /** + * @return Tracking points saved in backup file. + */ + private Predicate readFilter() throws IOException, ClassNotFoundException { + final Path file; + if (target == null || !Files.isReadable((file = target.resolve(FILTER_FILE)))) + return null; + + try (final InputStream source = Files.newInputStream(file); + final ObjectInputStream input = new ObjectInputStream(source)) { + final Object read = input.readObject(); + if (read instanceof Predicate) + return (Predicate) read; + } + + return null; + } + + private Collection readIndicators() throws IOException { + final Path file; + if (target == null || !Files.isReadable((file = target.resolve(INDICATOR_FILE)))) + return null; + + final HashSet indicators = new HashSet<>(); + try (final BufferedReader r = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + r.lines().forEach(line -> { + final Optional optIndicator = session.getIndicators().stream() + .filter(indicator -> line.equals(indicator.getName())) + .findFirst(); + optIndicator.ifPresent(indicators::add); + }); + } + + return indicators; + } + + private Map readUserData() throws IOException, ClassNotFoundException { + final Path file; + if (target == null || !Files.isReadable((file = target.resolve(USER_DATA_FILE)))) + return null; + + try (final InputStream source = Files.newInputStream(file); + final ObjectInputStream input = new ObjectInputStream(source)) { + final Object read = input.readObject(); + if (read instanceof Map) + return (Map) read; + } + + return null; + } + + private boolean writeFilter() throws IOException, IntrospectionException { + if (ctx.getFilter() == null) + return false; + + final Path tmpFile = Files.createTempFile(target, FILTER_FILE, ".tmp"); + try (final OutputStream destination = Files.newOutputStream(tmpFile); + final ObjectOutputStream output = new ObjectOutputStream(destination)) { + output.writeObject(ctx.getFilter()); + } + Files.move(tmpFile, target.resolve(FILTER_FILE), StandardCopyOption.REPLACE_EXISTING); + return true; + } + + private boolean writeIndicators() throws IOException { + if (ctx.getIndicators().isEmpty()) + return false; + + final Path tmpFile = Files.createTempFile(target, INDICATOR_FILE, ".tmp"); + try (final BufferedWriter w = Files.newBufferedWriter(tmpFile, StandardCharsets.UTF_8)) { + for (final Indicator i : ctx.getIndicators()) { + w.write(i.getName()); + w.newLine(); + } + } + Files.move(tmpFile, target.resolve(INDICATOR_FILE), StandardCopyOption.REPLACE_EXISTING); + return true; + } + + private boolean writeUserData() throws IOException { + // Defensive copy. Moreover, Serialization does not work with observable map. + final Map userData = new HashMap(ctx.getDataCtx().getUserData()); + if (userData.isEmpty()) + return false; + + final Path tmpFile = Files.createTempFile(target, USER_DATA_FILE, ".tmp"); + try (final OutputStream destination = Files.newOutputStream(tmpFile); + final ObjectOutputStream output = new ObjectOutputStream(destination)) { + output.writeObject(userData); + } + + Files.move(tmpFile, target.resolve(USER_DATA_FILE), StandardCopyOption.REPLACE_EXISTING); + return true; + } + + private void filterChanged(final ObservableValue obs, Predicate oldValue, Predicate newValue) { + filterTrigger.arm(); + } + + private void indicatorsChanged(final SetChangeListener.Change c) { + indicatorTrigger.arm(); + } + + private void userDataChanged(final MapChangeListener.Change c) { + userDataTrigger.arm(); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/ResultManager.java b/core/src/main/java/fr/cenra/rhomeo/core/state/ResultManager.java new file mode 100644 index 0000000..d59cda1 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/ResultManager.java @@ -0,0 +1,270 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.result.AdditionalValueMapper; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.IndexSpi; +import fr.cenra.rhomeo.api.result.TrackingPointIndex; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; +import java.util.logging.Level; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * Special case : The results are immutable, so we just save data once, when the + * manager is activated. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class ResultManager implements StepStateManager { + + private static final String RESULT_FILE = "results.json"; + private static final String ADDITIONAL_VALUE_DIR = "additional"; + + @Autowired + private Session session; + + @Autowired + private StateManager manager; + + @Autowired(required=false) + private Set avMappers; + + private Path target; + + private Future writeState; + + private ObjectMapper mapper; + + /** + * Initialises Jackson serialisation feature in order to allow index reading + * and writing. + * + * @param spis SPIs registered for index building. + */ + @Autowired + void init(final List spis) { + final HashMap spiMap = new HashMap<>(spis.size()); + for (final IndexSpi spi : spis) { + spiMap.put(spi.getName(), spi); + } + + final SimpleModule mod = new SimpleModule("Index SPI"); + mod.addSerializer(new JsonSerializer() { + @Override + public Class handledType() { + return Index.class; + } + + @Override + public void serialize(Index value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + if (value.getSpi() == null) + return; + + jgen.writeStartObject(); + if (value instanceof TrackingPointIndex) { + jgen.writeStringField("location", ((TrackingPointIndex)value).getPoint().getName()); + } + jgen.writeNumberField("year", value.getYear()); + jgen.writeStringField("spi", value.getSpi().getName()); + jgen.writeObjectField("value", value.getValue()); + jgen.writeEndObject(); + } + }); + + mod.addDeserializer(Index.class, new JsonDeserializer() { + @Override + public Index deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + jp.nextToken(); + + final String location; + if ("location".equals(jp.getCurrentName())) { + location = jp.nextTextValue(); + jp.nextToken(); + } else { + location = null; + } + + final int year = jp.nextIntValue(0); + jp.nextToken(); + + final String strSpi = jp.nextTextValue(); + jp.nextToken(); + + jp.nextToken(); + final Number value = jp.getNumberValue(); + jp.nextToken(); + + IndexSpi spi = spiMap.get(strSpi); + if (spi != null) { + Object entity = null; + if (location != null) { + for (final TrackingPoint tp : (List) session.getDataset().getTrackingPoints()) { + if (tp.getYear() == year && location.equals(tp.getName())) { + entity = tp; + break; + } + } + } else { + entity = year; + } + return spi.createIndex(value, entity); + } + + return null; + } + }); + + mapper = new ObjectMapper(); + mapper.registerModule(mod); + } + + @Override + public void setBackupFolder(Path toWriteInto) { + target = toWriteInto; + } + + @Override + public WorkflowStep getStep() { + return WorkflowStep.RESULT; + } + + @Override + public void startSaving() { + if (writeState != null && !writeState.isDone()) { + writeState.cancel(true); + } + + writeState = manager.threadPool.submit(this::writeResults); + } + + @Override + public void stopSaving() { + // Nothing to do. + } + + @Override + public boolean restoreStep() { + final Path file; + if (target == null || !Files.isReadable(file = target.resolve(RESULT_FILE))) { + return false; + } + + try (final BufferedReader r = Files.newBufferedReader(file)) { + Index[] read = mapper.readValue(r, Index[].class); + session.setResults(new HashSet<>(Arrays.asList(read))); + } catch (Exception e) { + StateManager.LOGGER.log(Level.WARNING, "Cannot restore results."); + return false; + } + + if (avMappers != null && !avMappers.isEmpty()) { + final Path avDir = target.resolve(ADDITIONAL_VALUE_DIR); + try { + if (Files.exists(avDir)) { + Set read; + for (final AdditionalValueMapper mapper : avMappers) { + read = mapper.readValues(avDir); + if (!read.isEmpty()) { + session.getAdditionalValues().addAll(read); + break; + } + } + } + } catch (Exception e) { + StateManager.LOGGER.log(Level.WARNING, "Cannot restore additional results."); + } + } + + return true; + } + + boolean writeResults() throws IOException { + final Set results = session.getResults(); + if (results == null || results.isEmpty()) { + return false; + } + + final Path tmpFile = Files.createTempFile(target, "results", ".tmp"); + try (final BufferedWriter w = Files.newBufferedWriter(tmpFile, StandardCharsets.UTF_8)) { + mapper.writeValue(w, results); + } + + Files.move(tmpFile, target.resolve(RESULT_FILE), StandardCopyOption.REPLACE_EXISTING); + + if (avMappers != null && ! avMappers.isEmpty() && !session.getAdditionalValues().isEmpty()) { + final Path tmpDir = Files.createTempDirectory(target, ADDITIONAL_VALUE_DIR); + for (final AdditionalValueMapper avMapper : avMappers) { + if (avMapper.writeValues(tmpDir, session.getAdditionalValues())) { + Files.move(tmpDir, target.resolve(ADDITIONAL_VALUE_DIR), StandardCopyOption.REPLACE_EXISTING); + break; + } + } + } + + return true; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/StateLoader.java b/core/src/main/java/fr/cenra/rhomeo/core/state/StateLoader.java new file mode 100644 index 0000000..e03180b --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/StateLoader.java @@ -0,0 +1,47 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class StateLoader { + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/StateManager.java b/core/src/main/java/fr/cenra/rhomeo/core/state/StateManager.java new file mode 100644 index 0000000..b2f59dd --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/StateManager.java @@ -0,0 +1,360 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.beans.value.ObservableValue; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import org.apache.sis.util.logging.Logging; +import org.geotoolkit.nio.IOUtilities; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class StateManager { + + static final Logger LOGGER = Logging.getLogger("fr.cenra.rhomeo.core.state"); + + @Autowired + private Session session; + + final ExecutorService threadPool; + + private final ReadOnlyObjectWrapper folder; + + private final Map stepManagers; + + StateManager() throws IOException { + Files.createDirectories(RhomeoCore.RESTORATION_FOLDER); + threadPool = Executors.newCachedThreadPool(); + + // Register state savers. + this.stepManagers = new HashMap<>(WorkflowStep.values().length); + + folder = new ReadOnlyObjectWrapper<>(this, "state folder"); + folder.addListener(this::pathChanged); + } + + @PostConstruct + private void init() { + session.workflowStepProperty().addListener(this::stepChanged); + } + + @Autowired + private void initManagers(final Collection stepManagers) { + for (final StepStateManager ssm : stepManagers) { + this.stepManagers.put(ssm.getStep(), ssm); + } + } + + @PreDestroy + private void shutdown() { + threadPool.shutdown(); + try { + threadPool.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ex) { + threadPool.shutdownNow(); + } + } + + /** + * Try to restore applicative state from file-system backup. + * + * /!\ You MUST NOT call this method once user has started seizure. Once in + * edition mode, the manager performs backups as daemon, so it cannot restore + * previous data. + * + * @return True if a backup have been found and loaded. False otherwise. + * @throws IllegalStateException If a backup is running. + * @throws IOException If an error happens while analysing backup files. + */ + public synchronized boolean restoreState() throws IOException { + if (session.getWorkflowStep() != null) { + throw new IllegalStateException("Cannot restore dataset while backup is running !"); + } + + final List dirs; + if (folder.get() == null) { + dirs = Files.list(RhomeoCore.RESTORATION_FOLDER) + .filter(input -> { + if (!Files.isDirectory(input)) { + return false; + } + + try { + return Long.parseLong(input.getFileName().toString()) > 0; + } catch (NumberFormatException e) { + return false; + } + }).collect(Collectors.toList()); + + dirs.sort((o1, o2) -> Long.compare(Long.parseLong(o1.getFileName().toString()), Long.parseLong(o2.getFileName().toString()))); + } else { + dirs = Collections.singletonList(folder.get()); + } + + int stepCount = 0; + final Iterator it = dirs.iterator(); + while (it.hasNext() && stepCount < 1) { + folder.set(it.next()); + while (restoreStep(WorkflowStep.values()[stepCount])) { + session.requestWorkflowStep(WorkflowStep.values()[stepCount++]); + } + } + + /* + * We remove last scanned folder from the previously built list, because + * we'll use it as backup folder. For the remaining ones, we'll delete + * them. They've got no reason to remain if they don't serve our backup + * purpose. + */ + if (!dirs.isEmpty()) { + it.remove(); + for (final Path p : dirs) { + submitDeletion(p); + } + } + + return stepCount > 0; + } + + private synchronized void resetContext() { + try { + final Path newPath = RhomeoCore.RESTORATION_FOLDER.resolve(Long.toString(System.currentTimeMillis())); + Files.createDirectories(newPath); + folder.set(newPath); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Impossible to set a new restoration context.", ex); + folder.set(null); + } + } + + public ReadOnlyObjectProperty folderProperty() { + return folder.getReadOnlyProperty(); + } + + public Path getFolder() { + return folder.get(); + } + + /* + * STATE OPERATIONS + */ + + private boolean restoreStep(WorkflowStep target) { + final StepStateManager ssm = stepManagers.get(target); + if (ssm != null) { + return ssm.restoreStep(); + } + + return false; + } + + private void startSaving(final WorkflowStep target) { + final StepStateManager ssm = stepManagers.get(target); + if (ssm != null) { + ssm.startSaving(); + } + } + + private void stopSaving(final WorkflowStep target) { + final StepStateManager ssm = stepManagers.get(target); + if (ssm != null) { + ssm.stopSaving(); + } + } + + /* + * PROPERTY LISTENERS + */ + + private void stepChanged(final ObservableValue obs, WorkflowStep oldStep, WorkflowStep newStep) { + if (oldStep != null) { + stopSaving(oldStep); + } + + if (newStep != null) { + if (folder.get() == null) { + resetContext(); // No folder parameterized yet. Create a new one. + } + startSaving(newStep); + } else { + resetContext(); // Seizure properly stopped. We clear backups. + } + } + + /** + * When changing backup folder, we notify each step manager with a new sub-directory. + * Once done, we delete the old backup. + * @param obs + * @param oldPath + * @param newPath + */ + private void pathChanged(final ObservableValue obs, final Path oldPath, final Path newPath) { + try { + if (newPath != null) { + for (final Map.Entry entry : stepManagers.entrySet()) { + final Path subDir = newPath.resolve(entry.getKey().name()); + try { + Files.createDirectories(subDir); + } catch (IOException ex) { + throw new RhomeoRuntimeException("Cannot create a backup directory", ex); + } + entry.getValue().setBackupFolder(subDir); + } + } else { + for (final StepStateManager ssm : stepManagers.values()) { + ssm.setBackupFolder(null); + } + } + } finally { + if (oldPath != null) { + submitDeletion(oldPath); + } + } + } + + private void submitDeletion(final Path dir) { + threadPool.submit(() -> { + Thread.currentThread().setName("State deletion"); + try { + IOUtilities.deleteRecursively(dir); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "Cannot remove directory ".concat(dir.toString()), ex); + } + }); + } + + /** + * Setup a trigger which controls the call to a given task. The aim is to + * provide a simple way to launch a process with the following constraints : + * - One unique instance of the process must be running at any time. + * - Whatever the number of time the process is queried between two calls, + * it will be triggered for execution only once. + * @param task The task to put as triggerable process. + * @return A trigger to allow calling input process anytime needed. + */ + Trigger prepareTask(final Callable task, final String name) { + return new Trigger(task, name); + } + + /** + * A trigger which controls the call to a given task. The aim is to provide + * a simple way to launch a process with the following constraints : + * - One unique instance of the process must be running at any time. + * - Whatever the number of time the process is queried between two calls, + * it will be triggered for execution only once. + */ + class Trigger implements AutoCloseable { + + final AtomicBoolean flag; + final Future taskState; + + private Trigger(final Callable task) { + this(task, null); + } + + private Trigger(final Callable task, final String name) { + flag = new AtomicBoolean(false); + taskState = threadPool.submit(() -> { + final Thread thread = Thread.currentThread(); + if (name != null && !name.isEmpty()) + thread.setName(name); + while (!thread.isInterrupted()) { + if (flag.getAndSet(false)) { + try { + task.call(); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "A state management task has failed.", e); + } + } else + synchronized (flag) { + flag.wait(); + } + } + return true; + }); + } + + /** + * Ask for a call of the associated process. + */ + public void arm() { + flag.set(true); + synchronized (flag) { + flag.notify(); + } + } + + /** + * Definitely cancel associated process. + */ + public void close() { + taskState.cancel(true); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/state/StepStateManager.java b/core/src/main/java/fr/cenra/rhomeo/core/state/StepStateManager.java new file mode 100644 index 0000000..5b79039 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/state/StepStateManager.java @@ -0,0 +1,82 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import fr.cenra.rhomeo.core.WorkflowStep; +import java.nio.file.Path; + +/** + * Describes an operator designed to save a particular {@link WorkflowStep} data. + * the aim is to provide for each step a way to activate or de-activate saving. + * + * @author Alexis Manin (Geomatys) + */ +interface StepStateManager { + + /** + * Specify the folder in which this manager will have to write saved data. + * + * @param toWriteInto Path to use for saving. + */ + void setBackupFolder(final Path toWriteInto); + + /** + * + * @return The step this object is designed for. + */ + WorkflowStep getStep(); + + /** + * Ask to this component to save step related changes over time, until we + * ask for it to stop using {@link #stopSaving() }. + */ + void startSaving(); + + /** + * Ask to this component to stop saving changes related to the given step. + */ + void stopSaving(); + + /** + * Ask to this object to load step related data from backup. + * @return True if information has been successfully loaded. Return false if + * no backup is available, or it failed to load. + */ + boolean restoreStep(); +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/AOC.java b/core/src/main/java/fr/cenra/rhomeo/core/util/AOC.java new file mode 100644 index 0000000..b6d7c0e --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/AOC.java @@ -0,0 +1,73 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import java.util.Collections; +import java.util.Set; +import org.apache.sis.math.FunctionProperty; +import org.apache.sis.util.ObjectConverter; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public abstract class AOC implements ObjectConverter { + private final Class inputType; + private final Class outputType; + + public AOC(Class inputType, Class outputType) { + this.inputType = inputType; + this.outputType = outputType; + } + + @Override + public Set properties() { + return Collections.EMPTY_SET; + } + + @Override + public Class getSourceClass() { + return inputType; + } + + @Override + public Class getTargetClass() { + return outputType; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/EncryptionResult.java b/core/src/main/java/fr/cenra/rhomeo/core/util/EncryptionResult.java new file mode 100644 index 0000000..d5d90ff --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/EncryptionResult.java @@ -0,0 +1,70 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import java.util.Arrays; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class EncryptionResult { + + public final String output; + private final byte[] key; + private final byte[] sr; + + public EncryptionResult(String output, byte[] key, byte[] sr) { + this.output = output; + this.key = key; + this.sr = sr; + } + + public String getOutput() { + return output; + } + + public byte[] getKey() { + return Arrays.copyOf(key, key.length); // defensive copy + } + + public byte[] getSecureRandom() { + return Arrays.copyOf(sr, sr.length); // defensive copy + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/ExportUtils.java b/core/src/main/java/fr/cenra/rhomeo/core/util/ExportUtils.java new file mode 100644 index 0000000..8ee9041 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/ExportUtils.java @@ -0,0 +1,686 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryCollection; +import com.vividsolutions.jts.geom.LineString; +import com.vividsolutions.jts.geom.MultiPolygon; +import com.vividsolutions.jts.geom.Point; +import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.list.HumidZoneType; +import fr.cenra.rhomeo.core.list.OdonateZoneBio; +import fr.cenra.rhomeo.core.list.OrthoptereZoneBio; +import fr.cenra.rhomeo.core.data.site.SiteImpl; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.zip.ZipOutputStream; +import javax.xml.stream.XMLStreamException; + +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.atom.AtomFactory; +import org.geotoolkit.atom.DefaultAtomFactory; +import org.geotoolkit.atom.xml.AtomConstants; +import org.geotoolkit.data.FeatureStoreUtilities; +import org.geotoolkit.data.geojson.GeoJSONFeatureStore; +import org.geotoolkit.data.kml.DefaultKmlFactory; +import org.geotoolkit.data.kml.KmlFactory; +import org.geotoolkit.data.kml.model.AbstractGeometry; +import org.geotoolkit.data.kml.model.Boundary; +import org.geotoolkit.data.kml.model.Kml; +import org.geotoolkit.data.kml.model.KmlException; +import org.geotoolkit.data.kml.model.LinearRing; +import org.geotoolkit.data.kml.xml.KmlConstants; +import org.geotoolkit.data.kml.xml.KmlWriter; +import org.geotoolkit.data.kml.xsd.SimpleTypeContainer; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.feature.type.FeatureType; +import org.geotoolkit.geometry.jts.JTS; +import org.geotoolkit.referencing.CRS; +import org.geotoolkit.xal.xml.XalConstants; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; +import java.util.Set; +import javax.validation.ConstraintViolation; +import org.geotoolkit.feature.FeatureTypeBuilder; +import org.geotoolkit.feature.FeatureUtilities; +import org.geotoolkit.nio.ZipUtilities; +import static fr.cenra.rhomeo.core.data.site.SiteRepositoryImpl.toFeature; +import java.io.OutputStream; +import java.net.URI; +import java.util.Iterator; +import java.util.Optional; +import java.util.StringJoiner; +import java.util.stream.Collectors; +import org.apache.sis.referencing.CommonCRS; +import org.geotoolkit.data.FeatureStore; +import org.geotoolkit.data.geojson.GeoJSONStreamWriter; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.feature.FeatureBuilder; +import org.geotoolkit.feature.GeometryAttribute; +import org.geotoolkit.nio.IOUtilities; +import org.opengis.feature.Property; +import org.opengis.util.GenericName; + +/** + * Export utilities. + * + * @author Samuel Andrés (Geomatys) + * @author Cédric Briançon (Geomatys) + */ +public class ExportUtils { + + public static final String SITE_JSON = RhomeoCore.SITES.concat(".geojson"); + + public static final String METADATA_JSON = "metadata.json"; + + public static final String RHOMEO_PREFIX = "rhomeo"; + + public static final KmlFactory KML_FACTORY = DefaultKmlFactory.getInstance(); + + public static final AtomFactory ATOM_FACTORY = DefaultAtomFactory.getInstance(); + + public static MathTransform TRANSFORM; + static { + try { + // Set transform from site CRS to .kml CRS. + TRANSFORM = CRS.findMathTransform(RhomeoCore.getSiteCRS(), CommonCRS.WGS84.normalizedGeographic(), true); + } catch (FactoryException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Unable to build MathTransform to Kml export.", ex); + } + } + + public static enum ExportType { + SHP("ESRI Shapefile", "shp"), + KML("Google KML", "kml"), + GJSON("GeoJSON", "json"); + + public final String title; + public final String extension; + private ExportType(final String title, final String extension) { + this.title = title; + this.extension = extension; + } + + @Override + public String toString() { + return title; + } + } + + /** + * Builds a list of KML {@link AbstractGeometry} (WGS 84) from a {@link Site} + * {@link MultiPolygon} (Lambert 93). + * + * @param multiPolygon The site Lambert 93 geometry. + * @return A list of KML WGS 84 geometries mapping the site geometry. + */ + static List toKmlGeometries(Geometry geom) throws TransformException { + final List result; + if (geom instanceof GeometryCollection) { + final GeometryCollection col = (GeometryCollection) geom; + result = new ArrayList<>(); + for (int i = 0 ; i < col.getNumGeometries() ; i++) { + result.addAll(toKmlGeometries(col.getGeometryN(i))); + } + } else { + AbstractGeometry converted = null; + if (geom != null) + geom = JTS.transform(geom, TRANSFORM); + if (geom instanceof com.vividsolutions.jts.geom.Polygon) { + final com.vividsolutions.jts.geom.Polygon currentPolygon = (com.vividsolutions.jts.geom.Polygon) geom; + // Exterior ring + final LinearRing extRing = KML_FACTORY.createLinearRing(new CoordinateArraySequence(currentPolygon.getExteriorRing().getCoordinates())); + final Boundary extBoundary = KML_FACTORY.createBoundary(extRing, null, null); + + // Interior rings + final List intBoundaries = new ArrayList<>(); + for (int j = 0; j < currentPolygon.getNumInteriorRing(); j++) { + final LinearRing intRing = KML_FACTORY.createLinearRing(new CoordinateArraySequence(currentPolygon.getInteriorRingN(j).getCoordinates())); + intBoundaries.add(KML_FACTORY.createBoundary(intRing, null, null)); + } + converted = KML_FACTORY.createPolygon(extBoundary, intBoundaries); + } else if (geom instanceof com.vividsolutions.jts.geom.LinearRing) { + converted = KML_FACTORY.createLinearRing(((com.vividsolutions.jts.geom.LinearRing) geom).getCoordinateSequence()); + } else if (geom instanceof LineString) { + converted = KML_FACTORY.createLineString(((com.vividsolutions.jts.geom.LineString) geom).getCoordinateSequence()); + } else if (geom instanceof Point) { + converted = KML_FACTORY.createPoint(((Point) geom).getCoordinateSequence()); + } + + result = converted == null? Collections.EMPTY_LIST : Collections.singletonList(converted); + } + + return result; + } + + /** + * Builds a description from some {@link Site} fields. + * + * @param countyCode + * @param zoneType + * @param odonateType + * @param orthoptereType + * @return + */ + static String description(final String countyCode, final String zoneType, final String odonateType, final String orthoptereType, final String remarks){ + final StringBuilder sb = new StringBuilder(); + + if (countyCode != null && !countyCode.isEmpty()) { + sb.append(InternationalResource.getFormatedResourceString(SiteImpl.class, "countyCode.kml", countyCode)); + } + + if (zoneType != null) { + final HumidZoneType zone = HumidZoneType.getByCode(zoneType); + if (zone != null) { + if (sb.length() > 0) + sb.append(System.lineSeparator()); + + sb.append(InternationalResource.getFormatedResourceString(SiteImpl.class, "zoneType.kml", zone.getValue(), zone.getCode())); + } + } + if (odonateType != null) { + final OdonateZoneBio zone = OdonateZoneBio.getByCode(odonateType); + if (zone != null) { + if (sb.length() > 0) + sb.append(System.lineSeparator()); + + sb.append(InternationalResource.getFormatedResourceString(SiteImpl.class, "odonateType.kml", zone.getValue(), zone.getCode())); + } + } + if (orthoptereType != null) { + final OrthoptereZoneBio zone = OrthoptereZoneBio.getByCode(orthoptereType); + if (zone != null) { + if (sb.length() > 0) + sb.append(System.lineSeparator()); + + sb.append(InternationalResource.getFormatedResourceString(SiteImpl.class, "orthoptereType.kml", zone.getValue(), zone.getCode())); + } + } + if (remarks != null && !remarks.isEmpty()) { + if (sb.length() > 0) + sb.append(System.lineSeparator()); + sb.append(InternationalResource.getFormatedResourceString(SiteImpl.class, "remarks.kml", remarks)); + } + return sb.toString(); + } + + /** + * Writes the given {@link Site} to the given output as .kml v2.2 format. + * + * @param site + * @param output + * @throws XMLStreamException + * @throws IOException + * @throws KmlException + * @throws TransformException + * @throws DataStoreException + */ + public static void writeKml(final Site site, final Object output) throws XMLStreamException, IOException, KmlException, TransformException, DataStoreException{ + ExportUtils.writeKml(toFeature(site), output); + } + + /** + * Writes the given {@link Site} {@link Feature} to the given output as .kml v2.2 format. + * + * @param site + * @param output + * @throws XMLStreamException + * @throws IOException + * @throws KmlException + * @throws org.opengis.referencing.operation.TransformException + */ + public static void writeKml(final Feature site, final Object output) throws XMLStreamException, IOException, KmlException, TransformException { + + // Retrieve site data incompatible with KML base semantics. + final Property county = site.getProperty(RhomeoCore.FT_PROPERTIES.COUNTY.name()); + final Property type = site.getProperty(RhomeoCore.FT_PROPERTIES.TYPE.name()); + final Property odonate = site.getProperty(RhomeoCore.FT_PROPERTIES.ODONATE.name()); + final Property orthoptere = site.getProperty(RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name()); + final Property remarks = site.getProperty(RhomeoCore.FT_PROPERTIES.REMARKS.name()); + + // Build containers for rhomeo specific semantics. + final List rhomeoExtensions = new ArrayList<>(); + if (county != null) + rhomeoExtensions.add(KML_FACTORY.createSimpleTypeContainer(RhomeoKmlWriter.NAMESPACE, RhomeoKmlWriter.TAG_COUNTY, county.getValue())); + if (type != null) + rhomeoExtensions.add(KML_FACTORY.createSimpleTypeContainer(RhomeoKmlWriter.NAMESPACE, RhomeoKmlWriter.TAG_TYPE, type.getValue())); + if (odonate != null) + rhomeoExtensions.add(KML_FACTORY.createSimpleTypeContainer(RhomeoKmlWriter.NAMESPACE, RhomeoKmlWriter.TAG_ODONATE, odonate.getValue())); + if (orthoptere != null) + rhomeoExtensions.add(KML_FACTORY.createSimpleTypeContainer(RhomeoKmlWriter.NAMESPACE, RhomeoKmlWriter.TAG_ORTHOPTERE, orthoptere.getValue())); + + final Property org = site.getProperty(RhomeoCore.FT_PROPERTIES.ORG.name()); + + final GeometryAttribute geomProp = site.getDefaultGeometryProperty(); + final List geoms = geomProp == null || geomProp.getValue() == null? null : toKmlGeometries((Geometry)geomProp.getValue()); + + final Property nameProp = site.getProperty(RhomeoCore.FT_PROPERTIES.NAME.name()); + final String name; + if (nameProp == null || nameProp.getValue() == null) + name = null; + else + name = nameProp.getValue().toString(); + + final String description; + if (county != null || type != null || odonate != null || orthoptere != null || remarks != null) + description = description( + county == null? null : String.valueOf(county.getValue()), + type == null? null : String.valueOf(type.getValue()), + odonate == null? null : String.valueOf(odonate.getValue()), + orthoptere == null? null : String.valueOf(orthoptere.getValue()), + remarks == null? null : String.valueOf(remarks.getValue())); + else description = null; + + final Property referent = site.getProperty(RhomeoCore.FT_PROPERTIES.REFERENT.name()); + + // Build placemark mapping site data. + final Feature placemark = KML_FACTORY.createPlacemark(null, + null, + name, + true, + true, + name==null ? null : ATOM_FACTORY.createAtomPersonConstruct(Collections.singletonList(name)), + null, + name, + null, + null, + null, + description == null? null : description, + null, + null, + null, + new ArrayList<>(), + null, + null, + null, + null, + KML_FACTORY.createMultiGeometry(null, null, null, null, geoms, null, null), + rhomeoExtensions, + null); + + final Kml kml = KML_FACTORY.createKml(); + + // Set KML version + kml.setVersion(KmlConstants.URI_KML_2_2); + + // Set prefixes for URIs. + final Map extensions = new HashMap<>(); + extensions.put(AtomConstants.URI_ATOM, KmlConstants.PREFIX_ATOM); + extensions.put(XalConstants.URI_XAL, KmlConstants.PREFIX_XAL); + extensions.put(RhomeoKmlWriter.NAMESPACE, RHOMEO_PREFIX); + kml.setExtensionsUris(extensions); + + // Set site placemark + kml.setAbstractFeature(placemark); + + final KmlWriter writer = new KmlWriter(); + try{ + writer.setOutput(output); + writer.addExtensionWriter(RhomeoKmlWriter.NAMESPACE, new RhomeoKmlWriter()); + writer.write(kml); + } + finally{ + writer.dispose(); + } + } + + /** + * Writes the given {@Site} as a shapefile to the given output file. + * + * @param site + * @param outputFile + * @throws DataStoreException + * @throws MalformedURLException + */ + public static void writeShp(final Site site, final File outputFile) throws DataStoreException, MalformedURLException, IOException { + ArgumentChecks.ensureNonNull("Output zip file", outputFile); + final Feature toAdd = toFeature(site); + write(toAdd, outputFile.toPath(), ExportType.SHP); + } + + /** + * Writes the selected site into a geojson file. + * + * @param site Selected site to serialize. + * @param outputFolder Geojson in which to write the site. + * @return The json path created. + * @throws IOException + * @throws DataStoreException + */ + public static Path writeGeoJson(final Site site, final Path outputFolder) throws IOException, DataStoreException { + final Path outputJson = outputFolder.resolve(SITE_JSON); + Files.deleteIfExists(outputJson); + final Path outputTypeJson = outputFolder.resolve(RhomeoCore.SITES +"_Type.json"); + Files.deleteIfExists(outputTypeJson); + final Feature toAdd = toFeature(site); + + + write(toAdd, outputJson, ExportType.GJSON); + return outputJson; + } + + public static void write(final Feature feature, final Path outputFile, final ExportType format) throws IOException, DataStoreException { + switch (format) { + case KML: + try { + writeKml(feature, outputFile); + break; + } catch (Exception e) { + throw new IOException(e); + } + case GJSON: + // HACK : Force long/lat coordinates, because our writer uses + // comas in big numbers (Ex: 10000 --> 10,000), which breaks format. + FeatureStoreUtilities.collection(feature).subCollection(QueryBuilder.reprojected(feature.getType().getName(), CommonCRS.defaultGeographic())); + try (final OutputStream out = Files.newOutputStream(outputFile); + final GeoJSONStreamWriter writer = new GeoJSONStreamWriter(out, feature.getType(), JsonEncoding.UTF8, 4)) { + FeatureUtilities.copy( + FeatureStoreUtilities.collection(feature) + .subCollection(QueryBuilder.reprojected(feature.getType().getName(), CommonCRS.defaultGeographic())) + .iterator().next(), + writer.next(), false); + writer.write(); + } + break; + + default: + final GenericName name = feature.getType().getName(); + final FeatureStore store = create(outputFile.toUri(), String.valueOf(name.scope()), format); + try { + store.createFeatureType(name, feature.getType()); + store.addFeatures(name, FeatureStoreUtilities.collection(feature)); + } finally { + // TODO : replace with try/w/r once FeatureStore implements auto-closeable. + if (store != null) + store.close(); + } + } + } + + public static FeatureStore create(final URI dataPath, final String namespace, final ExportType format) throws DataStoreException, MalformedURLException { + ArgumentChecks.ensureNonNull("Output path", dataPath); + switch (format) { + case SHP: return new ShapefileFeatureStore(dataPath, namespace); + case KML: throw new IllegalArgumentException("No feature store available for KML data"); + default: + return new GeoJSONFeatureStore(dataPath, namespace, null); + } + } + + /** + * Writes the given {@Site} as a zipped shapefile to the given output which + * is assumed end with ".zip" extension. + * + * @param site + * @param outputFile + * @throws java.io.IOException + * @throws org.apache.sis.storage.DataStoreException + * @throws IllegalArgumentException if outputPath does not end with ".zip" extension. + */ + public static void writeZip(final Site site, final File outputFile) throws IOException, DataStoreException{ + + ArgumentChecks.ensureNonNull("Output zip file", outputFile); + + if(!outputFile.getName().endsWith(".zip")) throw new IllegalArgumentException("Output file name must end with .zip extension."); + + final Path tmpDirectory = Files.createTempDirectory("site"); + tmpDirectory.toFile().deleteOnExit(); + final File shpFile = new File(tmpDirectory.toFile(), outputFile.getName().replaceFirst(".zip$", ".shp")); + + writeShp(site, shpFile); + + final Collection resources = new ArrayList<>(); + + Files.walkFileTree(tmpDirectory, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final File toAdd = file.toFile(); + toAdd.deleteOnExit(); + resources.add(file); + return FileVisitResult.CONTINUE; + } + }); + + ZipUtilities.zip(outputFile.toPath(), ZipOutputStream.DEFLATED, 9, null, resources.toArray(new Path[0])); + } + + /** + * Write metadata in a JSON file using information stored in the {@linkplain SerializableDataContext context}. + * + * @param context Data to serialize. + * @param outputFolder Folder which will contain the json file. + * @return Path of the generated metadata json. + * @throws IOException + */ + public static Path writeMetadata(final SerializableDataContext context, final Path outputFolder) throws IOException { + final Path metadataJsonFile = outputFolder.resolve(METADATA_JSON); + Files.deleteIfExists(metadataJsonFile); + try (final BufferedWriter writer = Files.newBufferedWriter(metadataJsonFile, StandardCharsets.UTF_8)) { + final ObjectMapper jsonMapper = new ObjectMapper(); + jsonMapper.writeValue(writer, context); + } + return metadataJsonFile; + } + + /** + * Export Site and its buffer in a given zip archive. + * + * @param outputFile The ZIP file in which we must insert data. Must not be null + * @param site The site to export. Must not be null. + * @param buffer The computed buffer for input site. Must not be null + * @param territory Geometry representing hydrographic zones which intersects the buffer. Optional. + * @param type File format to use for data. Must not be null + * + */ + public static void exportSiteAndBuffer(File outputFile, Site site, Geometry buffer, Optional territory, ExportType type) throws Exception { + if (outputFile == null || type == null) + return; + + final Path outputZipFile = outputFile.toPath(); + final Path tmpDir = Files.createTempDirectory("export"); + try { + // issue #40 : write concatenation of site + buffer + Feature siteFeat = toFeature(site); + final FeatureTypeBuilder ftb = new FeatureTypeBuilder(); + ftb.copy(siteFeat.getType()); + ftb.setName("site_buffer"); + ftb.add("type_contour", String.class); + final FeatureType siteBufType = ftb.buildFeatureType(); + siteFeat.getDefaultGeometryProperty().setValue(buffer.difference(site.getGeometry())); + FeatureBuilder fb = new FeatureBuilder(siteBufType); + for (final Property p : siteFeat.getProperties()) { + fb.setPropertyValue(p.getName(), p.getValue()); + } + fb.setPropertyValue("type_contour", "site+buffer"); + // write site + write(fb.buildFeature(site.getName()), tmpDir.resolve("site_buffer.".concat(type.extension)), type); + + // issue #40 : write intersecting hydrologic zones + + if (territory.isPresent()) { + final Geometry territoryGeom = territory.get(); + ftb.reset(); + ftb.setName("territoire"); + ftb.add("the_geom", territoryGeom.getClass(), JTS.findCoordinateReferenceSystem(territoryGeom)); + ftb.add("type_contour", String.class); + final FeatureType featureType = ftb.buildFeatureType(); + final Feature feat = FeatureUtilities.defaultFeature(featureType, "territoire"); + feat.getDefaultGeometryProperty().setValue(territoryGeom); + feat.setPropertyValue("type_contour", "territoire"); + write(feat, tmpDir.resolve("territoire.".concat(type.extension)), type); + } + // Zip all data + ZipUtilities.zip(outputZipFile, ZipOutputStream.DEFLATED, 9, null, Files.list(tmpDir).collect(Collectors.toList()).toArray(new Path[0])); + } finally { + IOUtilities.deleteRecursively(tmpDir); + } + } + + /** + * Write result metadata file from input parameters. The output format is + * simple text. + * @param output File to write into. + * @param site Site to write. + * @param protocol Protocol to write. + * @param referenceVersions Reference versions used for computing. (Key = reference or reference name, value = version) + * @param userData + * @param indicators computed indicators + * @param errors Errors which occurred while processing results. + * @throws IOException + */ + public static void writeMetadataText( + final Path output, + final Site site, + final Protocol protocol, + final Map referenceVersions, + final Collection indicators, + final Map userData, + final Set> errors) throws IOException { + try (final BufferedWriter writer = Files.newBufferedWriter(output, StandardCharsets.UTF_8)) { + writer.write("---- Site ----"); + writer.newLine(); + writer.write("Nom : "); + writer.write(site.getName()); + writer.newLine(); + writer.write("Referent : "); + writer.write(site.getReferent() == null? "Non renseigné" : site.getReferent()); + writer.newLine(); + writer.write("Organisation : "); + writer.write(site.getOrganization() == null? "Non renseigné" : site.getOrganization()); + writer.newLine(); + writer.write("Département : "); + writer.write(site.getCountyCode()); + writer.newLine(); + writer.newLine(); + + writer.write("---- Protocole ----"); + writer.newLine(); + writer.write(protocol.getTitle()); + writer.newLine(); + writer.newLine(); + + if (!referenceVersions.isEmpty()) { + writer.write("---- Versions des référentiels ----"); + for (final Map.Entry ref : referenceVersions.entrySet()) { + writer.newLine(); + writer.write(toString(ref.getKey())); + writer.write(" : "); + writer.write(toString(ref.getValue())); + } + writer.newLine(); + writer.newLine(); + } + + writer.write("---- Indicateurs calculés ----"); + for (final Indicator i : indicators) { + writer.newLine(); + writer.write(i.getTitle()); + } + + if (userData != null && !userData.isEmpty()) { + for (final Map.Entry data : userData.entrySet()) { + writer.newLine(); + writer.newLine(); + writer.write("---- " + data.getKey() + " ----"); + writer.newLine(); + writer.write(toString(data.getValue())); + } + } + + if (!errors.isEmpty()) { + writer.newLine(); + writer.newLine(); + writer.write("---- Erreurs sur le lot de données ----"); + for (final ConstraintViolation v : errors) { + writer.newLine(); + writer.write(v.getMessage()); + } + } + } + } + + private static String toString(final Object o) { + if (o == null) + return "N/A"; + else if (o instanceof Collection) { + final StringJoiner joiner = new StringJoiner(", ", "[", "]"); + final Iterator it = ((Collection)o).iterator(); + while (it.hasNext()) + joiner.add(toString(it.next())); + return joiner.toString(); + } else if (o instanceof IdentifiedObject) { + return ((IdentifiedObject)o).getTitle(); + } else { + final String value = o.toString().trim(); + if (value.isEmpty()) + return "N/A"; + return value; + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryConverters.java b/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryConverters.java new file mode 100644 index 0000000..8acd8e8 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryConverters.java @@ -0,0 +1,99 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.io.ParseException; +import com.vividsolutions.jts.io.WKTReader; +import org.apache.sis.util.ObjectConverter; +import org.apache.sis.util.UnconvertibleObjectException; + +/** + * Simple object converter between {@link Geometry} and {@link String} types. + * @author Alexis Manin (Geomatys) + */ +public class GeometryConverters { + + public static class GeometryToString extends AOC { + + public GeometryToString() { + super(Geometry.class, String.class); + } + + @Override + public String apply(Geometry object) throws UnconvertibleObjectException { + if (object == null) + return null; + return object.toText(); + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new StringToGeometry(); + } + } + + + + public static class StringToGeometry extends AOC { + + final WKTReader reader; + public StringToGeometry() { + super(String.class, Geometry.class); + reader = new WKTReader(); + } + + @Override + public Geometry apply(String object) throws UnconvertibleObjectException { + if (object == null || (object = object.trim()).isEmpty()) + return null; + + try { + return reader.read(object); + } catch (ParseException ex) { + throw new UnconvertibleObjectException(ex); + } + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new GeometryToString(); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryUtils.java b/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryUtils.java new file mode 100644 index 0000000..a62a544 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/GeometryUtils.java @@ -0,0 +1,87 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.vividsolutions.jts.geom.Geometry; +import org.apache.commons.codec.digest.DigestUtils; + +/** + * @author Cédric Briançon (Geomatys) + * @author Johann Sorel (Geomatys) + */ +public class GeometryUtils { + /** + * Get a SHA1 representation of a geometry. + * + * @param geometry Geometry to convert. Must not be {@code null} + * @return SHA1 of the geometry, never {@code null} + */ + public static String getSha1(Geometry geometry) { + return DigestUtils.sha1Hex(geometry.toText()); + } + + /** + * Compute geometry buffer.
+ *
+ * Java version of SQL : + *
+     * {@code
+     * CREATE OR replace FUNCTION outils.calcul_geom_buffer()
+     * returns TRIGGER AS $body$DECLARE surf_buff DOUBLE PRECISION;radius DOUBLE PRECISION;BEGIN
+     *      surf_buff := st_area(new.geom);
+     *      radius := ( |/ (surf_buff*2/pi()) ) - ( |/ (surf_buff/pi()) );
+     *      new.geom_buffer := st_multi(st_difference(st_buffer(new.geom,radius,40),new.geom));
+     *      return new;
+     *  end;$BODY$ language plpgsql volatile cost 100;
+     * }
+     * 
+ * + * @return buffer geometry + */ + public static Geometry computeBuffer(Geometry geom){ + final double surfBuff = geom.getArea(); + final double radius = Math.sqrt(surfBuff*2.0/Math.PI) - Math.sqrt(surfBuff/Math.PI); + Geometry g = geom.buffer(radius, 40); + //.difference(geom); + //note : by comparing result in database dump, the difference operator is not used anymore + g.setSRID(geom.getSRID()); + g.setUserData(geom.getUserData()); + return g; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/RelationResolver.java b/core/src/main/java/fr/cenra/rhomeo/core/util/RelationResolver.java new file mode 100644 index 0000000..a1e5a43 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/RelationResolver.java @@ -0,0 +1,256 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.annotations.RefersTo; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.ReferenceManager; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.logging.Level; +import javafx.beans.value.ObservableValue; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * Tries to find boundaries declared by the {@link RefersTo} annotation. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class RelationResolver { + + @Autowired + private Session session; + + /** + * Try to find all available objects of the type matching input annotation + * referred class. + * + * @param reference The annotation describing class to get instances for. + * @return A set of available values, or an empty set if we cannot find any. + * Never null. + * @throws IntrospectionException + */ + public Set getTargetObjects(final RefersTo reference) throws IntrospectionException { + final Class referedType = reference.type(); + Set result = Collections.EMPTY_SET; + if (Reference.class.isAssignableFrom(referedType)) { + ReferenceDescription desc = session.getDescription(referedType); + if (desc != null) + result = getReferences(desc); + + } else if (ReferenceDescription.class.isAssignableFrom(referedType)) { + final ReferenceDescription desc = (ReferenceDescription) session.getBean(referedType); + if (desc != null) + result = getReferences(desc); + + } else if (Enum.class.isAssignableFrom(referedType)) { + result = new LinkedHashSet(Arrays.asList(referedType.getEnumConstants())); + + } else { + // TODO : try to acquire a repository. + } + + if (!result.isEmpty()) { + final Class filter = reference.filterClass(); + try { + if (filter != null && !filter.isInterface() && !Modifier.isAbstract(filter.getModifiers())) { + final Predicate p = filter.newInstance(); + result.removeIf(p.negate()); + } + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot filter objects !", e); + } + } + return result; + } + + /** + * Try to find all possible values for the property described by input + * annotation. + * + * @param reference An annotation describing a property of a specific class. + * @return A set of possible values for the described property. Never null, + * but can be empty. + * @throws IntrospectionException + * @throws NoSuchMethodException + */ + public Set getPossibleValues(final RefersTo reference) throws IntrospectionException, NoSuchMethodException { + final Function extractor = acquireExtractor(reference); + + final Set objects = getTargetObjects(reference); + if (objects.isEmpty()) { + return Collections.EMPTY_SET; + } + + final LinkedHashSet result = new LinkedHashSet(objects.size()); + for (final Object o : objects) { + result.add(extractor.apply(o)); + } + + return result; + } + + /** + * Find objects of type specified by given annotation, whose property quoted + * the same annotation is equal to given object. + * + * @param reference The reference annotation giving target type and + * property. + * @param sourceValue Value of the property to compare to available + * referenced objects. + * @return A set of object whose property described in input annotation is + * equal to given object. + * @throws IntrospectionException If we cannot analyze input refered type. + * @throws NoSuchMethodException If we cannot acquire a getter for refered + * property. + */ + public Set getMatchingObjects(final RefersTo reference, final Object sourceValue) throws IntrospectionException, NoSuchMethodException { + Function extractor = acquireExtractor(reference); + + final HashSet result = new HashSet(); + final Set possibleObjects = getTargetObjects(reference); + Object extracted; + for (final Object o : possibleObjects) { + extracted = extractor.apply(o); + if (Objects.equals(sourceValue, extracted)) { + result.add(o); + } + } + + return result; + } + + /** + * Try to find loaded values for the given reference type. + * + * IMPORTANT : This method won't try to load any data. It's the caller job + * to ensure that queried reference has already been loaded using + * {@link ReferenceManager} mechanism. + * + * @param Type of reference object to get values for. + * @param desc Descriptor of the wanted type. + * @return Available values for given reference type. + * @throws IntrospectionException If we cannot analyze reference type. + */ + private Set getReferences(final ReferenceDescription desc) throws IntrospectionException { + final DataContext dataContext = session.getDataContext(); + if (dataContext != null) { + Version refVersion = dataContext.getReferences().get(desc.getReferenceType()); + if (refVersion != null) { + return new LinkedHashSet<>(ReferenceManager.getOrCreate(desc).getValues(refVersion)); + } + } + return Collections.EMPTY_SET; + } + + /** + * Create a function to get value of the property referenced by input + * annotation from an object of the class referenced by input annotation. + * + * @param reference The annotation containing target class and property + * information + * @return A function, never null. + * @throws IntrospectionException If the class referenced by input + * annotation cannot be analyzed. + * @throws NoSuchMethodException If we cannot find any getter in the + * referenced class for the wanted property. + */ + public static Function acquireExtractor(final RefersTo reference) throws IntrospectionException, NoSuchMethodException { + String pName = reference.property(); + if ((pName == null || (pName = pName.trim()).isEmpty())) { + throw new IllegalArgumentException("Given reference annotation does not refer to any property !"); + } + + final Class referedType = reference.type(); + final BeanInfo beanInfo = Introspector.getBeanInfo(referedType); + Function extractor = null; + for (final PropertyDescriptor d : beanInfo.getPropertyDescriptors()) { + if (d.getName().equals(pName)) { + final Method getter = d.getReadMethod(); + if (getter != null) { + getter.setAccessible(true); + extractor = input -> { + try { + return getter.invoke(input); + } catch (ReflectiveOperationException ex) { + throw new RhomeoRuntimeException(ex); + } + }; + } + break; + } + } + + if (extractor == null) { + final Method pGetter = referedType.getMethod(pName.concat("Property")); + if (pGetter != null && ObservableValue.class.isAssignableFrom(pGetter.getReturnType())) { + pGetter.setAccessible(true); + extractor = input -> { + try { + return ((ObservableValue) pGetter.invoke(input)).getValue(); + } catch (ReflectiveOperationException ex) { + throw new RhomeoRuntimeException(ex); + } + }; + } + } + + return extractor; + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/RhomeoKmlWriter.java b/core/src/main/java/fr/cenra/rhomeo/core/util/RhomeoKmlWriter.java new file mode 100644 index 0000000..d56e6b6 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/RhomeoKmlWriter.java @@ -0,0 +1,123 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.IOException; +import java.util.List; +import javax.xml.stream.XMLStreamException; +import org.geotoolkit.data.kml.model.Extensions; +import org.geotoolkit.data.kml.model.Extensions.Names; +import org.geotoolkit.data.kml.model.KmlException; +import org.geotoolkit.data.kml.xml.KmlExtensionWriter; +import org.geotoolkit.data.kml.xsd.SimpleTypeContainer; +import org.geotoolkit.xml.StaxStreamWriter; + +/** + * Customized {@link KmlExtensionWriter} for Rhomeo site attribute data. + * + * @author Samuel Andrés (Geomatys) + */ +public class RhomeoKmlWriter extends StaxStreamWriter implements KmlExtensionWriter{ + + public static final String NAMESPACE = "http://rhomeo.cenra.fr/kml"; + + public static final String TAG_COUNTY = RhomeoCore.FT_PROPERTIES.COUNTY.name(); + public static final String TAG_TYPE = RhomeoCore.FT_PROPERTIES.TYPE.name(); + public static final String TAG_ODONATE = RhomeoCore.FT_PROPERTIES.ODONATE.name(); + public static final String TAG_ORTHOPTERE = RhomeoCore.FT_PROPERTIES.ORTHOPTERE.name(); + + public RhomeoKmlWriter(){ + super(); + } + + /** + * + * @param output + * @throws XMLStreamException + * @throws IOException + */ + @Override + public void setOutput(Object output) throws XMLStreamException, IOException{ + super.setOutput(output); + } + + private void writeStringElement(final String tag, final String content) throws XMLStreamException{ + writer.writeStartElement(NAMESPACE, tag); + writer.writeCharacters(content); + writer.writeEndElement(); + } + + @Override + public boolean canHandleComplex(final String kmlVersionUri, final Names ext, final Object contentObject) { + if(contentObject instanceof List){ + return true; + } + return false; + } + + @Override + public boolean canHandleSimple(final String kmlVersionUri, final Names ext, final String elementTag) { + if(TAG_COUNTY.equals(elementTag) + || TAG_TYPE.equals(elementTag) + || TAG_ODONATE.equals(elementTag) + || TAG_ORTHOPTERE.equals(elementTag)){ + return true; + } + return false; + } + + @Override + public void writeComplexExtensionElement(String kmlVersionUri, Extensions.Names ext, Object contentElement) + throws XMLStreamException, KmlException { + throw new UnsupportedOperationException("Does not support complex types."); + } + + @Override + public void writeSimpleExtensionElement(final String kmlVersionUri, final Extensions.Names ext, final SimpleTypeContainer contentElement) + throws XMLStreamException, KmlException { + if(TAG_COUNTY.equals(contentElement.getTagName()) + || TAG_TYPE.equals(contentElement.getTagName()) + || TAG_ODONATE.equals(contentElement.getTagName()) + || TAG_ORTHOPTERE.equals(contentElement.getTagName())){ + final Object value = contentElement.getValue(); + if(value!=null) this.writeStringElement(contentElement.getTagName(), value.toString()); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/SecretGenerator.java b/core/src/main/java/fr/cenra/rhomeo/core/util/SecretGenerator.java new file mode 100644 index 0000000..5a5d815 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/SecretGenerator.java @@ -0,0 +1,121 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Optional; +import java.util.logging.Level; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class SecretGenerator { + + private static SecretGenerator INSTANCE; + + private final Cipher cipher; + private final Base64.Encoder encoder; + private final Base64.Decoder decoder; + private final KeyGenerator keyGen; + private final SecureRandom sr; + + private SecretGenerator() throws GeneralSecurityException { + cipher = Cipher.getInstance("Blowfish/CBC/PKCS5Padding"); + keyGen = KeyGenerator.getInstance("Blowfish"); + encoder = Base64.getEncoder().withoutPadding(); + decoder = Base64.getDecoder(); + sr = new SecureRandom(); + } + + public synchronized EncryptionResult encrypt(final String toEncrypt) throws GeneralSecurityException { + keyGen.init(128); + final byte[] keyBytes = keyGen.generateKey().getEncoded(); + if (keyBytes == null) { + throw new GeneralSecurityException("No key can be generated for queried encryption."); + } + + final byte[] srBytes = new byte[cipher.getBlockSize()]; + sr.nextBytes(srBytes); + + cipher.init( + Cipher.ENCRYPT_MODE, + new SecretKeySpec(keyBytes, "Blowfish"), + new IvParameterSpec(srBytes) + ); + + return new EncryptionResult( + encoder.encodeToString(cipher.doFinal(toEncrypt.getBytes(StandardCharsets.UTF_8))), + keyBytes, + srBytes + ); + } + + public synchronized String decrypt(final String toDecrypt, final byte[] key, final byte[] rs) throws GeneralSecurityException { + cipher.init( + Cipher.DECRYPT_MODE, + new SecretKeySpec(key, "Blowfish"), + new IvParameterSpec(rs) + ); + return new String( + cipher.doFinal(decoder.decode(toDecrypt)), + StandardCharsets.UTF_8 + ); + } + + public static final Optional getInstance() { + if (INSTANCE == null) { + try { + INSTANCE = new SecretGenerator(); + } catch (GeneralSecurityException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot initialize encryption management mechanism.", e); + } + } + + return Optional.ofNullable(INSTANCE); + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/SerializableDataContext.java b/core/src/main/java/fr/cenra/rhomeo/core/util/SerializableDataContext.java new file mode 100644 index 0000000..2153f7f --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/SerializableDataContext.java @@ -0,0 +1,194 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.site.SiteRepository; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; +import javafx.collections.ObservableMap; + +/** + * A simple POJO writable using Jackson. The aim is to persist information of a + * {@link DataContext}. + * + * @author Alexis Manin (Geomatys) + */ +public class SerializableDataContext { + + /** + * Name of the target site. + */ + public String siteName; + + /** + * Person in charge of the pointed site. + */ + public String referent; + + /** + * Organisation in charge of the site management. + */ + public String organisation; + + /** + * Name of the target protocol. + */ + public String protocolName; + + /** + * ISO Zoned date and time of the creation of the context. + */ + public String date; + + /** + * Contains versions used for reference list inside application. The key is + * the canonical name of the pointed {@link Reference} class, and value is + * the string representation of the {@link Version}. + */ + public Map referenceVersions = new HashMap<>(); + + public Map userData = new HashMap<>(); + + /** + * Try to set the session data context from the current object. + * @param session + * @return True if we succeeded to set session data context according to + * current object. False if there's missing information which prevent us + * from doing so. + */ + public boolean restoreDataContext(final Session session) { + final Site site = session.getBean(SiteRepository.class).findOne(siteName); + if (site == null) + return false; + + final Optional protocolOptional = session.getProtocols().stream() + .filter(protocolCandidate -> protocolName.equals(protocolCandidate.getName())) + .findFirst(); + if (!protocolOptional.isPresent()) { + return false; + } + + final Protocol protocol = protocolOptional.get(); + session.startEdition(site, protocol); + if (referenceVersions != null && !referenceVersions.isEmpty()) { + ObservableMap, Version> refs = session.getDataContext().getReferences(); + for (final Map.Entry entry : referenceVersions.entrySet()) { + // Using protocol class loader should be good, as it is sensed to reference the reference class it use. + try { + refs.put((Class) Class.forName(entry.getKey(), true, protocol.getClass().getClassLoader()), new Version(entry.getValue())); + } catch (ClassNotFoundException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot restore a reference version preference.", e); + } + } + } + + session.getDataContext().getUserData().putAll(userData); + + return true; + } + + /** + * Converts into a {@linkplain DataContext data context}. + * + * @param session + * @return A data context representing this instance, or {@code null} if no site or protocol defined in the session. + */ + public DataContext toDataContext(final Session session) { + if (session == null) { + return null; + } + final Site site = session.getBean(SiteRepository.class).findOne(siteName); + if (site == null) + return null; + + final Optional protocolOptional = session.getProtocols().stream() + .filter(protocolCandidate -> protocolName.equals(protocolCandidate.getName())) + .findFirst(); + if (!protocolOptional.isPresent()) { + return null; + } + + final Protocol protocol = protocolOptional.get(); + final DataContext dataContext = new DataContext(site, protocol); + if (referenceVersions == null || referenceVersions.isEmpty()) { + return dataContext; + } + + ObservableMap, Version> refs = dataContext.getReferences(); + for (final Map.Entry entry : referenceVersions.entrySet()) { + // Using protocol class loader should be good, as it is sensed to reference the reference class it use. + try { + refs.put((Class) Class.forName(entry.getKey(), true, protocol.getClass().getClassLoader()), new Version(entry.getValue())); + } catch (ClassNotFoundException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot restore a reference version preference.", e); + } + } + + dataContext.getUserData().putAll(userData); + + return dataContext; + } + + public static SerializableDataContext fromDataContext(final DataContext context) { + final SerializableDataContext ctx = new SerializableDataContext(); + ctx.protocolName = context.getProtocol().getName(); + ctx.siteName = context.getSite().getName(); + ctx.referent = context.getSite().getReferent(); + ctx.organisation = context.getSite().getOrganization(); + ctx.date = context.getDate().toString(); + for (final Map.Entry, Version> entry : context.getReferences().entrySet()) { + ctx.referenceVersions.put(entry.getKey().getCanonicalName(), entry.getValue().toString()); + } + + ctx.userData.putAll(context.getUserData()); + + return ctx; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/SimplePredicateChain.java b/core/src/main/java/fr/cenra/rhomeo/core/util/SimplePredicateChain.java new file mode 100644 index 0000000..9961438 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/SimplePredicateChain.java @@ -0,0 +1,92 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.api.data.Statement; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class SimplePredicateChain implements Predicate, Externalizable { + + protected static final long serialVersionUID = 1L; + + public final Set predicates = Collections.synchronizedSet(new HashSet<>()); + + @Override + public boolean test(Statement t) { + synchronized (predicates) { + for (final Predicate p : predicates) { + if (p.test(t)) + return true; + } + } + + return false; + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + final Predicate[] toWrite; + synchronized (predicates) { + toWrite = predicates.toArray(new Predicate[predicates.size()]); + } + + out.writeObject(toWrite); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + final Object read = in.readObject(); + if (read != null && read.getClass().isArray()) + synchronized (predicates) { + predicates.addAll(Arrays.asList((Predicate[]) read)); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/TemporalConverters.java b/core/src/main/java/fr/cenra/rhomeo/core/util/TemporalConverters.java new file mode 100644 index 0000000..ec25f20 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/TemporalConverters.java @@ -0,0 +1,319 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import java.time.DateTimeException; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.util.ArrayList; +import java.util.regex.Pattern; +import org.apache.sis.util.ObjectConverter; +import org.apache.sis.util.Static; +import org.apache.sis.util.UnconvertibleObjectException; + +/** + * A set of converters to switch from {@link ChronoZonedDateTime} to various types + * (string, number). + * + * @author Alexis Manin (Geomatys) + */ +public class TemporalConverters extends Static { + + /** + * Literal value expected between temporal fields in a string. + */ + private static final char DEFAULT_SEPARATOR = ':'; + + /** + * A regex to detect separator character behind each field of a date. + */ + private static final Pattern SEPARATOR_PATTERN = Pattern.compile("(\\d+)[^\\d](\\+|-)?"); + private static final String SEPARATOR_REPLACEMENT = new StringBuilder("$1") + .append(DEFAULT_SEPARATOR).append("$2").toString(); + + /** + * A zoned date time formatter whose aim is to find a date formed as following : + * + * YYYY MM dd H(H) m(m) s(s) z(zz) (Zone) + * + * Time of the day is optional, and time precision can be from hour to milliseconds. + * The zone can be a zone id or offset, also optional (even if recommended). + */ + private static final DateTimeFormatter YEAR_FIRST_DTF =new DateTimeFormatterBuilder() + .parseLenient() + .appendPattern("yyyy:M:d[:H:m:s][:xxx][:X][:Z][:0][:VV]") + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(); + + /** + * A zoned date time formatter whose aim is to find a date formed as following : + * + * dd MM YYYY H(H) m(m) s(s) z(zz) (Zone) + * + * Time of the day is optional, and time precision can be from hour to milliseconds. + * The zone can be a zone id or offset, also optional (even if recommended). + */ + private static final DateTimeFormatter DAY_FIRST_DTF = new DateTimeFormatterBuilder() + .parseLenient() + .appendPattern("d:M:yyyy[:H:m:s][:xxx][:X][:Z][:0][:VV]") + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.OFFSET_SECONDS, 0) + .toFormatter(); + + private static final DateTimeFormatter DAY_FIRST_DF = new DateTimeFormatterBuilder() + .parseLenient() + .appendPattern("d-M-yyyy") + .toFormatter(); + + /** + * Get timestamp (number of milliseconds since the epoch, UTC) from a given + * date. + * + * @param input The zoned date to extract timestamp from. + * @return number of milliseconds between given date and the UTC epoch. + */ + public static long toTimestamp(final ChronoZonedDateTime input) { + return input.toInstant().toEpochMilli(); + } + + /** + * Create a new zoned date/time object from a given timestamp (milli from epoch, UTC). + * @param time The timestamp to read. + * @return The date object corresponding to input object. + */ + public static ZonedDateTime fromTimestamp(final long time) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneOffset.UTC); + } + + + /** + * DATE TO TIMESTAMP + */ + public static class ZonedDateTime2Long extends AOC { + + public ZonedDateTime2Long() { + super(ZonedDateTime.class, Long.class); + } + + @Override + public Long apply(ZonedDateTime s) throws UnconvertibleObjectException { + return toTimestamp(s); + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new Long2ZonedDateTime(); + } + } + + /** + * TIMESTAMP TO DATE + */ + public static class Long2ZonedDateTime extends AOC { + + public Long2ZonedDateTime() { + super(Long.class, ZonedDateTime.class); + } + + @Override + public ZonedDateTime apply(Long s) throws UnconvertibleObjectException { + return fromTimestamp(s); + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new ZonedDateTime2Long(); + } + } + + /** + * STRING TO DATE. + */ + public static class String2ZonedDateTime extends AOC { + + public String2ZonedDateTime() { + super(String.class, ZonedDateTime.class); + } + + @Override + public ZonedDateTime apply(String s) throws UnconvertibleObjectException { + // TODO : change case order ? + try { + return fromTimestamp(Long.parseLong(s)); + } catch (NumberFormatException e) { + try { + return ZonedDateTime.parse(s, DateTimeFormatter.ISO_ZONED_DATE_TIME); + } catch (DateTimeParseException e1) { + e1.addSuppressed(e); + try { + return tryFormatters( + SEPARATOR_PATTERN.matcher(s).replaceAll(SEPARATOR_REPLACEMENT), + YEAR_FIRST_DTF, DAY_FIRST_DTF + ); + } catch (DateTimeParseException e2) { + e2.addSuppressed(e1); + UnconvertibleObjectException toThrow = new UnconvertibleObjectException("No known format fit input : ".concat(s), e2); + throw toThrow; + } + } + } + } + + /** + * Try to read the given string as a zoned date and time. We iterate on + * each provided formatters until we find one able to parse data. + * + * @param toConvert A string to convert as a date. + * @param formatters A list of formatters to try for conversion. + * @return The parsed string as a {@link ZonedDateTime}. + * @throws DateTimeParseException If no provided format can parse given string. + */ + private ZonedDateTime tryFormatters(final String toConvert, final DateTimeFormatter... formatters) throws DateTimeParseException { + if (formatters == null || formatters.length < 1) { + return ZonedDateTime.parse(toConvert); + } + + final ArrayList exceptions = new ArrayList<>(); + for (final DateTimeFormatter format : formatters) { + try { + if (format != null) { + return ZonedDateTime.parse(toConvert, format); + } + } catch (DateTimeParseException e) { + exceptions.add(e); + } + } + + if (exceptions.isEmpty()) { + throw new UnconvertibleObjectException("No format provided for conversion !"); + } else { + final DateTimeParseException toThrow = exceptions.get(0); + for (int i = 1 ; i < exceptions.size() ; i++) { + toThrow.addSuppressed(exceptions.get(i)); + } + + throw toThrow; + } + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new ZonedDateTime2String(); + } + } + + public static class String2LocalDate extends AOC { + + public String2LocalDate() { + super(String.class, LocalDate.class); + } + + @Override + public LocalDate apply(String object) throws UnconvertibleObjectException { + try { + return LocalDate.parse(object, DAY_FIRST_DF); + } catch (DateTimeParseException e) { + try { + return LocalDate.parse(object); + } catch (DateTimeParseException e2) { + // Unreadable as an ISO local date. Maybe it's a date time ? + return new String2ZonedDateTime().apply(object).toLocalDate(); + } + } + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new LocalDate2String(); + } + } + + /** + * DATE TO STRING. + */ + public static class ZonedDateTime2String extends AOC { + + public ZonedDateTime2String() { + super(ChronoZonedDateTime.class, String.class); + } + + @Override + public String apply(ZonedDateTime s) throws UnconvertibleObjectException { + return String.valueOf(toTimestamp(s)); + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new String2ZonedDateTime(); + } + } + + public static class LocalDate2String extends AOC { + + public LocalDate2String() { + super(LocalDate.class, String.class); + } + + @Override + public String apply(LocalDate object) throws UnconvertibleObjectException { + try { + return object.format(DAY_FIRST_DF); + } catch (DateTimeException e) { + return object.toString(); + } + } + + @Override + public ObjectConverter inverse() throws UnsupportedOperationException { + return new String2LocalDate(); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/TrackingPointComparator.java b/core/src/main/java/fr/cenra/rhomeo/core/util/TrackingPointComparator.java new file mode 100644 index 0000000..3f3d525 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/TrackingPointComparator.java @@ -0,0 +1,69 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import java.util.Comparator; + +/** + * Sort {@link Statement} objects per year and then by tracking point name. + * Ascendent order. + * + * @author Alexis Manin (Geomatys) + */ +public class TrackingPointComparator implements Comparator { + + @Override + public int compare(TrackingPoint o1, TrackingPoint o2) { + if (o1 == null) { + return o2 == null? 0 : 1; + } else if (o2 == null) { + return -1; + } + + final int yearOrder = o1.getYear() - o2.getYear(); + if (yearOrder != 0) { + return yearOrder; + } + + return o1.getName().compareTo(o2.getName()); + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/VersionDeserializer.java b/core/src/main/java/fr/cenra/rhomeo/core/util/VersionDeserializer.java new file mode 100644 index 0000000..0fa1d74 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/VersionDeserializer.java @@ -0,0 +1,67 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import fr.cenra.rhomeo.api.Version; +import java.io.IOException; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class VersionDeserializer extends StdDeserializer { + + public VersionDeserializer() { + super(Version.class); + } + + @Override + public Version deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + final String text = jp.getText(); + if (text == null || text.isEmpty()) { + return null; + } else { + return new Version(text); + } + } +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/VersionSerializer.java b/core/src/main/java/fr/cenra/rhomeo/core/util/VersionSerializer.java new file mode 100644 index 0000000..835686a --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/VersionSerializer.java @@ -0,0 +1,65 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import fr.cenra.rhomeo.api.Version; +import java.io.IOException; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class VersionSerializer extends StdSerializer { + + public VersionSerializer() { + super(Version.class); + } + + @Override + public void serialize(Version t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonGenerationException { + if (t != null) { + jg.writeString(t.toString()); + } + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeDeserializer.java b/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeDeserializer.java new file mode 100644 index 0000000..946b9db --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeDeserializer.java @@ -0,0 +1,73 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.time.ZonedDateTime; +import org.apache.sis.util.ObjectConverter; +import org.apache.sis.util.ObjectConverters; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class ZonedDateTimeDeserializer extends StdDeserializer { + + final ObjectConverter converter; + + ZonedDateTimeDeserializer() { + super(ZonedDateTime.class); + converter = ObjectConverters.find(String.class, ZonedDateTime.class); + } + + @Override + public ZonedDateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { + final String text = jp.getText(); + if (text != null && !text.isEmpty()) { + return converter.apply(text); + } + + return null; + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeSerializer.java b/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeSerializer.java new file mode 100644 index 0000000..c5b3738 --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/util/ZonedDateTimeSerializer.java @@ -0,0 +1,66 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class ZonedDateTimeSerializer extends StdSerializer { + + ZonedDateTimeSerializer() { + super(ZonedDateTime.class); + } + + @Override + public void serialize(ZonedDateTime t, JsonGenerator jg, SerializerProvider sp) throws IOException, JsonGenerationException { + if (t != null) { + jg.writeString(t.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)); + } + } + +} diff --git a/core/src/main/java/fr/cenra/rhomeo/core/validation/ReferenceValidator.java b/core/src/main/java/fr/cenra/rhomeo/core/validation/ReferenceValidator.java new file mode 100644 index 0000000..374844a --- /dev/null +++ b/core/src/main/java/fr/cenra/rhomeo/core/validation/ReferenceValidator.java @@ -0,0 +1,154 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.validation; + +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.annotations.RefersTo; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.util.RelationResolver; +import java.util.Collections; +import java.util.Set; +import java.util.logging.Level; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableMap; +import javax.annotation.PostConstruct; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Ensure the property value is referring to an existing object. However, it + * tests NEITHER nullity NOR blankness. + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class ReferenceValidator implements ConstraintValidator { + + @Autowired + private Session session; + + @Autowired + private RelationResolver resolver; + + private Set expectedValues; + + private String propertyTitle; + + private RefersTo annot; + + private final MapChangeListener changeListener; + + private ReferenceValidator() { + changeListener = c -> { + if (annot != null && annot.type() == c.getKey()) + initialize(this.annot); + }; + } + + @PostConstruct + private void init() { + // Force refresh when edition step change. + session.workflowStepProperty().addListener((obs) -> { + if (annot != null) + initialize(annot); + }); + } + + @Override + public void initialize(RefersTo annot) { + this.annot = annot; + try { + final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(annot.type()); + expectedValues = resolver.getPossibleValues(annot); + // If possible, we try to get a readable title for referred property. + propertyTitle = annot.property(); + if (bundleAvailable) { + try { + propertyTitle = InternationalResource.getResourceString((Class) annot.type(), annot.property(), "label"); + } catch (Exception e) { + RhomeoCore.LOGGER.log( + Level.FINE, e, + () -> new StringBuilder("No bundle available for property ") + .append(annot.property()) + .append(" of type ") + .append(annot.type()).toString() + ); + } + } + } catch (Exception ex) { + RhomeoCore.LOGGER.log(Level.SEVERE, "Impossible to initialize validation capabilities for a reference !", ex); + expectedValues = Collections.EMPTY_SET; // Cannot initialize comparison point : ALWAYS BAD. + } + + // expected values could change if user modify the reference version to use. + if (session.getDataContext() != null && Reference.class.isAssignableFrom(annot.type())) { + final ObservableMap, Version> refs = session.getDataContext().getReferences(); + refs.removeListener(changeListener); + refs.addListener(changeListener); + } + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value == null || ((value instanceof String) && ((String) value).trim().isEmpty())) { + return true; // IGNORE NULLITY / BLANKNESS. Use @Blank and @Null annotations for this. + + } else if (!expectedValues.contains(value)) { + final StringBuilder b = new StringBuilder(); + if (propertyTitle != null) + b.append(propertyTitle).append(" : "); + b.append("Aucune correspondance trouvée pour la valeur ").append(value); + ConstraintValidatorContext.ConstraintViolationBuilder builder = + context.buildConstraintViolationWithTemplate(b.toString()); + builder.addConstraintViolation(); + context.disableDefaultConstraintViolation(); + + return false; + } + + return true; + } +} diff --git a/core/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter b/core/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter new file mode 100644 index 0000000..4bf723d --- /dev/null +++ b/core/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter @@ -0,0 +1,8 @@ +fr.cenra.rhomeo.core.util.TemporalConverters$ZonedDateTime2Long +fr.cenra.rhomeo.core.util.TemporalConverters$Long2ZonedDateTime +fr.cenra.rhomeo.core.util.TemporalConverters$ZonedDateTime2String +fr.cenra.rhomeo.core.util.TemporalConverters$String2ZonedDateTime +fr.cenra.rhomeo.core.util.TemporalConverters$String2LocalDate +fr.cenra.rhomeo.core.util.TemporalConverters$LocalDate2String +fr.cenra.rhomeo.core.util.GeometryConverters$GeometryToString +fr.cenra.rhomeo.core.util.GeometryConverters$StringToGeometry \ No newline at end of file diff --git a/core/src/main/resources/fr/cenra/rhomeo/api/data/TrackingPoint.properties b/core/src/main/resources/fr/cenra/rhomeo/api/data/TrackingPoint.properties new file mode 100644 index 0000000..3f31aa1 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/api/data/TrackingPoint.properties @@ -0,0 +1,2 @@ +year=Ann\u00e9e +name=Nom du point diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.dbf b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.dbf new file mode 100644 index 0000000..8169cc3 Binary files /dev/null and b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.dbf differ diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.prj b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.prj new file mode 100644 index 0000000..56757fc --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.prj @@ -0,0 +1 @@ +PROJCS["RGF93_Lambert_93",GEOGCS["GCS_RGF_1993",DATUM["D_RGF_1993",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["False_Easting",700000.0],PARAMETER["False_Northing",6600000.0],PARAMETER["Central_Meridian",3.0],PARAMETER["Standard_Parallel_1",44.0],PARAMETER["Standard_Parallel_2",49.0],PARAMETER["Latitude_Of_Origin",46.5],UNIT["Meter",1.0]] \ No newline at end of file diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shp b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shp new file mode 100644 index 0000000..272a065 Binary files /dev/null and b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shp differ diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shx b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shx new file mode 100644 index 0000000..6fe91a3 Binary files /dev/null and b/core/src/main/resources/fr/cenra/rhomeo/core/data/county/DEPARTEMENT.shx differ diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/data/site/SiteImpl.properties b/core/src/main/resources/fr/cenra/rhomeo/core/data/site/SiteImpl.properties new file mode 100644 index 0000000..fcba868 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/data/site/SiteImpl.properties @@ -0,0 +1,6 @@ +# Properties used for KML export +countyCode.kml=D\u00e9partement : {0} +zoneType.kml=Zone humide : {0} ({1}) +odonateType.kml=Zone biog\u00e9ographique pour les odonates : {0} ({1}) +orthoptereType.kml=Zone biog\u00e9ographique pour les orthopt\u00e8res : {0} ({1}) +remarks.kml=Remarques : {0} diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPKey.properties b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPKey.properties new file mode 100644 index 0000000..db379bb --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPKey.properties @@ -0,0 +1,8 @@ +ftp_url._label=URL du serveur +ftp_url._description=URL ou IP de la machine h\u00e9bergeant le service FTP +ftp_port._label=Port +ftp_port._description=Port du service FTP (par défaut 21) +ftp_user._label=Utilisateur +ftp_user._description=Non de connexion pour le service FTP +ftp_pass._label=Mot de passe +ftp_pass._description=Mot de passe pour la connexion au service de mise \u00e0 jour \ No newline at end of file diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.properties b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.properties new file mode 100644 index 0000000..2117fcc --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesImpl.properties @@ -0,0 +1,2 @@ +_label=Configuration FTP +_description=D\u00e9taille les diff\u00e9rents acc\u00e8s FTP disponibles dans l'application diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetKey.properties b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetKey.properties new file mode 100644 index 0000000..c1abb27 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetKey.properties @@ -0,0 +1,9 @@ + +update_url._label=Fichier de mise \u00e0 jour +update_url._description=Lien vers le fichier contenant les informations de mise \u00e0 jour de l'application. +update_user._label=Login +update_user._description=Login pour la connexion au service de mise \u00e0 jour +update_pass._label=Mot de passe +update_pass._description=Mot de passe pour la connexion au service de mise \u00e0 jour +wfs_url._label=URL du WFS +wfs_url._description=Service Geor\u00e9f\u00e9rentiel diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetPreferences.properties b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetPreferences.properties new file mode 100644 index 0000000..2a60d42 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/NetPreferences.properties @@ -0,0 +1,3 @@ + +_label=Pr\u00e9f\u00e9rences r\u00e9seau +_description=D\u00e9finit l'ensemble des pr\u00e9f\u00e9rences concernant les connexions \u00e0 des services distants via HTTP. diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/PassNetKey.properties b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/PassNetKey.properties new file mode 100644 index 0000000..7582084 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/PassNetKey.properties @@ -0,0 +1,2 @@ +update_pass._label=Mot de passe +update_pass._description=Mot de passe pour la connexion au service de mise \u00e0 jour diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultKey b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultKey new file mode 100644 index 0000000..45239ad --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultKey @@ -0,0 +1 @@ +¨«)%ëFn‚¸OÒ>ÁþÒ \ No newline at end of file diff --git a/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultSR b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultSR new file mode 100644 index 0000000..f316205 --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/core/preferences/net/defaultSR @@ -0,0 +1 @@ + x=væ diff --git a/core/src/main/resources/fr/cenra/rhomeo/spring/spring-context.xml b/core/src/main/resources/fr/cenra/rhomeo/spring/spring-context.xml new file mode 100644 index 0000000..817bdee --- /dev/null +++ b/core/src/main/resources/fr/cenra/rhomeo/spring/spring-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/core/src/test/java/fr/cenra/rhomeo/RhomeoTestCase.java b/core/src/test/java/fr/cenra/rhomeo/RhomeoTestCase.java new file mode 100644 index 0000000..f513b4c --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/RhomeoTestCase.java @@ -0,0 +1,85 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo; + +import fr.cenra.rhomeo.core.RhomeoCore; +import org.apache.sis.test.DependsOnMethod; +import org.apache.sis.test.TestCase; +import org.apache.sis.test.TestRunner; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Base test class for Rhomeo project. Allow testing in Spring / Hibernate-validator + * environment. Also allow for use of Apache SIS test utilities, as {@link DependsOnMethod} + * annotation. + * + * TODO : Merge {@link SpringJUnit4ClassRunner} and {@link TestRunner} to get the + * best of both worlds. + * + * @author Alexis Manin (Geomatys) + */ +public abstract class RhomeoTestCase extends TestCase { + static { + System.setProperty(RhomeoCore.ENV_RHOMEO_PATH_KEY, "rhomeo-tests"); + } + + protected static ClassPathXmlApplicationContext APP_CTX; + + @BeforeClass + public static void initCtx() throws Exception { + RhomeoCore.initEpsgDB(false); + APP_CTX = new ClassPathXmlApplicationContext(RhomeoCore.SPRING_CONTEXT_XML); + } + + @AfterClass + public static void closeCtx() { + if (APP_CTX != null) { + APP_CTX.close(); + } + } + + @Before + public void injectDependencies() { + APP_CTX.getBeanFactory().autowireBean(this); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalDescriptionTest.java b/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalDescriptionTest.java new file mode 100644 index 0000000..0cf020a --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalDescriptionTest.java @@ -0,0 +1,72 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.international; + +import fr.cenra.rhomeo.api.InternationalDescription; +import java.util.Locale; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +public class InternationalDescriptionTest { + + private static class InternationalDescriptionImpl implements InternationalDescription { + } + + @Test + public void testInternationalDescription() { + + final InternationalDescription objet = new InternationalDescriptionImpl(); + + // Default locale + Assert.assertEquals("titre en français", objet.getLabel()); + Assert.assertEquals("description en français", objet.getDescription()); + + // Unknown locale => default + Assert.assertEquals("titre en français", objet.getLabel(Locale.ITALY)); + Assert.assertEquals("description en français", objet.getDescription(Locale.ITALY)); + + // Other existing locale + Assert.assertEquals("title in english", objet.getLabel(Locale.ENGLISH)); + Assert.assertEquals("description in english", objet.getDescription(Locale.ENGLISH)); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalResourceTest.java b/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalResourceTest.java new file mode 100644 index 0000000..54d0f9d --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/api/international/InternationalResourceTest.java @@ -0,0 +1,103 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.api.international; + +import fr.cenra.rhomeo.api.InternationalResource; +import java.util.Locale; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +public class InternationalResourceTest { + + private static class InternationalResourceImpl implements InternationalResource { + private final String field1 = "FIELD 1"; + private final int field2 = 5; + } + + @Test + public void testInternationalResource() { + + final InternationalResource objet = new InternationalResourceImpl(); + + // Default locale + Assert.assertEquals("champ 1", objet.getResourceString("field1")); + Assert.assertEquals("champ 2", objet.getResourceString("field2")); + Assert.assertEquals("titre du champ 1", objet.getResourceString("field1._label")); + Assert.assertEquals("titre du champ 2", objet.getResourceString("field2._label")); + Assert.assertEquals("titre du champ 1", objet.getResourceString("field1", "_label")); + Assert.assertEquals("titre du champ 2", objet.getResourceString("field2", "_label")); + Assert.assertEquals("description du champ 1", objet.getResourceString("field1", "_description")); + Assert.assertEquals("description du champ 2", objet.getResourceString("field2", "_description")); + + // Unknown locale => default + Assert.assertEquals("champ 1", objet.getResourceString(Locale.ITALY, "field1")); + Assert.assertEquals("champ 2", objet.getResourceString(Locale.ITALY, "field2")); + Assert.assertEquals("titre du champ 1", objet.getResourceString(Locale.ITALY, "field1._label")); + Assert.assertEquals("titre du champ 2", objet.getResourceString(Locale.ITALY, "field2._label")); + Assert.assertEquals("titre du champ 1", objet.getResourceString(Locale.ITALY, "field1", "_label")); + Assert.assertEquals("titre du champ 2", objet.getResourceString(Locale.ITALY, "field2", "_label")); + Assert.assertEquals("description du champ 1", objet.getResourceString(Locale.ITALY, "field1", "_description")); + Assert.assertEquals("description du champ 2", objet.getResourceString(Locale.ITALY, "field2", "_description")); + + // Other existing locale + Assert.assertEquals("field 1", objet.getResourceString(Locale.ENGLISH, "field1")); + Assert.assertEquals("field 2", objet.getResourceString(Locale.ENGLISH, "field2")); + Assert.assertEquals("title of field 1", objet.getResourceString(Locale.ENGLISH, "field1._label")); + Assert.assertEquals("title of field 2", objet.getResourceString(Locale.ENGLISH, "field2._label")); + Assert.assertEquals("title of field 1", objet.getResourceString(Locale.ENGLISH, "field1", "_label")); + Assert.assertEquals("title of field 2", objet.getResourceString(Locale.ENGLISH, "field2", "_label")); + Assert.assertEquals("description of field 1", objet.getResourceString(Locale.ENGLISH, "field1", "_description")); + Assert.assertEquals("description of field 2", objet.getResourceString(Locale.ENGLISH, "field2", "_description")); + + + Assert.assertEquals("Other field", objet.getResourceString(Locale.ENGLISH, "other")); + Assert.assertEquals("Autre champ", objet.getResourceString("other")); + + Assert.assertEquals("Hello Toto and good bye Titi!", objet.getFormatedResourceString(Locale.ENGLISH, "withParameters", "Toto", "Titi")); + Assert.assertEquals("Bonjour Toto et au revoir Titi !", objet.getFormatedResourceString("withParameters", "Toto", "Titi")); + + Assert.assertEquals("Bonjour Toto et au revoir {1} !", objet.getFormatedResourceString("withParameters", "Toto")); + Assert.assertEquals("Bonjour {0} et au revoir {1} !", objet.getFormatedResourceString("withParameters")); + Assert.assertEquals("Bonjour {0} et au revoir {1} !", objet.getResourceString("withParameters")); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/CSVMappingTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/CSVMappingTest.java new file mode 100644 index 0000000..517f441 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/CSVMappingTest.java @@ -0,0 +1,199 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.CSVEncoder; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.sis.test.DependsOnMethod; +import org.apache.sis.test.TestCase; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.util.collection.CloseableIterator; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class CSVMappingTest extends TestCase { + + @Test + public void encodeStringWithSeparatorTest() throws Exception { + final MockReference ref = new MockReference(); + ref.setFirst("lalaleigni; apegnpgbpa,;\"n ebo\"; bfozb\";\""); + ref.setFifth(Float.NaN); + ref.setSecond(ZonedDateTime.now()); + + final Path tmpFile = Files.createTempFile("stringWithSeparator", ".csv"); + try { + new CSVEncoder(tmpFile, MockReference.class).encode(Collections.singleton(ref), true); + final List decoded = new CSVDecoder(tmpFile, MockReference.class).decode(); + Assert.assertEquals("Decoded file should contain one unique element !", 1, decoded.size()); + Assert.assertEquals("Decoded object is not equal to the original one !", ref, decoded.get(0)); + } finally { + Files.delete(tmpFile); + } + } + + /** + * Create randomly filled instances of {@link MockReference}, then write them + * in a CSV file. We then decode lazily, and reencode it immediately in another + * file. + * + * To check procedure validity, we compare md5 hash of the two files written. + * + * @throws Exception + */ + @DependsOnMethod("encodeStringWithSeparatorTest") + @Test + public void readWriteReferenceTest() throws Exception { + // Check written content + final MessageDigest digest = MessageDigest.getInstance("md5"); + + final Path firstPass = Files.createTempFile("referenceIO", ".csv"); + final Path secondPass = Files.createTempFile("referenceIO", ".csv"); + try { + final int count = (int) 1e2; + new CSVEncoder(firstPass, MockReference.class).encode(new RefCreator(count), false); + final byte[] md5 = computeDigest(digest, firstPass); + + final CSVDecoder decoder = new CSVDecoder(firstPass, MockReference.class); + try (final CloseableIterator decoded = decoder.decodeLazy()) { + new CSVEncoder(secondPass, MockReference.class).encode(decoded, true); + } + + Assert.assertArrayEquals("Encoded md5 is not equal to read then re-encoded data file !", md5, computeDigest(digest, secondPass)); + + } finally { + Files.delete(firstPass); + Files.delete(secondPass); + } + } + + private static byte[] computeDigest(final MessageDigest digest, final Path in) throws IOException { + final byte[] buf = new byte[8192]; + int read = 0; + try (final InputStream stream = Files.newInputStream(in)) { + while ((read = stream.read(buf)) >= 0) { + digest.update(buf, 0, read); + } + } + + return digest.digest(); + } + + /** + * An iterator creating randomly filled objects as browsed. + */ + private static class RefCreator implements Iterator { + + private final int total; + private final AtomicInteger count = new AtomicInteger(); + + private MockReference next; + + private final Random random; + + public RefCreator(final int count) { + ArgumentChecks.ensureStrictlyPositive("Number of references to create", count); + this.total = count; + + random = new Random(); + } + + @Override + public synchronized boolean hasNext() { + if (next == null && count.getAndIncrement() >= total) { + return false; + } else if (next == null) { + next = createReference(); + } + + return true; + } + + @Override + public synchronized MockReference next() { + if (hasNext()) { + try { + return next; + } finally { + next = null; + } + } else { + throw new IllegalStateException("No more element available !"); + } + } + + private MockReference createReference() { + MockReference result = new MockReference(); + + // Insert a random character sequence, or let a null value. + if (random.nextBoolean()) { + final StringBuilder b = new StringBuilder(); + final byte[] bytes = new byte[random.nextInt(128)]; + result.setFirst(new String(bytes, StandardCharsets.UTF_8)); + } + + if (random.nextBoolean()) { + result.setSecond(ZonedDateTime.now()); + } + + result.setThird(random.nextDouble() * 1e6); + + result.setFourth(random.nextBoolean()); + + result.setFifth(random.nextBoolean()? Float.NaN : random.nextFloat()); + + return result; + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/DatasetTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/DatasetTest.java new file mode 100644 index 0000000..0428719 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/DatasetTest.java @@ -0,0 +1,111 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.RhomeoTestCase; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Random; +import javafx.collections.ObservableList; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class DatasetTest extends RhomeoTestCase { + + @Autowired + @Qualifier(MockProtocol.NAME) + Protocol protocol; + + @Test + public void checkTrackingPoints() { + final Dataset dataset = new Dataset(protocol); + final MockStatement first = new MockStatement(); + dataset.getItems().add(first); + final ObservableList tPoints = dataset.getTrackingPoints(); + Assert.assertTrue("No tracking point should exist", tPoints.isEmpty()); + + final LocalDate pointDate = LocalDate.now(); + final String pointName = "lolo"; + first.setDate(pointDate); + first.setTrackingPoint(pointName); + Assert.assertTrue("Tracking point list should contain one unique element", tPoints.size() == 1); + Assert.assertEquals("Tracking point name is invalid !", pointName, tPoints.get(0).getName()); + Assert.assertEquals("Tracking point date is invalid !", pointDate.getYear(), tPoints.get(0).getYear()); + + final MockStatement second = new MockStatement("secondPoint", LocalDate.now()); + dataset.getItems().add(second); + Assert.assertTrue("Tracking point list should contain two elements", tPoints.size() == 2); + Assert.assertEquals("Tracking point name is invalid !", second.getTrackingPoint(), tPoints.get(1).getName()); + Assert.assertEquals("Tracking point date is invalid !", second.getDate().getYear(), tPoints.get(1).getYear()); + + second.setTrackingPoint("another !"); + Assert.assertTrue("Tracking point list should contain two elements", tPoints.size() == 2); + Assert.assertEquals("Tracking point name is invalid !", second.getTrackingPoint(), tPoints.get(1).getName()); + Assert.assertEquals("Tracking point date is invalid !", second.getDate().getYear(), tPoints.get(1).getYear()); + + dataset.getItems().remove(first); + Assert.assertTrue("Tracking point list should contain one unique element", tPoints.size() == 1); + Assert.assertEquals("Tracking point name is invalid !", second.getTrackingPoint(), tPoints.get(0).getName()); + Assert.assertEquals("Tracking point date is invalid !", second.getDate().getYear(), tPoints.get(0).getYear()); + } + + @Test + public void chargeTest() { + final String points[] = new String[]{"a", "B", "obzo", "ff", "papa", "tata", "titi", "tete", "lala", "lolo", "lele"}; + final Random rand = new Random(); + final Dataset d = new Dataset(protocol); + final ObservableList tPoints = d.getTrackingPoints(); + for (int i = 0 ; i < 4000 ; i++) { + d.getItems().add(new MockStatement(points[rand.nextInt(points.length -1)], LocalDate.now().minusYears(rand.nextInt(10)))); + } + + Assert.assertFalse("Tracking point list should contain data !", tPoints.isEmpty()); + final HashSet uniquePoints = new HashSet(tPoints); + Assert.assertEquals("Uniqueness broken !", uniquePoints.size(), tPoints.size()); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/MockIndicator.java b/core/src/test/java/fr/cenra/rhomeo/core/data/MockIndicator.java new file mode 100644 index 0000000..bd5a308 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/MockIndicator.java @@ -0,0 +1,90 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.EditableIdentifiedObject; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.Process; +import fr.cenra.rhomeo.api.process.ProcessContext; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Qualifier(MockIndicator.NAME) +public class MockIndicator extends EditableIdentifiedObject implements Indicator { + + public static final String NAME = "MockIndicator"; + + @Autowired + @Qualifier(MockProtocol.NAME) + Protocol protocol; + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public Set createProcesses(ProcessContext ctx) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String getDefaultIndex() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public Set getZoneTypeCodes() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public int compareTo(Indicator o) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/MockProtocol.java b/core/src/test/java/fr/cenra/rhomeo/core/data/MockProtocol.java new file mode 100644 index 0000000..d3a3c8c --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/MockProtocol.java @@ -0,0 +1,81 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.EditableIdentifiedObject; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.data.Statement; +import java.util.Collections; +import java.util.Set; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Qualifier(MockProtocol.NAME) +public class MockProtocol extends EditableIdentifiedObject implements Protocol { + + public static final String NAME = "MockProtocol"; + + @Override + public Class getDataType() { + return MockStatement.class; + } + + @Override + public Set getReferenceTypes() { + return Collections.EMPTY_SET; + } + + @Override + public boolean isCompatible(Site site) { + return true; + } + + @Override + public int compareTo(Protocol o) { + return 0; + } + +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/MockReference.java b/core/src/test/java/fr/cenra/rhomeo/core/data/MockReference.java new file mode 100644 index 0000000..dfcb1ef --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/MockReference.java @@ -0,0 +1,158 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.data.Reference; +import java.time.ZonedDateTime; +import java.util.Objects; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class MockReference implements Reference { + + public final SimpleStringProperty first = new SimpleStringProperty(); + public final SimpleObjectProperty second = new SimpleObjectProperty<>(); + public final SimpleDoubleProperty third = new SimpleDoubleProperty(); + public final SimpleBooleanProperty fourth = new SimpleBooleanProperty(); + + @Override + public int hashCode() { + int hash = 7; + hash = 89 * hash + Objects.hashCode(this.first.get()); + hash = 89 * hash + Objects.hashCode(this.second.get()); + hash = 89 * hash + Objects.hashCode(this.third.get()); + hash = 89 * hash + Objects.hashCode(this.fourth.get()); + hash = 89 * hash + Objects.hashCode(this.fifth.get()); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final MockReference other = (MockReference) obj; + if (!Objects.equals(this.first.get(), other.first.get())) + return false; + if (!(this.second.get() == null)? other.second == null : this.second.get().isEqual(other.second.get())) + return false; + if (!Objects.equals(this.third.get(), other.third.get())) + return false; + if (!Objects.equals(this.fourth.get(), other.fourth.get())) + return false; + if (!Objects.equals(this.fifth.get(), other.fifth.get())) + return false; + return true; + } + public final SimpleObjectProperty fifth = new SimpleObjectProperty<>(); + + public String getFirst() { + return first.get(); + } + + public void setFirst(final String newValue) { + first.set(newValue); + } + + public StringProperty firstProperty() { + return first; + } + + + public ZonedDateTime getSecond() { + return second.get(); + } + + public void setSecond(final ZonedDateTime newValue) { + second.set(newValue); + } + + public ObjectProperty secondProperty() { + return second; + } + + + public double getThird() { + return third.get(); + } + + public void setThird(final double newValue) { + third.set(newValue); + } + + public DoubleProperty thirdProperty() { + return third; + } + + + public boolean getFourth() { + return fourth.get(); + } + + public void setFourth(final boolean newValue) { + fourth.set(newValue); + } + + public BooleanProperty fourthProperty() { + return fourth; + } + + public Float getFifth() { + return fifth.get(); + } + + public void setFifth(final Float newValue) { + fifth.set(newValue); + } + + public ObjectProperty fifthProperty() { + return fifth; + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/MockReferenceDescription.java b/core/src/test/java/fr/cenra/rhomeo/core/data/MockReferenceDescription.java new file mode 100644 index 0000000..ca27252 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/MockReferenceDescription.java @@ -0,0 +1,73 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import java.util.Collection; +import java.util.Collections; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class MockReferenceDescription implements ReferenceDescription { + + @Override + public Class getReferenceType() { + return MockReference.class; + } + + @Override + public String getName() { + return "MockReference"; + } + + @Override + public Collection getAlias() { + return Collections.singleton("Reference type for tests."); + } + + @Override + public String getRemarks() { + return "A reference created for test purpose."; + } + +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/MockStatement.java b/core/src/test/java/fr/cenra/rhomeo/core/data/MockStatement.java new file mode 100644 index 0000000..4c2172d --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/MockStatement.java @@ -0,0 +1,82 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.api.data.Statement; +import java.time.LocalDate; +import java.util.Objects; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class MockStatement extends Statement { + + public MockStatement() {} + + public MockStatement(final String tName, final LocalDate date) { + super(tName, date); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || ! obj.getClass().equals(this.getClass())) { + return false; + } + + final MockStatement other = (MockStatement) obj; + return Objects.equals(date.get(), other.date.get()) && Objects.equals(trackingPoint.get(), other.trackingPoint.get()); + } + + @Override + public int hashCode() { + int hash = 0; + final String point = trackingPoint.get(); + if (point != null) { + hash += point.hashCode(); + } + + final LocalDate date = this.date.get(); + if (date != null) { + hash += 31*date.hashCode(); + } + + return hash; + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/ReferenceManagerTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/ReferenceManagerTest.java new file mode 100644 index 0000000..75e328a --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/ReferenceManagerTest.java @@ -0,0 +1,163 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import fr.cenra.rhomeo.RhomeoTestCase; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.preferences.ftp.FTPPreferences; + +import java.beans.IntrospectionException; +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.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javafx.concurrent.Task; +import javafx.embed.swing.JFXPanel; +import org.apache.commons.net.ftp.FTPClient; +import org.geotoolkit.nio.IOUtilities; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class ReferenceManagerTest extends RhomeoTestCase { + + @Autowired + FTPPreferences prefs; + + @Autowired + MockReferenceDescription desc; + + @Test + public void testLists() throws Exception { + new JFXPanel().setVisible(false);// necessary to init fx toolkit + final FTPClient ftpClient = prefs.getAccess(FTPPreferences.ACCESS_POINTS.REFERENCES).createClient(); + + ftpClient.makeDirectory(desc.getName()); + final String pathname = Paths.get(desc.getName()).resolve("1.0.1.csv").toString(); + + // Create a new version on distant repository. + final List refs; + final Path tmpFile = Files.createTempFile("tmpVersion", ".csv"); + try { + refs = createMockList(tmpFile); + try (final InputStream stream = Files.newInputStream(tmpFile)) { + ftpClient.storeFile(pathname, stream); + } + } finally { + Files.delete(tmpFile); + } + + // Clear local test data. + IOUtilities.deleteRecursively(RhomeoCore.REFERENCE_PATH.resolve(desc.getName())); + + ReferenceManager manager = ReferenceManager.getOrCreate(desc); + Task t = manager.refresh(); + t.run(); + t.get(); + + final Set distantVersions = manager.getDistantVersions(); + Assert.assertNotNull("Distant version list", distantVersions); + Assert.assertFalse("Distant version list should not be empty.", distantVersions.isEmpty()); + + final Set localVersions = manager.getInstalledVersions(); + Assert.assertNotNull("List of installed versions", localVersions); + Assert.assertTrue("List of installed versions should be empty", localVersions.isEmpty()); + + // Install found version + final Version newVersion = distantVersions.iterator().next(); + t = manager.install(newVersion); + t.run(); + t.get(); + Assert.assertTrue("installed version set should be updated on new version install.", localVersions.contains(newVersion)); + + // Read data into downloaded version + final List values = manager.getValues(newVersion); + Assert.assertEquals("Loaded values are different from created ones !", refs, values); + + // Remove newly installed + t = manager.uninstall(newVersion); + t.run(); + t.get(); + Assert.assertFalse("Local version set should be updated when a version is deleted.", localVersions.contains(newVersion)); + + // delete distant version + ftpClient.deleteFile(pathname); + t = manager.refresh(); + t.run(); + t.get(); + Assert.assertFalse("Distant version list has not been updated", distantVersions.contains(newVersion)); + } + + private static List createMockList(final Path toWriteInto) throws IOException, IntrospectionException { + final CSVEncoder encoder = new CSVEncoder<>(toWriteInto, MockReference.class); + + final ArrayList list = new ArrayList<>(); + MockReference ref = new MockReference(); + ref.setFirst("toto"); + ref.setSecond(ZonedDateTime.now()); + ref.setThird(0.13); + ref.setFourth(true); + ref.setFifth(Float.NaN); + list.add(ref); + + ref = new MockReference(); + ref.setFirst("tata"); + ref.setSecond(ZonedDateTime.now()); + ref.setThird(100.2); + ref.setFourth(true); + ref.setFifth(9.3f); + list.add(ref); + + encoder.encode(list, true); + + return Collections.unmodifiableList(list); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/UpdateInfoTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/UpdateInfoTest.java new file mode 100644 index 0000000..e6745d8 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/UpdateInfoTest.java @@ -0,0 +1,81 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data; + +import com.fasterxml.jackson.databind.ObjectMapper; +import fr.cenra.rhomeo.api.Version; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.ZonedDateTime; + +import static org.junit.Assert.*; + +/** + * Verify that the update json file on the server can be read properly. + */ +public class UpdateInfoTest { + /** + * Read json update file and ensures its content is conformed and readable. + * + * @throws IOException + */ + @Test + public void deserializeTest() throws IOException { + try (final InputStream is = this.getClass().getResourceAsStream("update.json")) { + final UpdateInfo update = new ObjectMapper().readValue(is, UpdateInfo.class); + assertNotNull(update); + assertEquals(new Version("0.1"), update.getVersion()); + assertEquals(ZonedDateTime.parse("2016-04-29T15:00:00+01:00"), update.getDate()); + assertNotNull(update.getReleaseNote()); + assertEquals(1, update.getReleaseNote().length); + assertEquals("Première version de l'application permettant la gestion de sites", update.getReleaseNote()[0]); + assertEquals(new URL("http://testwin32").toExternalForm(), update.getWin32().toExternalForm()); + assertEquals("testwin32MD5", update.getWin32MD5()); + assertEquals(new URL("http://testdeb64").toExternalForm(), update.getDeb64().toExternalForm()); + assertEquals("testdeb64MD5", update.getDeb64MD5()); + assertEquals(new URL("http://testrpm64").toExternalForm(), update.getRpm64().toExternalForm()); + assertEquals("testrpm64MD5", update.getRpm64MD5()); + assertEquals(new URL("http://testmacOS64").toExternalForm(), update.getMacOS64().toExternalForm()); + assertEquals("testmacOS64MD5", update.getMacOS64MD5()); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/county/CountyTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/county/CountyTest.java new file mode 100644 index 0000000..764c54d --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/county/CountyTest.java @@ -0,0 +1,79 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.county; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.IOException; +import java.util.Map; +import org.geotoolkit.lang.Setup; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.opengis.util.FactoryException; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class CountyTest { + + @BeforeClass + public static void initGeotk() throws FactoryException, IOException { + RhomeoCore.initEpsgDB(false); + Setup.initialize(null); + } + + /** + * Check some county codes we should find into read file. + */ + private static final String[] COUNTY_CODES = new String[] { + "38", "69", "01", "34", "26", "07" + }; + + @Test + public void testLoading() throws Exception { + final Map counties = new CountyRepository().getAll(); + Assert.assertNotNull("County list", counties); + Assert.assertFalse("County list should not be empty", counties.isEmpty()); + + for (final String code : COUNTY_CODES) { + Assert.assertNotNull("A county is missing : ".concat(code), counties.get(code)); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryTest.java b/core/src/test/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryTest.java new file mode 100644 index 0000000..153da37 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/data/site/SiteRepositoryTest.java @@ -0,0 +1,191 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.data.site; + +import fr.cenra.rhomeo.core.data.site.SiteRepository; +import fr.cenra.rhomeo.core.data.site.DuplicatedKeyException; +import fr.cenra.rhomeo.core.data.site.SiteRepositoryImpl; +import fr.cenra.rhomeo.RhomeoTestCase; +import fr.cenra.rhomeo.api.IdentifiedObject; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.data.site.SiteImpl; +import javafx.collections.ObservableList; +import org.apache.sis.storage.DataStoreException; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.junit.Test; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import org.apache.sis.test.DependsOnMethod; +import org.geotoolkit.nio.IOUtilities; +import org.junit.AfterClass; + +import static org.junit.Assert.*; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * Test reading / writing methods using the {@link SiteRepository} API. + * + * @author Cédric Briançon (Geomatys) + */ +public class SiteRepositoryTest extends RhomeoTestCase { + + /** + * Mock SHP in the resources. + */ + public static final String RESOURCES_DATA_SHP = "sites.shp"; + + @Autowired + SiteRepository repo; + + /** + * Remove tests path for sites SHP. + * + * @throws IOException + */ + @AfterClass + public static void finalization() throws IOException { + IOUtilities.deleteRecursively(RhomeoCore.SITES_PATH); + } + + /** + * Verify method {@link SiteRepository#findAll()}. + */ + @Test + public void findAllTest() { + final ObservableList sites = repo.findNames(); + assertNotNull(sites); + assertTrue(sites.isEmpty()); + } + + /** + * Verify method {@link SiteRepository#create(IdentifiedObject)} and {@link SiteRepository#delete(String)}. + * + * @throws MalformedURLException + * @throws DataStoreException + * @throws DuplicatedKeyException + */ + @DependsOnMethod("findAllTest") + @Test + public void createDeleteTest() throws MalformedURLException, DataStoreException, DuplicatedKeyException, URISyntaxException { + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(this.getClass().getResource(RESOURCES_DATA_SHP).toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + assertTrue(reader.hasNext()); + + final Site newSite = SiteRepositoryImpl.toSite(reader.next()); + repo.create(newSite); + + final ObservableList allSites = repo.findNames(); + assertNotNull(allSites); + assertEquals(1, allSites.size()); + + repo.delete(newSite.getName()); + assertTrue(allSites.isEmpty()); + } + } + + + /** + * Ensures {@link SiteRepository#findOne(String)} works fine. + * + * @throws IOException + * @throws DataStoreException + */ + @DependsOnMethod("createDeleteTest") + @Test + public void getSiteTest() throws Exception { + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(this.getClass().getResource(RESOURCES_DATA_SHP).toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + final Site newSite = SiteRepositoryImpl.toSite(reader.next()); + repo.create(newSite); + + final Site addedSite = repo.findOne(newSite.getName()); + assertNotNull(addedSite); + assertEquals(newSite, addedSite); + assertEquals(newSite.getCountyCode(), addedSite.getCountyCode()); + + repo.delete(newSite.getName()); + } + } + + /** + * Verify method {@link SiteRepository#update(IdentifiedObject)}}. + * + * @throws MalformedURLException + * @throws DataStoreException + * @throws DuplicatedKeyException + */ + @DependsOnMethod("getSiteTest") + @Test + public void updateTest() throws MalformedURLException, DataStoreException, DuplicatedKeyException, URISyntaxException { + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(this.getClass().getResource(RESOURCES_DATA_SHP).toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + final Site newSite = SiteRepositoryImpl.toSite(reader.next()); + repo.create(newSite); + + final String testOrtho = "test orthoptere"; + ((SiteImpl)newSite).setOrthoptereType(testOrtho); + repo.update(newSite); + + final Site updatedSite = repo.findOne(newSite.getName()); + assertNotNull(updatedSite.getOrthoptereType()); + assertEquals(testOrtho, updatedSite.getOrthoptereType()); + assertEquals(newSite.getOrthoptereType(), updatedSite.getOrthoptereType()); + + ((SiteImpl)updatedSite).setName("myNewName"); + repo.create(updatedSite); + + try { + repo.update(updatedSite, newSite.getName()); + } catch (DuplicatedKeyException e) { + // expected behavior + } + + repo.delete(newSite.getName()); + repo.delete(updatedSite.getName()); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesTest.java b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesTest.java new file mode 100644 index 0000000..e999169 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/FTPPreferencesTest.java @@ -0,0 +1,128 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import fr.cenra.rhomeo.RhomeoTestCase; +import java.security.GeneralSecurityException; +import org.apache.sis.test.DependsOnMethod; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +/** + * Test {@link FTPPreferencesImpl} component, by trying to get and change information + * about an FTP access. + * + * TODO : test {@link FTPAccess#createClient() } + * + * @author Alexis Manin (Geomatys) + */ +public class FTPPreferencesTest extends RhomeoTestCase { + + @Autowired + @Qualifier(FTPPreferencesImpl.NAME) + FTPPreferences prefs; + + /** + * Test that methods which should not be used by user throw exception or expected empty value. + */ + @Test + public void testForbiddenMethods() { + Assert.assertTrue("Key collection should be empty !" , prefs.getKeys().isEmpty()); + + try { + prefs.getPreference("whatever"); + Assert.fail("getProperty() method should not be usable !"); + } catch (UnsupportedOperationException e) { + // Expected + } + + try { + prefs.setPreference("whatever", "blabla"); + Assert.fail("setProperty() method should not be usable !"); + } catch (UnsupportedOperationException e) { + // Expected + } + } + + @DependsOnMethod("testForbiddenMethods") + @Test + public void testAccessPoint() throws Exception { + final FTPAccess access = prefs.getAccess(FTPPreferences.ACCESS_POINTS.RESULTS); + Assert.assertNotNull("No access point found !", access); + + final String expectedAd = "myAdress"; + final String expectedLogin = "toto"; + final String expectedWorkDir = "/my/work/dir"; + assertChanges(access, expectedAd, expectedLogin, null, expectedWorkDir); + + assertChanges(access, "newAdress", "newLogin", "newPassword!£²345*ù%", "newWorkDir"); + } + + public static void assertChanges(final FTPAccess access, final String adress, final String login, final String password, final String workDir) throws GeneralSecurityException { + // Keep reference to access previous values to rollback user configuration after the test. + final String oldAdress = access.getAdress(); + final String oldLogin = access.getLogin(); + final String oldPw = access.getPassword(); + final String oldDir = access.getWorkingDir(); + + try { + access.setAdress(adress); + access.setLogin(login); + access.setPassword(password); + access.setWorkingDir(workDir); + + Assert.assertEquals("Adress", adress, access.getAdress()); + Assert.assertEquals("Login", login, access.getLogin()); + Assert.assertEquals("Working directory", workDir, access.getWorkingDir()); + if (password == null) { + Assert.assertTrue("Password should be empty", access.getPassword().isEmpty()); + } else { + Assert.assertEquals("Password", password, access.getPassword()); + } + } finally { + // Rollback to initial values. + access.setAdress(oldAdress); + access.setLogin(oldLogin); + access.setPassword(oldPw); + access.setWorkingDir(oldDir); + } + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPClient.java b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPClient.java new file mode 100644 index 0000000..4422902 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPClient.java @@ -0,0 +1,234 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPFileFilter; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class MockFTPClient extends FTPClient { + + private final Path root; + private Path workDir; + + public MockFTPClient(final Path root) throws IOException { + if (!Files.isDirectory(root)) { + throw new IllegalArgumentException("Input path is not a folder !"); + } + + this.root = root; + workDir = this.root; + } + + private Path getPath(final String pathname) { + if (pathname == null) + return workDir; + Path tmp = Paths.get(pathname); + if (!tmp.isAbsolute()) { + tmp = workDir.resolve(tmp); + } + + if (!tmp.startsWith(root)) { + throw new IllegalArgumentException("Given path does not represents a file of this FTP."); + } + + return tmp; + } + + @Override + public boolean makeDirectory(String pathname) throws IOException { + Files.createDirectories(getPath(pathname)); + return true; + } + + @Override + public boolean changeToParentDirectory() throws IOException { + if (workDir.equals(root)) + return false; + workDir = workDir.getParent(); + return true; + } + + @Override + public boolean changeWorkingDirectory(String pathname) throws IOException { + final Path tmpPath = getPath(pathname); + if (Files.isDirectory(tmpPath)) { + workDir = tmpPath; + return true; + } + + return false; + } + + @Override + public FTPFile[] listFiles() throws IOException { + return listFiles(null, null); + } + + @Override + public FTPFile[] listFiles(String pathname) throws IOException { + return listFiles(pathname, null); + } + + @Override + public FTPFile[] listFiles(String pathname, FTPFileFilter filter) throws IOException { + final Path toList = getPath(pathname); + + final ArrayList files = new ArrayList<>(); + Files.walkFileTree(toList, Collections.EMPTY_SET, 1, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + final FTPFile ftpFile = convert(file); + if (filter == null || filter.accept(ftpFile)) { + files.add(ftpFile); + } + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!dir.equals(toList)) { + final FTPFile ftpFile = convert(dir); + if (filter == null || filter.accept(ftpFile)) { + files.add(ftpFile); + } + } + return super.preVisitDirectory(dir, attrs); + } + }); + + return files.toArray(new FTPFile[files.size()]); + } + + @Override + public boolean deleteFile(String pathname) throws IOException { + return Files.deleteIfExists(getPath(pathname)); + } + + @Override + public OutputStream storeFileStream(String remote) throws IOException { + return Files.newOutputStream(getPath(remote)); + } + + @Override + public boolean storeFile(String remote, InputStream local) throws IOException { + return Files.copy(local, getPath(remote)) > 0; + } + + @Override + public InputStream retrieveFileStream(String remote) throws IOException { + return Files.newInputStream(getPath(remote)); + } + + @Override + public boolean retrieveFile(String remote, OutputStream local) throws IOException { + return Files.copy(getPath(remote), local) > 0; + } + + @Override + public OutputStream appendFileStream(String remote) throws IOException { + return Files.newOutputStream(getPath(remote), StandardOpenOption.CREATE, StandardOpenOption.APPEND); + } + + @Override + public boolean appendFile(String remote, InputStream local) throws IOException { + final byte[] buffer = new byte[8192]; + int total=0; + try (final OutputStream out = appendFileStream(remote)) { + int read = 0; + while ((read = local.read(buffer)) >= 0) { + out.write(buffer, 0, read); + total += read; + } + } + + return total > 0; + } + + /** + * Convert a path into {@link FTPFile}. + * @param input Path to see as {@link FTPFile} + * @return A view of given path through {@link FTPFile} API. + * @throws IOException If we cannot read attributes of input file. + */ + private static FTPFile convert(final Path input) throws IOException { + final BasicFileAttributes attrs = Files.getFileAttributeView(input, BasicFileAttributeView.class).readAttributes(); + final FTPFile ftp = new FTPFile(); + ftp.setName(input.getFileName().toString()); + ftp.setSize(attrs.size()); + final Calendar cal = new GregorianCalendar(); + cal.setTime(new Date(attrs.lastModifiedTime().toMillis())); + ftp.setTimestamp(cal); + if (Files.isDirectory(input)) { + ftp.setType(FTPFile.DIRECTORY_TYPE); + } else if (Files.isSymbolicLink(input)) { + ftp.setType(FTPFile.SYMBOLIC_LINK_TYPE); + } else { + ftp.setType(FTPFile.FILE_TYPE); + } + + return ftp; + } + + @Override + public boolean completePendingCommand() throws IOException { + return true; + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPPreferences.java b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPPreferences.java new file mode 100644 index 0000000..a3690af --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/preferences/ftp/MockFTPPreferences.java @@ -0,0 +1,136 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.preferences.ftp; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.security.GeneralSecurityException; +import java.util.List; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import javax.annotation.PreDestroy; +import org.apache.commons.net.ftp.FTPClient; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Primary +public class MockFTPPreferences implements FTPPreferences { + + /** + * A temporary directory to simulate FTP file tree. + */ + private final Path mockDir; + + private final Preferences mockPreferences; + + protected MockFTPPreferences() throws IOException { + mockDir = Files.createTempDirectory("mockFTPRhomeo"); + mockPreferences = Preferences.userNodeForPackage(MockFTPPreferences.class); + } + + @Override + public FTPAccess getAccess(ACCESS_POINTS ap) { + return new MockFTPAccess(mockPreferences.node("test_".concat(ap.name()))); + } + + @Override + public String getPreference(Object key) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void setPreference(Object key, String value) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public List getKeys() { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + private class MockFTPAccess extends FTPAccess { + + public MockFTPAccess(Preferences prefs) { + super(prefs); + } + + @Override + public FTPClient createClient() throws IOException, GeneralSecurityException { + return new MockFTPClient(mockDir); + } + } + + @PreDestroy + private void deleteTempDir() { + try { + Files.walkFileTree(mockDir, new SimpleFileVisitor() { + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return super.postVisitDirectory(dir, exc); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return super.visitFile(file, attrs); + } + + }); + } catch (IOException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "A temporary resource cannot be deleted : ".concat(mockDir.toString()), e); + } + } + + @Override + public int getPriority() { + return 0; + } +} \ No newline at end of file diff --git a/core/src/test/java/fr/cenra/rhomeo/core/result/MockAnnualIndexSpi.java b/core/src/test/java/fr/cenra/rhomeo/core/result/MockAnnualIndexSpi.java new file mode 100644 index 0000000..e2a3312 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/result/MockAnnualIndexSpi.java @@ -0,0 +1,142 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.IndexSpi; +import fr.cenra.rhomeo.core.data.MockIndicator; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import javax.measure.unit.Unit; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class MockAnnualIndexSpi implements IndexSpi { + + @Autowired + @Qualifier(MockIndicator.NAME) + Indicator i; + + @Override + public Unit getUnit() { + return Unit.ONE; + } + + @Override + public Indicator getIndicator() { + return i; + } + + @Override + public MockAnnualIndex createIndex(Integer value, Integer entity) { + return new MockAnnualIndex(value, entity == null? 0 : entity); + } + + @Override + public String getName() { + return "\"Mock annual SPI\""; + } + + @Override + public Collection getAlias() { + return Collections.EMPTY_SET; + } + + @Override + public String getRemarks() { + return "A mock index for test purposes."; + } + + public class MockAnnualIndex implements Index { + + private final Integer value; + private final int year; + + public MockAnnualIndex(Integer value, int year) { + ArgumentChecks.ensureNonNull("Value", value); + this.value = value; + this.year = year; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public int getYear() { + return year; + } + + @Override + public IndexSpi getSpi() { + return MockAnnualIndexSpi.this; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 31 * hash + Objects.hashCode(this.value); + hash = 31 * hash + this.year; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + final MockAnnualIndex other = (MockAnnualIndex) obj; + return this.year == other.year && Objects.equals(this.value, other.value); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/result/MockLocationIndexSpi.java b/core/src/test/java/fr/cenra/rhomeo/core/result/MockLocationIndexSpi.java new file mode 100644 index 0000000..a1bc8f6 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/result/MockLocationIndexSpi.java @@ -0,0 +1,150 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.result; + +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.IndexSpi; +import fr.cenra.rhomeo.api.result.TrackingPointIndex; +import fr.cenra.rhomeo.core.data.MockIndicator; +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import javax.measure.unit.Unit; +import org.apache.sis.util.ArgumentChecks; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class MockLocationIndexSpi implements IndexSpi { + + @Autowired + @Qualifier(MockIndicator.NAME) + Indicator i; + + @Override + public Unit getUnit() { + return Unit.ONE; + } + + @Override + public Indicator getIndicator() { + return i; + } + + @Override + public MockLocationIndex createIndex(Double value, TrackingPoint entity) { + return new MockLocationIndex(value, entity); + } + + @Override + public String getName() { + return "\"Mock location SPI\""; + } + + @Override + public Collection getAlias() { + return Collections.EMPTY_SET; + } + + @Override + public String getRemarks() { + return "A mock index for test purposes."; + } + + public class MockLocationIndex implements TrackingPointIndex { + + private final Double value; + private final TrackingPoint location; + + public MockLocationIndex(final Double value, final TrackingPoint location) { + ArgumentChecks.ensureNonNull("Value", value); + ArgumentChecks.ensureNonNull("Tracking point", location); + this.value = value; + this.location = location; + } + + @Override + public Double getValue() { + return value; + } + + @Override + public int getYear() { + return location.getYear(); + } + + @Override + public IndexSpi getSpi() { + return MockLocationIndexSpi.this; + } + + @Override + public TrackingPoint getPoint() { + return location; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 47 * hash + Objects.hashCode(this.value); + hash = 47 * hash + Objects.hashCode(this.location); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + + final MockLocationIndex other = (MockLocationIndex) obj; + return Objects.equals(this.value, other.value) && + Objects.equals(this.location, other.location); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/state/StateManagementTest.java b/core/src/test/java/fr/cenra/rhomeo/core/state/StateManagementTest.java new file mode 100644 index 0000000..84f5441 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/state/StateManagementTest.java @@ -0,0 +1,228 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.state; + +import fr.cenra.rhomeo.RhomeoTestCase; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.data.MockIndicator; +import fr.cenra.rhomeo.core.data.MockProtocol; +import fr.cenra.rhomeo.core.data.MockStatement; +import fr.cenra.rhomeo.core.result.MockAnnualIndexSpi; +import fr.cenra.rhomeo.core.result.MockLocationIndexSpi; +import fr.cenra.rhomeo.core.data.site.DuplicatedKeyException; +import fr.cenra.rhomeo.core.data.site.SiteRepository; +import fr.cenra.rhomeo.core.data.site.SiteRepositoryImpl; +import fr.cenra.rhomeo.core.data.site.SiteRepositoryTest; +import static fr.cenra.rhomeo.core.data.site.SiteRepositoryTest.RESOURCES_DATA_SHP; +import java.io.IOException; +import java.net.URISyntaxException; +import java.time.LocalDate; +import java.util.Collections; +import java.util.HashSet; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.test.DependsOn; +import org.apache.sis.test.DependsOnMethod; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +/** + * TODO : activate back when the conflict with {@link SiteRepositoryTest} is + * resolved. + * + * @author Alexis Manin (Geomatys) + */ +@DependsOn(SiteRepositoryTest.class) +public class StateManagementTest extends RhomeoTestCase { + + static final MockStatement STATEMENT = new MockStatement("toto", LocalDate.now()); + + /* + * STATE MANAGERS + */ + + @Autowired + ProcessManager processManager; + + @Autowired + ResultManager resultManager; + + /* + * GLOBAL DATA + */ + @Autowired + StateManager manager; + + @Autowired + SiteRepository repo; + + @Autowired + Session session; + + @Autowired + @Qualifier(MockProtocol.NAME) + Protocol protocol; + + @Autowired + @Qualifier(MockIndicator.NAME) + Indicator indicator; + + @Autowired + MockAnnualIndexSpi annualSpi; + + @Autowired + MockLocationIndexSpi locationSpi; + + @Ignore + @Test + public void testSaveDataset() throws Exception { + // Create a fake site for tests. + final Site newSite = createMockSite(repo); + + // insert fake data + session.startEdition(newSite, protocol); + session.requestWorkflowStep(WorkflowStep.DATASET); + + session.getDataset().getItems().add(STATEMENT); + } + + @Ignore + @DependsOnMethod("testSaveDataset") + @Test + public void restoreDataset() throws Exception { + manager.restoreState(); + + // Restore data, check result + Assert.assertTrue("Dataset has not been restored !", manager.restoreState()); + Assert.assertEquals("Dataset has not been filled !", 1, session.getDataset().getItems().size()); + Assert.assertEquals("Dataset does not contain the right statement !", StateManagementTest.STATEMENT, session.getDataset().getItems().get(0)); + } + + @Ignore + @Test + public void testProcess() throws Exception { + final Site mSite = createMockSite(repo); + session.startEdition(mSite, protocol); + session.getDataset().getItems().addAll( + new MockStatement("toto", LocalDate.now()), + new MockStatement("toto", LocalDate.now().minusYears(1)), + new MockStatement("tata", LocalDate.now().minusYears(1)), + new MockStatement("titi", LocalDate.now().minusYears(4)) + ); + + final ProcessContext pCtx = new ProcessContext(session.getDataset(), session.getDataContext()); + session.setProcessContext(pCtx); + session.requestWorkflowStep(WorkflowStep.PROCESS); + + //pCtx.getTrackingPoints().addAll(session.getDataset().getTrackingPoints()); + pCtx.getIndicators().add(indicator); + synchronized (this) { + wait(200); + } + + ProcessContext read = processManager.readProcessContext(); + Assert.assertNotNull("Backup process context", read); + //Assert.assertTrue("Selected tracking points", read.getTrackingPoints().containsAll(session.getDataset().getTrackingPoints())); + Assert.assertEquals(Collections.singleton(indicator), read.getIndicators()); + } + + @Ignore + @Test + public void testResults() throws Exception { + final Site mSite = createMockSite(repo); + session.startEdition(mSite, protocol); + session.getDataset().getItems().addAll( + new MockStatement("toto", LocalDate.now()), + new MockStatement("titi", LocalDate.now().minusYears(4)) + ); + + final ProcessContext pCtx = new ProcessContext(session.getDataset(), session.getDataContext()); + session.setProcessContext(pCtx); + + final HashSet results = new HashSet<>(4); + final MockAnnualIndexSpi.MockAnnualIndex first = annualSpi.createIndex(10, 2016); + final MockLocationIndexSpi.MockLocationIndex second = locationSpi.createIndex(4.3, (TrackingPoint) session.getDataset().getTrackingPoints().get(0)); + final MockAnnualIndexSpi.MockAnnualIndex third = annualSpi.createIndex(12, 2016); + final MockLocationIndexSpi.MockLocationIndex fourth = locationSpi.createIndex(6d, (TrackingPoint) session.getDataset().getTrackingPoints().get(1)); + + results.add(first); + results.add(second); + results.add(third); + results.add(fourth); + + session.setResults(results); + session.requestWorkflowStep(WorkflowStep.RESULT); // should start writing data + synchronized (this) { + wait(200); + } + + session.setResults(null); + Assert.assertTrue("Results should have been restored !", resultManager.restoreStep()); + + Assert.assertEquals("Restored result", results, session.getResults()); + } + + static Site createMockSite(final SiteRepository repo) throws DataStoreException, IOException, DuplicatedKeyException, URISyntaxException { + final Site newSite; + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(SiteRepositoryTest.class.getResource(RESOURCES_DATA_SHP).toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + newSite = SiteRepositoryImpl.toSite(reader.next()); + try { + repo.create(newSite); + } catch (DuplicatedKeyException e) { + // The site already exists + } + } + + return newSite; + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/util/ExportUtilsTest.java b/core/src/test/java/fr/cenra/rhomeo/core/util/ExportUtilsTest.java new file mode 100644 index 0000000..c105cd8 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/util/ExportUtilsTest.java @@ -0,0 +1,123 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.core.data.site.SiteRepositoryTest; +import java.io.File; +import java.io.IOException; +import javax.xml.stream.XMLStreamException; +import org.apache.sis.storage.DataStoreException; +import org.geotoolkit.data.FeatureReader; +import org.geotoolkit.data.kml.model.KmlException; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.feature.Feature; +import org.junit.Test; +import org.opengis.referencing.operation.TransformException; +import fr.cenra.rhomeo.core.data.site.SiteImpl; +import static fr.cenra.rhomeo.core.data.site.SiteRepositoryImpl.toSite; +import static fr.cenra.rhomeo.core.util.ExportUtils.writeKml; +import java.net.URISyntaxException; +import static org.junit.Assert.assertTrue; +import static fr.cenra.rhomeo.core.util.ExportUtils.writeKml; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +public class ExportUtilsTest { + + @Test + public void test() throws IOException, XMLStreamException, DataStoreException, KmlException, TransformException, URISyntaxException { + + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(SiteRepositoryTest.class.getResource("sites.shp").toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + assertTrue(reader.hasNext()); + + final Feature site = reader.next(); + final File output = File.createTempFile("export", "kml"); + output.deleteOnExit(); + writeKml(site, output); + assertTrue(output.length()>0); + } + } + + @Test + public void testNullValues() throws IOException, XMLStreamException, DataStoreException, KmlException, TransformException, URISyntaxException { + + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(SiteRepositoryTest.class.getResource("sites.shp").toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + assertTrue(reader.hasNext()); + + final SiteImpl site = (SiteImpl) toSite(reader.next()); + site.setCountyCode(null); + site.setOdonateType(null); + site.setOrganization(null); + site.setOrthoptereType(null); + site.setGeometry(null); + site.setZoneType(null); + site.setName(null); + site.setReferent(null); + site.setRemarks(null); + + final File output = File.createTempFile("export", "kml"); + output.deleteOnExit(); + writeKml(site, output); + assertTrue(output.length()>0); + } + } + + @Test + public void testNullValues2() throws IOException, XMLStreamException, DataStoreException, KmlException, TransformException, URISyntaxException { + + try (final ShapefileFeatureStore testDataStore = new ShapefileFeatureStore(SiteRepositoryTest.class.getResource("sitesVide.shp").toURI(), "no namespace"); + final FeatureReader reader = testDataStore.getFeatureReader(QueryBuilder.all(testDataStore.getName()))) { + + assertTrue(reader.hasNext()); + + final Feature site = reader.next(); + final File output = File.createTempFile("export", "kml"); + output.deleteOnExit(); + writeKml(site, output); + assertTrue(output.length()>0); + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/util/SecretGeneratorTest.java b/core/src/test/java/fr/cenra/rhomeo/core/util/SecretGeneratorTest.java new file mode 100644 index 0000000..cf9ba7a --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/util/SecretGeneratorTest.java @@ -0,0 +1,60 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import java.security.GeneralSecurityException; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class SecretGeneratorTest { + + @Test + public void testEncryptDecrypt() throws GeneralSecurityException { + final SecretGenerator gen = SecretGenerator.getInstance().orElseThrow(() -> new IllegalStateException()); + final String input = "This is My test: a random% phrase g-enerated { with str~*ge syntax ! (123] "; + final EncryptionResult encrypted = gen.encrypt(input); + + final String output = gen.decrypt(encrypted.output, encrypted.getKey(), encrypted.getSecureRandom()); + Assert.assertEquals("Decrypted string should be the same as encrypted one !", input, output); + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/core/util/TemporalConvertersTest.java b/core/src/test/java/fr/cenra/rhomeo/core/util/TemporalConvertersTest.java new file mode 100644 index 0000000..af85eec --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/core/util/TemporalConvertersTest.java @@ -0,0 +1,124 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.core.util; + +import fr.cenra.rhomeo.core.RhomeoCore; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.Month; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.logging.Level; +import org.apache.sis.internal.converter.SystemRegistry; +import org.apache.sis.util.ObjectConverters; +import org.junit.Assert; +import org.junit.Test; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class TemporalConvertersTest { + + @Test + public void testToString() { + final ZonedDateTime input = ZonedDateTime.now(); + + TemporalConverters.ZonedDateTime2String date2String = new TemporalConverters.ZonedDateTime2String(); + final String output = date2String.apply(input); + Assert.assertTrue("Formatted date is not a valid timestamp !", input.isEqual(TemporalConverters.fromTimestamp(Long.parseLong(output)))); + } + + @Test + public void testFromString() { + // Separator shuffle ! + final String firstTest = "2015-04-19_10/12.14;GMT+02:00"; + final ZonedDateTime firstExpected = ZonedDateTime.of(2015, 04, 19, 10, 12, 14, 0, ZoneId.of("UTC+02:00")); + // Simple say first date. + final String secondTest = "19-04-2016"; + final ZonedDateTime secondExpected = ZonedDateTime.of(LocalDate.of(2016, Month.APRIL, 19), LocalTime.MIDNIGHT, ZoneOffset.UTC); + // ISO date time + final ZonedDateTime thirdExpected = ZonedDateTime.now(); + final String thirdTest = thirdExpected.format(DateTimeFormatter.ISO_ZONED_DATE_TIME); + // A timestamp put in a string + final String fourthTest = Long.toString(thirdExpected.toInstant().toEpochMilli()); + + TemporalConverters.String2ZonedDateTime str2Date = new TemporalConverters.String2ZonedDateTime(); + Assert.assertTrue("Random separator conversion failed !", firstExpected.isEqual(str2Date.apply(firstTest))); + Assert.assertTrue("Simple day first date conversion failed !", secondExpected.isEqual(str2Date.apply(secondTest))); + Assert.assertTrue("ISO date conversion failed !", thirdExpected.isEqual(str2Date.apply(thirdTest))); + Assert.assertTrue("String timestamp conversion failed !", thirdExpected.isEqual(str2Date.apply(fourthTest))); + } + + @Test + public void testToLong() { + final ZonedDateTime input = ZonedDateTime.now(); + + TemporalConverters.ZonedDateTime2Long date2Long = new TemporalConverters.ZonedDateTime2Long(); + final Long output = date2Long.apply(input); + Assert.assertTrue("Formatted date is not a valid timestamp !", input.isEqual(new Timestamp(output).toInstant().atZone(ZoneOffset.UTC))); + } + + @Test + public void testFromLong() { + final long milli = System.currentTimeMillis(); + final ZonedDateTime expected = ZonedDateTime.from(Instant.ofEpochMilli(milli).atZone(ZoneOffset.UTC)); + + TemporalConverters.Long2ZonedDateTime long2Date = new TemporalConverters.Long2ZonedDateTime(); + Assert.assertTrue("Long to date and time conversion failed !", expected.isEqual(long2Date.apply(milli))); + } + + @Test + public void testDiscovery() { + try { + Assert.assertEquals(TemporalConverters.String2ZonedDateTime.class, ObjectConverters.find(String.class, ZonedDateTime.class).getClass()); + Assert.assertEquals(TemporalConverters.ZonedDateTime2String.class, ObjectConverters.find(ZonedDateTime.class, String.class).getClass()); + + Assert.assertEquals(TemporalConverters.Long2ZonedDateTime.class, ObjectConverters.find(Long.class, ZonedDateTime.class).getClass()); + Assert.assertEquals(TemporalConverters.ZonedDateTime2Long.class, ObjectConverters.find(ZonedDateTime.class, Long.class).getClass()); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.INFO, SystemRegistry.INSTANCE.toString()); + throw e; + } + } +} diff --git a/core/src/test/java/fr/cenra/rhomeo/validation/SimpleValidationTest.java b/core/src/test/java/fr/cenra/rhomeo/validation/SimpleValidationTest.java new file mode 100644 index 0000000..57402b7 --- /dev/null +++ b/core/src/test/java/fr/cenra/rhomeo/validation/SimpleValidationTest.java @@ -0,0 +1,89 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.validation; + +import fr.cenra.rhomeo.RhomeoTestCase; +import javax.validation.ConstraintViolationException; +import javax.validation.constraints.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * A simple test to check integration of JSR-349 integration with Spring. + * + * @author Alexis Manin (Geomatys) + */ +public class SimpleValidationTest extends RhomeoTestCase { + + @Test + public void testParameterValidation() { + + final ValidationBean bean = APP_CTX.getBean(ValidationBean.class); + try { + bean.testMethod(null); + Assert.fail("Method validation failed to detect parameter error."); + } catch (ConstraintViolationException e) { + // normal behavior + } + + try { + bean.testMethod("toto"); + Assert.fail("Method validation failed to detect return value error."); + } catch (ConstraintViolationException e) { + // normal behavior + } + } + + /** + * A bean voluntarily violating validation constraints. + */ + @Component + @Scope(value = "prototype") + @Validated + public static class ValidationBean { + + @NotNull + public String testMethod(@NotNull String input) { + return null; + } + } +} diff --git a/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl.properties b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl.properties new file mode 100644 index 0000000..29ca5da --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl.properties @@ -0,0 +1,6 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +_label=titre en fran\u00e7ais +_description=description en fran\u00e7ais diff --git a/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl_en.properties b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl_en.properties new file mode 100644 index 0000000..65ab52a --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalDescriptionTest$InternationalDescriptionImpl_en.properties @@ -0,0 +1,6 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +_label=title in english +_description=description in english diff --git a/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl.properties b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl.properties new file mode 100644 index 0000000..873e55a --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl.properties @@ -0,0 +1,13 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +field1=champ 1 +field2=champ 2 +field1._label=titre du champ 1 +field1._description=description du champ 1 +field2._label=titre du champ 2 +field2._description=description du champ 2 +#Commentaire +other=Autre champ +withParameters=Bonjour {0} et au revoir {1} ! diff --git a/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl_en.properties b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl_en.properties new file mode 100644 index 0000000..4069e20 --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/api/international/InternationalResourceTest$InternationalResourceImpl_en.properties @@ -0,0 +1,13 @@ +# To change this license header, choose License Headers in Project Properties. +# To change this template file, choose Tools | Templates +# and open the template in the editor. + +field1=field 1 +field2=field 2 +field1._label=title of field 1 +field1._description=description of field 1 +field2._label=title of field 2 +field2._description=description of field 2 +#Commentaire +other=Other field +withParameters=Hello {0} and good bye {1}! diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.cpg b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.cpg new file mode 100644 index 0000000..cd89cb9 --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.cpg @@ -0,0 +1 @@ +ISO-8859-1 \ No newline at end of file diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.dbf b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.dbf new file mode 100644 index 0000000..1861c06 Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.dbf differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.fix b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.fix new file mode 100644 index 0000000..52c6bac Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.fix differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.prj b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.prj new file mode 100644 index 0000000..6fcf078 --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.prj @@ -0,0 +1 @@ +PROJCS["RGF93 / Lambert-93", GEOGCS["RGF93", DATUM["D_Reseau Geodesique Francais 1993", SPHEROID["GRS 1980", 6378137.0, 298.257222101], TOWGS84[0.0, 0.0, 0.0]], PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], AXIS["Latitude", NORTH], AXIS["Longitude", EAST]], PROJECTION["Lambert_Conformal_Conic", AUTHORITY["EPSG", "9802"]], PARAMETER["Latitude_Of_Origin", 46.5], PARAMETER["Central_Meridian", 3.0], PARAMETER["Standard_Parallel_1", 49.0], PARAMETER["Standard_Parallel_2", 44.0], PARAMETER["False_Easting", 700000.0], PARAMETER["False_Northing", 6600000.0], UNIT["meter", 1], AXIS["Easting", EAST], AXIS["Northing", NORTH], AUTHORITY["EPSG", "2154"]] \ No newline at end of file diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.qix b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.qix new file mode 100644 index 0000000..478d86b Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.qix differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shp b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shp new file mode 100644 index 0000000..8f2bdcd Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shp differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shx b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shx new file mode 100644 index 0000000..6f5b31d Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sites.shx differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.dbf b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.dbf new file mode 100644 index 0000000..0943ffd Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.dbf differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.prj b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.prj new file mode 100644 index 0000000..5adb2a9 --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.prj @@ -0,0 +1 @@ +PROJCS["RGF93_Lambert_93",GEOGCS["GCS_RGF93",DATUM["D_RGF_1993",SPHEROID["GRS_1980",6378137,298.257222101]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["Meter",1]] \ No newline at end of file diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.qpj b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.qpj new file mode 100644 index 0000000..52a60bf --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.qpj @@ -0,0 +1 @@ +PROJCS["RGF93 / Lambert-93",GEOGCS["RGF93",DATUM["Reseau_Geodesique_Francais_1993",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6171"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4171"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",49],PARAMETER["standard_parallel_2",44],PARAMETER["latitude_of_origin",46.5],PARAMETER["central_meridian",3],PARAMETER["false_easting",700000],PARAMETER["false_northing",6600000],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["X",EAST],AXIS["Y",NORTH],AUTHORITY["EPSG","2154"]] diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shp b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shp new file mode 100644 index 0000000..8f2bdcd Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shp differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shx b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shx new file mode 100644 index 0000000..6f5b31d Binary files /dev/null and b/core/src/test/resources/fr/cenra/rhomeo/core/data/site/sitesVide.shx differ diff --git a/core/src/test/resources/fr/cenra/rhomeo/core/data/update.json b/core/src/test/resources/fr/cenra/rhomeo/core/data/update.json new file mode 100644 index 0000000..bb80e16 --- /dev/null +++ b/core/src/test/resources/fr/cenra/rhomeo/core/data/update.json @@ -0,0 +1,13 @@ +{ + "version":"0.1", + "date":"2016-04-29T15:00:00+01:00", + "releaseNote": ["Première version de l'application permettant la gestion de sites"], + "win32":"http://testwin32", + "win32MD5":"testwin32MD5", + "deb64":"http://testdeb64", + "deb64MD5":"testdeb64MD5", + "rpm64":"http://testrpm64", + "rpm64MD5":"testrpm64MD5", + "macOS64":"http://testmacOS64", + "macOS64MD5":"testmacOS64MD5" +} diff --git a/core/src/test/resources/logback.xml b/core/src/test/resources/logback.xml new file mode 100644 index 0000000..cb6ba7e --- /dev/null +++ b/core/src/test/resources/logback.xml @@ -0,0 +1,16 @@ + + + true + + + + + %msg%n + %d{dd-MM HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n + + + + + + + \ No newline at end of file diff --git a/desktop/pom.xml b/desktop/pom.xml new file mode 100644 index 0000000..d22bacb --- /dev/null +++ b/desktop/pom.xml @@ -0,0 +1,116 @@ + + + + 4.0.0 + + + fr.cenra.rhomeo + rhomeo + 1.2-SNAPSHOT + + + fr.cenra.rhomeo + desktop + jar + Rhomeo desktop + Rhomeo client application + + + + fr.cenra.rhomeo + core + ${project.version} + + + fr.cenra.rhomeo + protocol + ${project.version} + + + commons-codec + commons-codec + + + org.geotoolkit + geotk-widgets-javafx + + + org.quartz-scheduler + quartz + + + org.fxmisc.richtext + richtextfx + + + org.codehaus.groovy + groovy-all + + + + + org.geotoolkit + geotk-client-wfs + + + org.geotoolkit + geotk-jaxp-xsd + + + + junit + junit + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + + org.springframework + spring-test + test + + + + + + Rhomeo + + + com.zenjava + javafx-maven-plugin + + ${project.version} + fr.cenra.rhomeo.Launcher + true + + + -XX:+UseG1GC + + -Xms256m + -Xmx1G + + -Djava.net.useSystemProxies=true + + -Dfile.encoding=UTF-8 + + + src/main/deploy/additional + + + + Copyright (C) 2016, CENRA + CLUF + + + + + + + \ No newline at end of file diff --git a/desktop/src/main/deploy/additional/CLUF b/desktop/src/main/deploy/additional/CLUF new file mode 100644 index 0000000..0e5d105 --- /dev/null +++ b/desktop/src/main/deploy/additional/CLUF @@ -0,0 +1,550 @@ + + CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL + +Version 2.1 du 2013-06-21 + + + Avertissement + +Ce contrat est une licence de logiciel libre issue d'une concertation +entre ses auteurs afin que le respect de deux grands principes préside à +sa rédaction: + + * d'une part, le respect des principes de diffusion des logiciels + libres: accès au code source, droits étendus conférés aux utilisateurs, + * d'autre part, la désignation d'un droit applicable, le droit + français, auquel elle est conforme, tant au regard du droit de la + responsabilité civile que du droit de la propriété intellectuelle et + de la protection qu'il offre aux auteurs et titulaires des droits + patrimoniaux sur un logiciel. + +Les auteurs de la licence CeCILL (Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +sont: + +Commissariat à l'énergie atomique et aux énergies alternatives - CEA, +établissement public de recherche à caractère scientifique, technique et +industriel, dont le siège est situé 25 rue Leblanc, immeuble Le Ponant +D, 75015 Paris. + +Centre National de la Recherche Scientifique - CNRS, établissement +public à caractère scientifique et technologique, dont le siège est +situé 3 rue Michel-Ange, 75794 Paris cedex 16. + +Institut National de Recherche en Informatique et en Automatique - +Inria, établissement public à caractère scientifique et technologique, +dont le siège est situé Domaine de Voluceau, Rocquencourt, BP 105, 78153 +Le Chesnay cedex. + + + Préambule + +Ce contrat est une licence de logiciel libre dont l'objectif est de +conférer aux utilisateurs la liberté de modification et de +redistribution du logiciel régi par cette licence dans le cadre d'un +modèle de diffusion en logiciel libre. + +L'exercice de ces libertés est assorti de certains devoirs à la charge +des utilisateurs afin de préserver ce statut au cours des +redistributions ultérieures. + +L'accessibilité au code source et les droits de copie, de modification +et de redistribution qui en découlent ont pour contrepartie de n'offrir +aux utilisateurs qu'une garantie limitée et de ne faire peser sur +l'auteur du logiciel, le titulaire des droits patrimoniaux et les +concédants successifs qu'une responsabilité restreinte. + +A cet égard l'attention de l'utilisateur est attirée sur les risques +associés au chargement, à l'utilisation, à la modification et/ou au +développement et à la reproduction du logiciel par l'utilisateur étant +donné sa spécificité de logiciel libre, qui peut le rendre complexe à +manipuler et qui le réserve donc à des développeurs ou des +professionnels avertis possédant des connaissances informatiques +approfondies. Les utilisateurs sont donc invités à charger et tester +l'adéquation du logiciel à leurs besoins dans des conditions permettant +d'assurer la sécurité de leurs systèmes et/ou de leurs données et, plus +généralement, à l'utiliser et l'exploiter dans les mêmes conditions de +sécurité. Ce contrat peut être reproduit et diffusé librement, sous +réserve de le conserver en l'état, sans ajout ni suppression de clauses. + +Ce contrat est susceptible de s'appliquer à tout logiciel dont le +titulaire des droits patrimoniaux décide de soumettre l'exploitation aux +dispositions qu'il contient. + +Une liste de questions fréquemment posées se trouve sur le site web +officiel de la famille des licences CeCILL +(http://www.cecill.info/index.fr.html) pour toute clarification qui +serait nécessaire. + + + Article 1 - DEFINITIONS + +Dans ce contrat, les termes suivants, lorsqu'ils seront écrits avec une +lettre capitale, auront la signification suivante: + +Contrat: désigne le présent contrat de licence, ses éventuelles versions +postérieures et annexes. + +Logiciel: désigne le logiciel sous sa forme de Code Objet et/ou de Code +Source et le cas échéant sa documentation, dans leur état au moment de +l'acceptation du Contrat par le Licencié. + +Logiciel Initial: désigne le Logiciel sous sa forme de Code Source et +éventuellement de Code Objet et le cas échéant sa documentation, dans +leur état au moment de leur première diffusion sous les termes du Contrat. + +Logiciel Modifié: désigne le Logiciel modifié par au moins une +Contribution. + +Code Source: désigne l'ensemble des instructions et des lignes de +programme du Logiciel et auquel l'accès est nécessaire en vue de +modifier le Logiciel. + +Code Objet: désigne les fichiers binaires issus de la compilation du +Code Source. + +Titulaire: désigne le ou les détenteurs des droits patrimoniaux d'auteur +sur le Logiciel Initial. + +Licencié: désigne le ou les utilisateurs du Logiciel ayant accepté le +Contrat. + +Contributeur: désigne le Licencié auteur d'au moins une Contribution. + +Concédant: désigne le Titulaire ou toute personne physique ou morale +distribuant le Logiciel sous le Contrat. + +Contribution: désigne l'ensemble des modifications, corrections, +traductions, adaptations et/ou nouvelles fonctionnalités intégrées dans +le Logiciel par tout Contributeur, ainsi que tout Module Interne. + +Module: désigne un ensemble de fichiers sources y compris leur +documentation qui permet de réaliser des fonctionnalités ou services +supplémentaires à ceux fournis par le Logiciel. + +Module Externe: désigne tout Module, non dérivé du Logiciel, tel que ce +Module et le Logiciel s'exécutent dans des espaces d'adressage +différents, l'un appelant l'autre au moment de leur exécution. + +Module Interne: désigne tout Module lié au Logiciel de telle sorte +qu'ils s'exécutent dans le même espace d'adressage. + +GNU GPL: désigne la GNU General Public License dans sa version 2 ou +toute version ultérieure, telle que publiée par Free Software Foundation +Inc. + +GNU Affero GPL: désigne la GNU Affero General Public License dans sa +version 3 ou toute version ultérieure, telle que publiée par Free +Software Foundation Inc. + +EUPL: désigne la Licence Publique de l'Union européenne dans sa version +1.1 ou toute version ultérieure, telle que publiée par la Commission +Européenne. + +Parties: désigne collectivement le Licencié et le Concédant. + +Ces termes s'entendent au singulier comme au pluriel. + + + Article 2 - OBJET + +Le Contrat a pour objet la concession par le Concédant au Licencié d'une +licence non exclusive, cessible et mondiale du Logiciel telle que +définie ci-après à l'article 5 <#etendue> pour toute la durée de +protection des droits portant sur ce Logiciel. + + + Article 3 - ACCEPTATION + +3.1 L'acceptation par le Licencié des termes du Contrat est réputée +acquise du fait du premier des faits suivants: + + * (i) le chargement du Logiciel par tout moyen notamment par + téléchargement à partir d'un serveur distant ou par chargement à + partir d'un support physique; + * (ii) le premier exercice par le Licencié de l'un quelconque des + droits concédés par le Contrat. + +3.2 Un exemplaire du Contrat, contenant notamment un avertissement +relatif aux spécificités du Logiciel, à la restriction de garantie et à +la limitation à un usage par des utilisateurs expérimentés a été mis à +disposition du Licencié préalablement à son acceptation telle que +définie à l'article 3.1 <#acceptation-acquise> ci dessus et le Licencié +reconnaît en avoir pris connaissance. + + + Article 4 - ENTREE EN VIGUEUR ET DUREE + + + 4.1 ENTREE EN VIGUEUR + +Le Contrat entre en vigueur à la date de son acceptation par le Licencié +telle que définie en 3.1 <#acceptation-acquise>. + + + 4.2 DUREE + +Le Contrat produira ses effets pendant toute la durée légale de +protection des droits patrimoniaux portant sur le Logiciel. + + + Article 5 - ETENDUE DES DROITS CONCEDES + +Le Concédant concède au Licencié, qui accepte, les droits suivants sur +le Logiciel pour toutes destinations et pour la durée du Contrat dans +les conditions ci-après détaillées. + +Par ailleurs, si le Concédant détient ou venait à détenir un ou +plusieurs brevets d'invention protégeant tout ou partie des +fonctionnalités du Logiciel ou de ses composants, il s'engage à ne pas +opposer les éventuels droits conférés par ces brevets aux Licenciés +successifs qui utiliseraient, exploiteraient ou modifieraient le +Logiciel. En cas de cession de ces brevets, le Concédant s'engage à +faire reprendre les obligations du présent alinéa aux cessionnaires. + + + 5.1 DROIT D'UTILISATION + +Le Licencié est autorisé à utiliser le Logiciel, sans restriction quant +aux domaines d'application, étant ci-après précisé que cela comporte: + + 1. + + la reproduction permanente ou provisoire du Logiciel en tout ou + partie par tout moyen et sous toute forme. + + 2. + + le chargement, l'affichage, l'exécution, ou le stockage du Logiciel + sur tout support. + + 3. + + la possibilité d'en observer, d'en étudier, ou d'en tester le + fonctionnement afin de déterminer les idées et principes qui sont à + la base de n'importe quel élément de ce Logiciel; et ceci, lorsque + le Licencié effectue toute opération de chargement, d'affichage, + d'exécution, de transmission ou de stockage du Logiciel qu'il est en + droit d'effectuer en vertu du Contrat. + + + 5.2 DROIT D'APPORTER DES CONTRIBUTIONS + +Le droit d'apporter des Contributions comporte le droit de traduire, +d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel +et le droit de reproduire le logiciel en résultant. + +Le Licencié est autorisé à apporter toute Contribution au Logiciel sous +réserve de mentionner, de façon explicite, son nom en tant qu'auteur de +cette Contribution et la date de création de celle-ci. + + + 5.3 DROIT DE DISTRIBUTION + +Le droit de distribution comporte notamment le droit de diffuser, de +transmettre et de communiquer le Logiciel au public sur tout support et +par tout moyen ainsi que le droit de mettre sur le marché à titre +onéreux ou gratuit, un ou des exemplaires du Logiciel par tout procédé. + +Le Licencié est autorisé à distribuer des copies du Logiciel, modifié ou +non, à des tiers dans les conditions ci-après détaillées. + + + 5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION + +Le Licencié est autorisé à distribuer des copies conformes du Logiciel, +sous forme de Code Source ou de Code Objet, à condition que cette +distribution respecte les dispositions du Contrat dans leur totalité et +soit accompagnée: + + 1. + + d'un exemplaire du Contrat, + + 2. + + d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + <#responsabilite> et 9 <#garantie>, + +et que, dans le cas où seul le Code Objet du Logiciel est redistribué, +le Licencié permette un accès effectif au Code Source complet du +Logiciel pour une durée d'au moins 3 ans à compter de la distribution du +logiciel, étant entendu que le coût additionnel d'acquisition du Code +Source ne devra pas excéder le simple coût de transfert des données. + + + 5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE + +Lorsque le Licencié apporte une Contribution au Logiciel, les conditions +de distribution du Logiciel Modifié en résultant sont alors soumises à +l'intégralité des dispositions du Contrat. + +Le Licencié est autorisé à distribuer le Logiciel Modifié, sous forme de +code source ou de code objet, à condition que cette distribution +respecte les dispositions du Contrat dans leur totalité et soit +accompagnée: + + 1. + + d'un exemplaire du Contrat, + + 2. + + d'un avertissement relatif à la restriction de garantie et de + responsabilité du Concédant telle que prévue aux articles 8 + <#responsabilite> et 9 <#garantie>, + +et, dans le cas où seul le code objet du Logiciel Modifié est redistribué, + + 3. + + d'une note précisant les conditions d'accès effectif au code source + complet du Logiciel Modifié, pendant une période d'au moins 3 ans à + compter de la distribution du Logiciel Modifié, étant entendu que le + coût additionnel d'acquisition du code source ne devra pas excéder + le simple coût de transfert des données. + + + 5.3.3 DISTRIBUTION DES MODULES EXTERNES + +Lorsque le Licencié a développé un Module Externe les conditions du +Contrat ne s'appliquent pas à ce Module Externe, qui peut être distribué +sous un contrat de licence différent. + + + 5.3.4 COMPATIBILITE AVEC D'AUTRES LICENCES + +Le Licencié peut inclure un code soumis aux dispositions d'une des +versions de la licence GNU GPL, GNU Affero GPL et/ou EUPL dans le +Logiciel modifié ou non et distribuer l'ensemble sous les conditions de +la même version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. + +Le Licencié peut inclure le Logiciel modifié ou non dans un code soumis +aux dispositions d'une des versions de la licence GNU GPL, GNU Affero +GPL et/ou EUPL et distribuer l'ensemble sous les conditions de la même +version de la licence GNU GPL, GNU Affero GPL et/ou EUPL. + + + Article 6 - PROPRIETE INTELLECTUELLE + + + 6.1 SUR LE LOGICIEL INITIAL + +Le Titulaire est détenteur des droits patrimoniaux sur le Logiciel +Initial. Toute utilisation du Logiciel Initial est soumise au respect +des conditions dans lesquelles le Titulaire a choisi de diffuser son +oeuvre et nul autre n'a la faculté de modifier les conditions de +diffusion de ce Logiciel Initial. + +Le Titulaire s'engage à ce que le Logiciel Initial reste au moins régi +par le Contrat et ce, pour la durée visée à l'article 4.2 <#duree>. + + + 6.2 SUR LES CONTRIBUTIONS + +Le Licencié qui a développé une Contribution est titulaire sur celle-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable. + + + 6.3 SUR LES MODULES EXTERNES + +Le Licencié qui a développé un Module Externe est titulaire sur celui-ci +des droits de propriété intellectuelle dans les conditions définies par +la législation applicable et reste libre du choix du contrat régissant +sa diffusion. + + + 6.4 DISPOSITIONS COMMUNES + +Le Licencié s'engage expressément: + + 1. + + à ne pas supprimer ou modifier de quelque manière que ce soit les + mentions de propriété intellectuelle apposées sur le Logiciel; + + 2. + + à reproduire à l'identique lesdites mentions de propriété + intellectuelle sur les copies du Logiciel modifié ou non. + +Le Licencié s'engage à ne pas porter atteinte, directement ou +indirectement, aux droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs sur le Logiciel et à prendre, le cas échéant, à +l'égard de son personnel toutes les mesures nécessaires pour assurer le +respect des dits droits de propriété intellectuelle du Titulaire et/ou +des Contributeurs. + + + Article 7 - SERVICES ASSOCIES + +7.1 Le Contrat n'oblige en aucun cas le Concédant à la réalisation de +prestations d'assistance technique ou de maintenance du Logiciel. + +Cependant le Concédant reste libre de proposer ce type de services. Les +termes et conditions d'une telle assistance technique et/ou d'une telle +maintenance seront alors déterminés dans un acte séparé. Ces actes de +maintenance et/ou assistance technique n'engageront que la seule +responsabilité du Concédant qui les propose. + +7.2 De même, tout Concédant est libre de proposer, sous sa seule +responsabilité, à ses licenciés une garantie, qui n'engagera que lui, +lors de la redistribution du Logiciel et/ou du Logiciel Modifié et ce, +dans les conditions qu'il souhaite. Cette garantie et les modalités +financières de son application feront l'objet d'un acte séparé entre le +Concédant et le Licencié. + + + Article 8 - RESPONSABILITE + +8.1 Sous réserve des dispositions de l'article 8.2 +<#limite-responsabilite>, le Licencié a la faculté, sous réserve de +prouver la faute du Concédant concerné, de solliciter la réparation du +préjudice direct qu'il subirait du fait du Logiciel et dont il apportera +la preuve. + +8.2 La responsabilité du Concédant est limitée aux engagements pris en +application du Contrat et ne saurait être engagée en raison notamment: +(i) des dommages dus à l'inexécution, totale ou partielle, de ses +obligations par le Licencié, (ii) des dommages directs ou indirects +découlant de l'utilisation ou des performances du Logiciel subis par le +Licencié et (iii) plus généralement d'un quelconque dommage indirect. En +particulier, les Parties conviennent expressément que tout préjudice +financier ou commercial (par exemple perte de données, perte de +bénéfices, perte d'exploitation, perte de clientèle ou de commandes, +manque à gagner, trouble commercial quelconque) ou toute action dirigée +contre le Licencié par un tiers, constitue un dommage indirect et +n'ouvre pas droit à réparation par le Concédant. + + + Article 9 - GARANTIE + +9.1 Le Licencié reconnaît que l'état actuel des connaissances +scientifiques et techniques au moment de la mise en circulation du +Logiciel ne permet pas d'en tester et d'en vérifier toutes les +utilisations ni de détecter l'existence d'éventuels défauts. L'attention +du Licencié a été attirée sur ce point sur les risques associés au +chargement, à l'utilisation, la modification et/ou au développement et à +la reproduction du Logiciel qui sont réservés à des utilisateurs avertis. + +Il relève de la responsabilité du Licencié de contrôler, par tous +moyens, l'adéquation du produit à ses besoins, son bon fonctionnement et +de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens. + +9.2 Le Concédant déclare de bonne foi être en droit de concéder +l'ensemble des droits attachés au Logiciel (comprenant notamment les +droits visés à l'article 5 <#etendue>). + +9.3 Le Licencié reconnaît que le Logiciel est fourni "en l'état" par le +Concédant sans autre garantie, expresse ou tacite, que celle prévue à +l'article 9.2 <#bonne-foi> et notamment sans aucune garantie sur sa +valeur commerciale, son caractère sécurisé, innovant ou pertinent. + +En particulier, le Concédant ne garantit pas que le Logiciel est exempt +d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible +avec l'équipement du Licencié et sa configuration logicielle ni qu'il +remplira les besoins du Licencié. + +9.4 Le Concédant ne garantit pas, de manière expresse ou tacite, que le +Logiciel ne porte pas atteinte à un quelconque droit de propriété +intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout +autre droit de propriété. Ainsi, le Concédant exclut toute garantie au +profit du Licencié contre les actions en contrefaçon qui pourraient être +diligentées au titre de l'utilisation, de la modification, et de la +redistribution du Logiciel. Néanmoins, si de telles actions sont +exercées contre le Licencié, le Concédant lui apportera son expertise +technique et juridique pour sa défense. Cette expertise technique et +juridique est déterminée au cas par cas entre le Concédant concerné et +le Licencié dans le cadre d'un protocole d'accord. Le Concédant dégage +toute responsabilité quant à l'utilisation de la dénomination du +Logiciel par le Licencié. Aucune garantie n'est apportée quant à +l'existence de droits antérieurs sur le nom du Logiciel et sur +l'existence d'une marque. + + + Article 10 - RESILIATION + +10.1 En cas de manquement par le Licencié aux obligations mises à sa +charge par le Contrat, le Concédant pourra résilier de plein droit le +Contrat trente (30) jours après notification adressée au Licencié et +restée sans effet. + +10.2 Le Licencié dont le Contrat est résilié n'est plus autorisé à +utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les +licences qu'il aura concédées antérieurement à la résiliation du Contrat +resteront valides sous réserve qu'elles aient été effectuées en +conformité avec le Contrat. + + + Article 11 - DISPOSITIONS DIVERSES + + + 11.1 CAUSE EXTERIEURE + +Aucune des Parties ne sera responsable d'un retard ou d'une défaillance +d'exécution du Contrat qui serait dû à un cas de force majeure, un cas +fortuit ou une cause extérieure, telle que, notamment, le mauvais +fonctionnement ou les interruptions du réseau électrique ou de +télécommunication, la paralysie du réseau liée à une attaque +informatique, l'intervention des autorités gouvernementales, les +catastrophes naturelles, les dégâts des eaux, les tremblements de terre, +le feu, les explosions, les grèves et les conflits sociaux, l'état de +guerre... + +11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou +plusieurs occasions de se prévaloir d'une ou plusieurs dispositions du +Contrat, ne pourra en aucun cas impliquer renonciation par la Partie +intéressée à s'en prévaloir ultérieurement. + +11.3 Le Contrat annule et remplace toute convention antérieure, écrite +ou orale, entre les Parties sur le même objet et constitue l'accord +entier entre les Parties sur cet objet. Aucune addition ou modification +aux termes du Contrat n'aura d'effet à l'égard des Parties à moins +d'être faite par écrit et signée par leurs représentants dûment habilités. + +11.4 Dans l'hypothèse où une ou plusieurs des dispositions du Contrat +s'avèrerait contraire à une loi ou à un texte applicable, existants ou +futurs, cette loi ou ce texte prévaudrait, et les Parties feraient les +amendements nécessaires pour se conformer à cette loi ou à ce texte. +Toutes les autres dispositions resteront en vigueur. De même, la +nullité, pour quelque raison que ce soit, d'une des dispositions du +Contrat ne saurait entraîner la nullité de l'ensemble du Contrat. + + + 11.5 LANGUE + +Le Contrat est rédigé en langue française et en langue anglaise, ces +deux versions faisant également foi. + + + Article 12 - NOUVELLES VERSIONS DU CONTRAT + +12.1 Toute personne est autorisée à copier et distribuer des copies de +ce Contrat. + +12.2 Afin d'en préserver la cohérence, le texte du Contrat est protégé +et ne peut être modifié que par les auteurs de la licence, lesquels se +réservent le droit de publier périodiquement des mises à jour ou de +nouvelles versions du Contrat, qui posséderont chacune un numéro +distinct. Ces versions ultérieures seront susceptibles de prendre en +compte de nouvelles problématiques rencontrées par les logiciels libres. + +12.3 Tout Logiciel diffusé sous une version donnée du Contrat ne pourra +faire l'objet d'une diffusion ultérieure que sous la même version du +Contrat ou une version postérieure, sous réserve des dispositions de +l'article 5.3.4 <#compatibilite>. + + + Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE + +13.1 Le Contrat est régi par la loi française. Les Parties conviennent +de tenter de régler à l'amiable les différends ou litiges qui +viendraient à se produire par suite ou à l'occasion du Contrat. + +13.2 A défaut d'accord amiable dans un délai de deux (2) mois à compter +de leur survenance et sauf situation relevant d'une procédure d'urgence, +les différends ou litiges seront portés par la Partie la plus diligente +devant les Tribunaux compétents de Paris. + + diff --git a/desktop/src/main/deploy/package/linux/Rhomeo.png b/desktop/src/main/deploy/package/linux/Rhomeo.png new file mode 100644 index 0000000..216a115 Binary files /dev/null and b/desktop/src/main/deploy/package/linux/Rhomeo.png differ diff --git a/desktop/src/main/deploy/package/macosx/Rhomeo.icns b/desktop/src/main/deploy/package/macosx/Rhomeo.icns new file mode 100644 index 0000000..3ec2838 Binary files /dev/null and b/desktop/src/main/deploy/package/macosx/Rhomeo.icns differ diff --git a/desktop/src/main/deploy/package/windows/Rhomeo.ico b/desktop/src/main/deploy/package/windows/Rhomeo.ico new file mode 100644 index 0000000..4762350 Binary files /dev/null and b/desktop/src/main/deploy/package/windows/Rhomeo.ico differ diff --git a/desktop/src/main/java/fr/cenra/rhomeo/DocManager.java b/desktop/src/main/java/fr/cenra/rhomeo/DocManager.java new file mode 100644 index 0000000..4b5335c --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/DocManager.java @@ -0,0 +1,122 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; +import javax.annotation.PreDestroy; +import org.apache.sis.util.NullArgumentException; +import org.geotoolkit.nio.IOUtilities; +import org.springframework.stereotype.Component; +import org.springframework.util.DigestUtils; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class DocManager { + + protected final Path tmpDir; + + protected DocManager() { + Path tmp = null; + try { + // Init folder to put docs into. + tmp = Files.createTempDirectory("rhomeo_docs"); + } catch (IOException ex) { + Rhomeo.LOGGER.log(Level.WARNING, "Impossible to prepare documentation management.", ex); + } + + tmpDir = tmp; + } + + public void openDocument(final URL resource) { + try { + if (isHref(resource)) { + Rhomeo.browse(resource); + } + final Path p = getPath(resource); + if (p != null) + Rhomeo.openFile(p.toFile()); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.WARNING, "Cannot open given resource : ".concat(String.valueOf(resource)), e); + } + } + + private Path getPath(URL resource) throws URISyntaxException, IOException { + if (resource == null) + throw new NullArgumentException("Given resource is null !"); + if ("file".equals(resource.getProtocol())) + return Paths.get(resource.toURI()); + String md5 = DigestUtils.md5DigestAsHex(resource.toExternalForm().getBytes()); + + // HACK for win systems which are too dumb to open files without extension + final String extension = IOUtilities.extension(resource); + final Path target; + if (extension != null && !extension.isEmpty()) + target = tmpDir.resolve(md5 + "." + extension); + else + target = tmpDir.resolve(md5); + + if (!Files.exists(target)) { + try (final InputStream source = resource.openStream()) { + Files.copy(source, target); + } + } + + return target; + } + + @PreDestroy + private void clear() throws IOException { + if (tmpDir != null) + IOUtilities.deleteRecursively(tmpDir); + } + + private boolean isHref(URL resource) { + return resource != null && resource.getProtocol().startsWith("http"); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/Launcher.java b/desktop/src/main/java/fr/cenra/rhomeo/Launcher.java new file mode 100644 index 0000000..40352a3 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/Launcher.java @@ -0,0 +1,511 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo; + +import com.sun.javafx.PlatformUtil; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.UpdateInfo; +import fr.cenra.rhomeo.core.preferences.net.NetPreferences; +import fr.cenra.rhomeo.core.state.StateManager; +import fr.cenra.rhomeo.fx.FXInitialDialogPane; +import fr.cenra.rhomeo.fx.FXMainPane; +import fr.cenra.rhomeo.fx.FXSplashScreen; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import javafx.animation.FadeTransition; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.DialogEvent; +import javafx.scene.control.TextArea; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.Duration; +import org.controlsfx.dialog.ExceptionDialog; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.internal.GeotkFX; +import org.slf4j.bridge.SLF4JBridgeHandler; +import org.springframework.context.support.ClassPathXmlApplicationContext; + + +/** + * Application launcher. + * + * @author Cédric Briançon (Geomatys) + */ +public class Launcher extends Application { + /** + * Launches the application with the given arguments. + * + * @see #launch(String...) + */ + public static void main(String[] args) { + launch(args); + } + + /** + * {@inheritDoc} + */ + @Override + public void start(Stage primaryStage) throws Exception { + SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5) + + // add SLF4JBridgeHandler to j.u.l's root logger, should be done once during + // the initialization phase of your application + SLF4JBridgeHandler.install(); + + Thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> { + +// if(e instanceof Exception){ +// TaskManager.INSTANCE.submit(() -> {throw (Exception) e;}); +// } +// else{ + RhomeoCore.LOGGER.log(Level.WARNING, "A throwable has not been caught.", e); +// } + }); + + // perform initialization and plugin loading tasks + final InitialLoadingTask initTask = new InitialLoadingTask(); + showLoadingStage(initTask); + TaskManager.INSTANCE.submit(initTask); + } + + /** + * {@inheritDoc} + */ + @Override + public void stop() throws Exception { + try { + Session.getInstance().close(); + } catch (IllegalStateException ex) { + // The context was not already initialized, no need to close it. + // Not a real exception since nothing to do + } + + TaskManager.INSTANCE.close(); + } + + /** + * Display splash screen. + * + * @param task the related task to show progress + * @throws IOException + */ + private void showLoadingStage(final InitialLoadingTask task) throws IOException { + // Initialize splash screen + final Stage splashStage = Rhomeo.newStage(); + splashStage.initStyle(StageStyle.TRANSPARENT); + + final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fr/cenra/rhomeo/fx/FXSplashScreen.fxml")); + final GridPane root = loader.load(); + final FXSplashScreen splashScreen = loader.getController(); + splashScreen.uiCancel.setVisible(false); + splashScreen.uiProgressLabel.textProperty().bind(task.messageProperty()); + splashScreen.uiProgressBar.progressProperty().bind(task.progressProperty()); + + final Scene scene = new Scene(root); + scene.getStylesheets().add("/fr/cenra/rhomeo/splashscreen.css"); + scene.setFill(new Color(0, 0, 0, 0)); + + splashStage.setScene(scene); + splashStage.show(); + + // If the task succeeded, meaning the initialisation is finished or an update has been found + // and user wants to download it. + task.setOnSucceeded(evt -> Platform.runLater(() -> { + final Object value = task.getValue(); + // If value is an URL, then it is an update to download. + // Otherwise, nothing to download keep going with the application start. + if (value instanceof URLAndMD5) { + final DownloadUpdateTask downloadTask = new DownloadUpdateTask((URLAndMD5) value); + splashScreen.uiProgressLabel.textProperty().unbind(); + splashScreen.uiProgressLabel.textProperty().bind(downloadTask.messageProperty()); + splashScreen.uiProgressBar.progressProperty().unbind(); + splashScreen.uiProgressBar.progressProperty().bind(downloadTask.progressProperty()); + + TaskManager.INSTANCE.submit(downloadTask); + + downloadTask.setOnCancelled(evt2 -> System.exit(0)); + + downloadTask.setOnFailed(evt2 -> Platform.runLater(() -> { + GeotkFX.newExceptionDialog("Le téléchargement de la mise à jour a échoué !", downloadTask.getException()).showAndWait(); + System.exit(1); + })); + + downloadTask.setOnSucceeded((evt2) -> { + Path newValue = downloadTask.getValue(); + try { + Rhomeo.openFile(newValue); + Thread.sleep(1000); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot proceed to update !", e); + } finally { + Platform.runLater(() -> splashStage.close()); + } + }); + } else { + // Launches the main window here + splashStage.toFront(); + splashScreen.uiLoadingPane.setVisible(false); + + final FadeTransition fadeSplash = new FadeTransition(Duration.seconds(1.2), root); + fadeSplash.setFromValue(1.0); + fadeSplash.setToValue(0.0); + fadeSplash.setOnFinished(actionEvent -> { + splashStage.hide(); + root.setOpacity(1.0); + try { + final Stage mainStage = createDashboardStage(); + final Preferences welcomePrefs = Preferences.userNodeForPackage(FXInitialDialogPane.class); + + final Stage popup; + if (welcomePrefs.getBoolean(FXInitialDialogPane.DISPLAY_RULE_KEY, true)) { + popup = Rhomeo.createWelcomePopup(); + popup.initOwner(mainStage); + popup.show(); + popup.requestFocus(); + } else { + popup = null; + } + + // HACK : display of both stages is done now, because if + // main stage is displayed before we've ended configuring + // popup, some properties (like resizing capabilities) + // looks shared. + mainStage.show(); + if (popup != null) { + popup.show(); + popup.requestFocus(); + } + + } catch (Throwable ex) { + try { + RhomeoCore.LOGGER.log(Level.WARNING, "Erreur inattendue lors de l'initialisation du panneau principal.", ex); + final ExceptionDialog exDialog = GeotkFX.newExceptionDialog("L'application a rencontré une erreur inattendue et doit fermer.", ex); + exDialog.setOnHidden((DialogEvent de) -> System.exit(1)); + exDialog.show(); + } catch (Throwable e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot show error dialog to user", e); + System.exit(1); + } + } + }); + fadeSplash.play(); + } + })); + + task.setOnCancelled(evt -> { + System.exit(0); + }); + + // Task cancelled, because of an error or a user cancellation action. + task.setOnFailed(evt -> { + splashScreen.uiProgressLabel.getStyleClass().remove("label"); + splashScreen.uiProgressLabel.getStyleClass().add("label-error"); + splashScreen.uiCancel.setVisible(true); + }); + } + + /** + * Show the updates confirmation window. User is asked if the update should be downloaded. + * + * @param newVersion The update version. + * @return {@code true} if the user wants to apply the update, {@code false} otherwise. + * @throws ExecutionException + * @throws InterruptedException + */ + private boolean showUpdatesStage(final UpdateInfo newVersion) throws ExecutionException, InterruptedException { + // Now that we found that an update is available, we can ask the user if the update should be downloaded. + final Task askForUpdate = new Task() { + + @Override + protected Boolean call() throws Exception { + final StringBuilder builder = new StringBuilder() + .append("Version installée : ").append(Rhomeo.getVersion()) + .append(System.lineSeparator()) + .append("Version de la mise à jour : ").append(newVersion.getVersion()) + .append(System.lineSeparator()) + .append("Date de publication : ").append(newVersion.getDate() == null ? "Inconnue" : newVersion.getDate()) + .append(System.lineSeparator()); + + final String[] releaseNote = newVersion.getReleaseNote(); + if (releaseNote != null && releaseNote.length > 0) { + builder.append("Release-note : "); + for (final String str : releaseNote) { + builder.append(System.lineSeparator()).append(str); + } + } + + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, + builder.toString(), ButtonType.NO, ButtonType.YES); + alert.setHeaderText("Une mise à jour est disponible. Télécharger la mise à jour ?"); + final TextArea infoArea = new TextArea(builder.toString()); + infoArea.setEditable(false); + infoArea.setPrefSize(666, 444); + alert.setResizable(true); + alert.getDialogPane().setContent(infoArea); + final Optional choice = alert.showAndWait(); + + return ButtonType.YES.equals(choice.orElse(ButtonType.NO)); + } + }; + Platform.runLater(() -> askForUpdate.run()); + return Boolean.TRUE.equals(askForUpdate.get()); + } + + + /** + * Main dashboard stage. Will be opened when the application will be launched. + * + * @return The main stage, never {@code null} + */ + private Stage createDashboardStage() { + final Stage mainStage = Rhomeo.newStage(); + mainStage.setOnCloseRequest(event -> System.exit(0)); + final FXMainPane mainPane = Session.getInstance().getBean(FXMainPane.class); + final Scene scene = new Scene(mainPane); + scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH); + mainStage.setScene(scene); + mainStage.initModality(Modality.NONE); + mainStage.setMinWidth(800); + mainStage.setMinHeight(600); + mainStage.setResizable(true); + mainStage.setMaximized(true); + return mainStage; + } + + /** + * + * @return + */ + private URLAndMD5 checkUpdate() throws Exception { + final UpdateInfo newVersion = RhomeoCore.existingUpdate(); + if (newVersion != null) { + final URL updateURL; + final String md5; + if (PlatformUtil.isWindows()) { + updateURL = newVersion.getWin32(); + md5 = newVersion.getWin32MD5(); + } else if (PlatformUtil.isMac()) { + updateURL = newVersion.getMacOS64(); + md5 = newVersion.getMacOS64MD5(); + } else if (PlatformUtil.isLinux()) { + if (newVersion.getDeb64() != null && isDeb()) { + updateURL = newVersion.getDeb64(); + md5 = newVersion.getDeb64MD5(); + } else { + updateURL = newVersion.getRpm64(); + md5 = newVersion.getRpm64MD5(); + } + } else { + updateURL = null; + md5 = null; + } + + if (updateURL != null && showUpdatesStage(newVersion)) { + return new URLAndMD5(updateURL, md5); + } + } + + return null; + } + + private boolean isDeb() { + try { + return Runtime.getRuntime().exec("dpkg --version").waitFor() == 0; + } catch (InterruptedException|IOException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot detect if the system is a dpkg or RPM based OS.", ex); + } + + return false; + } + + /** + * Initial loading task, to follow with a progress bar. + * If an update has been detected, returns the download url. + */ + private final class InitialLoadingTask extends Task { + /** + * Launched when this task will be called. + * + * @return Always return {@code null}. + * @throws Exception + */ + @Override + protected Object call() throws Exception { + final int total = 4; + int inc = 0; + + try { + // CHECK UPDATES /////////////////////////////////////////////// + updateProgress(inc++, total); + updateMessage("Vérification des mises à jour..."); + final URLAndMD5 upURL = checkUpdate(); + if (upURL != null) { + return upURL; + } + + // EPSG DATABASE /////////////////////////////////////////////// + updateProgress(inc++, total); + updateMessage("Création de la base de données de projections cartographiques (EPSG) ..."); + // try to create it, won't do anything if already exist + RhomeoCore.initEpsgDB(); + + // SPRING CONTEXT ////////////////////////////////////////////// + updateProgress(inc++, total); + updateMessage("Chargement du contexte de l'application ..."); + new ClassPathXmlApplicationContext(RhomeoCore.SPRING_CONTEXT_XML); + + // RESTORE APPLICATION STATE /////////////////////////////////// + updateProgress(inc++, total); + updateMessage("Récupération des données..."); + Session.getInstance().getBean(StateManager.class).restoreState(); + + // END + updateProgress(total, total); + updateMessage("Chargement terminé"); + Thread.sleep(400); + } catch (Throwable ex) { + updateProgress(-1, -1); + updateMessage("Erreur inattendue : ".concat(ex.getLocalizedMessage())); + RhomeoCore.LOGGER.log(Level.WARNING, ex.getMessage(), ex); + + throw ex; + } + + return null; + } + } + + /** + * Application update downloader. + * When completed, returns the {@linkplain Path downloaded file}. + */ + private final class DownloadUpdateTask extends Task { + private final URLAndMD5 url; + + public DownloadUpdateTask(final URLAndMD5 url) { + this.url = url; + } + + /** + * Download the new update. + * + * @return The {@linkplain Path downloaded update} + * @throws Exception + */ + @Override + protected Path call() throws Exception { + updateMessage("Téléchargement de la mise à jour en cours ..."); + + String completeURL = url.url.toExternalForm(); + final Matcher matcher = RhomeoCore.EXT_PATTERN.matcher(completeURL); + + final Path tmpFile = Files.createTempFile("tmpInstall", matcher.find()? matcher.group() : ".tmp"); + + // Now download the update in the appropriate file + final URLConnection bundleConnec = new NetPreferences().openConnection(url.url); + bundleConnec.setConnectTimeout(RhomeoCore.CONNECTION_TIMEOUT); + bundleConnec.setReadTimeout(RhomeoCore.READ_TIMEOUT); + final long totalLength = bundleConnec.getContentLengthLong(); + long downloaded = 0; + final String totalReadableSize = RhomeoCore.toReadableSize(totalLength); + final MessageDigest digest = MessageDigest.getInstance("md5"); + try (final InputStream input = bundleConnec.getInputStream(); + final OutputStream output = Files.newOutputStream(tmpFile)) { + int readBytes; + final byte[] buffer = new byte[4096]; + while ((readBytes = input.read(buffer)) >= 0) { + output.write(buffer, 0, readBytes); + digest.update(buffer, 0, readBytes); + downloaded += readBytes; + updateProgress(downloaded, totalLength); + updateMessage("Téléchargement "+ RhomeoCore.toReadableSize(downloaded) + " / " + totalReadableSize); + } + output.flush(); + } + + // Verify that the content was fully downloaded + if (downloaded != totalLength) { + throw new IOException("La mise à jour n'a pas été entièrement téléchargée"); + } + + // MD5 checksum + if (url.md5Base64 != null && !url.md5Base64.isEmpty()) { + byte[] computed = digest.digest(); + if (!url.md5Base64.equals(new BigInteger(1, computed).toString(16))) { + throw new IOException("Downloaded file checksum is invalid !"); + } + } + + updateMessage("Mise à jour téléchargée. Veuillez cliquer sur le bouton et l'installer."); + return tmpFile; + } + } + + private static class URLAndMD5 { + final URL url; + final String md5Base64; + + public URLAndMD5(final URL packageURL, final String base64MD5) { + this.url = packageURL; + this.md5Base64 = base64MD5; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/Rhomeo.java b/desktop/src/main/java/fr/cenra/rhomeo/Rhomeo.java new file mode 100644 index 0000000..d458e74 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/Rhomeo.java @@ -0,0 +1,758 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo; + +import com.sun.javafx.PlatformUtil; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.fx.FXInitialDialogPane; +import java.awt.Color; +import java.awt.Desktop; +import java.awt.Font; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import java.util.zip.ZipOutputStream; + +import javafx.application.Platform; +import javafx.beans.property.ReadOnlyProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingFXUtils; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.stage.FileChooser; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.util.StringConverter; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.font.FontAwesomeIcons; +import org.geotoolkit.font.IconBuilder; +import org.geotoolkit.gui.javafx.util.ComboBoxCompletion; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.nio.ZipUtilities; + +/** + * Regroups methods used by the application for handling JavaFX features. + * + * @author Cédric Briançon (Geomatys) + * @see RhomeoCore + */ +public final class Rhomeo extends RhomeoCore { + /** + * Application icon. + */ + public static final Image ICON = new Image(Rhomeo.class.getResource("/fr/cenra/rhomeo/images/logo.png").toString(), 16, 16, true, false); + + public static final Image ICON_NOT_PUBLISHED = new Image(Rhomeo.class.getResource("/fr/cenra/rhomeo/images/not_published.png").toString(), + 16, 16, true, true); + + public static final Color GRAY_SITE = Color.decode("#898989"); + public static final Color GREEN_SITE = Color.decode("#9CB35B"); + public static final Color BLUE_SITE = Color.decode("#5B8AA4"); + public static final Color RED_SITE = Color.decode("#D85F70"); + public static final Color ORANGE_SITE = Color.decode("#FFA500"); + + public static final Font PROGRESS_MONITOR_ICON_FONT = IconBuilder.FONT.deriveFont(16f); + + public static final Image ICON_BARS_WHITE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_BARS,24, Color.WHITE),null); + public static final Image ICON_CARET_UP = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CARET_UP,16, GRAY_SITE),null); + public static final Image ICON_CARET_DOWN = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CARET_DOWN,16, GRAY_SITE),null); + public static final Image ICON_CHECK_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CHECK,16, GRAY_SITE),null); + public static final Image ICON_CHECK_GREEN = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CHECK,16, GREEN_SITE),null); + public static final Image ICON_CIRCLE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_CIRCLE,8, GRAY_SITE),null); + public static final Image ICON_COG = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_COG,24, GRAY_SITE),null); + public static final Image ICON_DOWNLOAD_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_DOWNLOAD,24, BLUE_SITE),null); + public static final Image ICON_EXCHANGE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_EXCHANGE,16, GRAY_SITE),null); + public static final Image ICON_EYE_GRAY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_EYE,16, GRAY_SITE),null); + public static final Image ICON_FILE_PDF = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_FILE_PDF_O,24, RED_SITE),null); + public static final Image ICON_FILE_TEXT = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_FILE_TEXT_O,24, BLUE_SITE),null); + public static final Image ICON_INFO_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_INFO_CIRCLE,24, BLUE_SITE),null); + public static final Image ICON_INFO_GREY = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_INFO_CIRCLE,24, GRAY_SITE),null); + public static final Image ICON_LONG_ARROW_RIGHT = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_LONG_ARROW_RIGHT,48, GRAY_SITE),null); + public static final Image ICON_PENCIL_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_PENCIL,24, BLUE_SITE),null); + public static final Image ICON_PENCIL_WHITE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_PENCIL,24, Color.WHITE),null); + public static final Image ICON_STOP_RED = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_STOP,16, RED_SITE),null); + public static final Image ICON_TIMES_RED = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_TIMES,16, RED_SITE),null); + public static final Image ICON_TRASH_BLUE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_TRASH_O,24, BLUE_SITE),null); + public static final Image ICON_WARNING_ORANGE = SwingFXUtils.toFXImage(IconBuilder.createImage(FontAwesomeIcons.ICON_WARNING_ALIAS,24, ORANGE_SITE),null); + + public static final String CSS_FONT_AWESOME = "rhomeo-font-awesome"; + public static final String CSS_ERROR_FIELD = "error-field"; + public static final String CSS_WARNING_FIELD = "warning-field"; + public static final String CSS_INDICATOR_LABEL = "indicator-label"; + public static final String CSS_CURRENT_STEP = "current-step"; + public static final String CSS_PROTOCOL_BUTTON = "protocol-button"; + public static final String CSS_PROTOCOL_POPUP_BODY = "protocol-popup-body"; + + /** + * Main theme path + */ + public static final String CSS_THEME_PATH = "/fr/cenra/rhomeo/theme.css"; + + private static final ChangeListener SPINNER_LISTENER = Rhomeo::spinnerTextChanged; + /** + * Generates a stage with {@linkplain RhomeoCore#APPLICATION_TITLE application title} and + * {@linkplain #ICON application icon}. + * + * @return a new Rhomeo stage, never {@code null} + */ + public static Stage newStage() { + final Stage rhomeoStage = new Stage(); + rhomeoStage.getIcons().add(ICON); + final String version = RhomeoCore.getVersion(); + final String title = (version == null || version.isEmpty()) ? APPLICATION_TITLE : APPLICATION_TITLE +" ("+ version +")"; + rhomeoStage.setTitle(title); + return rhomeoStage; + } + + /** + * Generates the initial dialog. Will be shown when launching the application. + * + * @return The dialog stage, never {@code null} + */ + public static Stage createWelcomePopup() { + final Stage stage = Rhomeo.newStage(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setResizable(true); + stage.setTitle("Accueil"); + final FXInitialDialogPane dialog = new FXInitialDialogPane(); + final Scene scene = new Scene(dialog); + scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH); + stage.setScene(scene); + stage.sizeToScene(); + return stage; + } + + public static void loadFXML(Parent candidate) { + final Class modelClass = null; + loadFXML(candidate, modelClass); + } + + /** + * Load FXML document matching input controller. If a model class is given, + * we'll try to load a bundle for text internationalization. + * @param candidate The controller object to get FXMl for. + * @param bundleClass A class which will be used for bundle loading. + */ + public static void loadFXML(final Parent candidate, final Class bundleClass) { + ResourceBundle bundle = null; + if (bundleClass != null) { + try{ + bundle = ResourceBundle.getBundle(bundleClass.getName(), Locale.FRENCH, + Thread.currentThread().getContextClassLoader()); + }catch(MissingResourceException ex){ + LOGGER.log(Level.INFO, "Missing bundle for : {0}", bundleClass.getName()); + } + } + loadFXML(candidate, bundle); + } + + public static void loadFXML(final Parent candidate, final ResourceBundle bundle) { + loadFXML(candidate, candidate.getClass(), bundle); + } + + public static void loadFXML(final Parent candidate, final Class fxmlClass, final ResourceBundle bundle) { + ArgumentChecks.ensureNonNull("JavaFX controller object", candidate); + final String fxmlpath = "/"+fxmlClass.getName().replace('.', '/')+".fxml"; + final URL resource = fxmlClass.getResource(fxmlpath); + if (resource == null) { + throw new RuntimeException("No FXML document can be found for path : "+fxmlpath); + } + final FXMLLoader loader = new FXMLLoader(resource); + loader.setController(candidate); + loader.setRoot(candidate); + //in special environement like osgi or other, we must use the proper class loaders + //not necessarly the one which loaded the FXMLLoader class + loader.setClassLoader(fxmlClass.getClassLoader()); + + if(bundle!=null) loader.setResources(bundle); + + fxRunAndWait(() -> { + try { + loader.load(); + } catch (IOException ex) { + throw new IllegalArgumentException(ex.getMessage(), ex); + } + }); + } + + /** + * Run given task in FX application thread (immediately if we're already in it), + * and wait for its result before returning. + * + * @param Return TYPE of the input task. + * @param toRun Task to run in JavaFX thread. + * @return Result of the input task. + */ + public static T fxRunAndWait(final Callable toRun) { + return fxRunAndWait(new TaskManager.MockTask<>(toRun)); + } + + /** + * + * @param toRun The task to run. + */ + public static void fxRunAndWait(final Runnable toRun) { + fxRunAndWait(new TaskManager.MockTask(toRun)); + } + + /** + * Run given task in FX application thread (immediately if we're already in it), + * and wait for its result before returning. + * + * @param Return TYPE of the input task. + * @param toRun Task to run in JavaFX thread. + * @return Result of the input task. + */ + public static T fxRunAndWait(final Task toRun) { + return fxRun(true, toRun); + } + + /** + * Run given task in FX application thread (immediately if we're already in it). + * According to input boolean, we will return immediately or wait for the task to + * be over. + * @param wait True if we must wait for the task to end before returning, false + * to return immediately after submission. + * @param toRun The task to run into JavaFX application thread. + */ + public static void fxRun(final boolean wait, final Runnable toRun) { + fxRun(wait, new TaskManager.MockTask(toRun)); + } + + /** + * Run given task in FX application thread (immediately if we're already in it). + * According to input boolean, we will return immediately or wait for the task to + * be over. + * @param Return TYPE of input task. + * @param wait True if we must wait for the task to end before returning, false + * to return immediately after submission. + * @return The task return value if we must wait, or we're in platform thread. Otherwise null. + * @param toRun The task to run into JavaFX application thread. + */ + public static T fxRun(final boolean wait, final Callable toRun) { + return fxRun(wait, new TaskManager.MockTask<>(toRun)); + } + + /** + * Run given task in FX application thread (immediately if we're already in it). + * According to input boolean, we will return immediately or wait for the task to + * be over. + * @param Return TYPE of input task. + * @param wait True if we must wait for the task to end before returning, false + * to return immediately after submission. + * @return The task return value if we must wait, or we're in platform thread. Otherwise null. + * @param toRun The task to run into JavaFX application thread. + */ + public static T fxRun(final boolean wait, final Task toRun) { + if (Platform.isFxApplicationThread()) + toRun.run(); + else + Platform.runLater(toRun); + + if (wait || Platform.isFxApplicationThread()) { + try { + return toRun.get(); + } catch (RuntimeException ex) { + throw ex; + } catch (ExecutionException ex) { + if (ex.getCause() instanceof RuntimeException) { + throw (RuntimeException) ex.getCause(); + } else { + throw new RhomeoRuntimeException(ex.getCause()); + } + } catch (Exception e) { + throw new RhomeoRuntimeException(e); + } + } else + return null; + } + + /** + * Try to open given file on system. + * @param toOpen The file open on underlying system. + * @return True if we succeeded opening file on system, false otherwise. + */ + public static Task openFile(final Path toOpen) { + return openFile(toOpen.toAbsolutePath().toFile()); + } + + /** + * Try to open given file on system. + * @param toOpen The file open on underlying system. + * @return True if we succeeded opening file on system, false otherwise. + */ + public static Task openFile(final File toOpen) { + return TaskManager.INSTANCE.submit("Ouverture d'un fichier", () -> { + try { + final Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.OPEN)) { + desktop.open(toOpen); + } else if (desktop.isSupported(Desktop.Action.EDIT)) { + desktop.edit(toOpen); + } else { + throw new IOException("Impossible de communiquer avec l'OS."); + } + } catch (IOException e) { + /* + * HACK : on Windows, it seems that some file associations cannot be found + * using above method (Ex : .odt), so we try running following command as + * a fallback. + */ + if (PlatformUtil.isWindows()) { + Runtime.getRuntime().exec(new String[]{"cmd /c start", toOpen.toURI().toString()}); + } else { + throw e; + } + } + return true; + }); + } + + public static Task browse(final URL href) { + return TaskManager.INSTANCE.submit("Ouverture d'un lien", () -> { + final Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.BROWSE)) + desktop.browse(href.toURI()); + else + throw new IOException("Impossible de communiquer avec l'OS."); + + return true; + }); + } + + public static Task mail(final String mailAdress) { + return TaskManager.INSTANCE.submit("Ouverture d'un lien e-mail", () -> { + final Desktop desktop = Desktop.getDesktop(); + if (desktop.isSupported(Desktop.Action.MAIL)) + desktop.mail(new URI(mailAdress.startsWith("mailto")? mailAdress : "mailto:".concat(mailAdress))); + else + throw new IOException("Impossible de communiquer avec l'OS."); + + return true; + }); + } + + /** + * Initializes an auto completable combo box, without selecting one. + * + * @param + * @param comboBox + * @param items + * @see #initComboBox(ComboBox, ObservableList, Object) + */ + public static void initComboBox(final ComboBox comboBox, final ObservableList items) { + initComboBox(comboBox, items, null); + } + + /** + * Add items in the combo box and make it auto completable. + * + * @param comboBox A new combo box, not {@code null}. + * @param items List of items to add. + * @param itemSelected If not null, select this item in the list. + * @param item class + */ + public static void initComboBox(final ComboBox comboBox, final ObservableList items, final I itemSelected) { + comboBox.setItems(items); + try { + comboBox.setEditable(true); + } catch (RuntimeException re) { + // A runtime exception is launched if the editable property is already bound + // Just log and let the property bound and handled by something else. + Rhomeo.LOGGER.log(Level.FINE, re.getLocalizedMessage(), re); + } + if (itemSelected != null) { + comboBox.getSelectionModel().select(itemSelected); + } + ComboBoxCompletion.autocomplete(comboBox); + } + + + /** + * Generates an alert of the given type with the given text and just an OK button. + * + * @param type {@linkplain Alert.AlertType alert type}. + * @param text Text to display. + */ + public static void showAlert(final Alert.AlertType type, final String text) { + final Alert alert = new Alert(type, text, ButtonType.OK); + alert.setResizable(true); + alert.show(); + } + + /** + * Get the previous path stored for reopening a file chooser in it. + * + * @param packageClass Class in a specific java package on which we want to attach + * the path preference + * @return the previous path stored with the last opening of the file chooser. + */ + public static File getPreviousPath(final Class packageClass) { + final Preferences prefs = Preferences.userNodeForPackage(packageClass); + final String str = prefs.get("path", null); + if (str != null) { + final File file = new File(str); + if (file.isDirectory()) { + return file; + } + } + return null; + } + + /** + * Set the path of the previously selected file into the file chooser. + * + * @param packageClass Class in a specific java package on which we want to attach + * the path preference + * @param path the path to store. Should point on a folder. + */ + public static void setPreviousPath(final Class packageClass, final File path) { + final Preferences prefs = Preferences.userNodeForPackage(packageClass); + prefs.put("path", path.getAbsolutePath()); + } + + /** + * Open modal popup asking for a file on the local path. + * + * @param initialFileName Initial file name to use. + * @param owner The owner of this popup. + * @param filters Filters to use. Possibly empty or null. The first item will be used as the default one. + * @return The file wished, or {@code null} if the user cancelled the popup. + */ + public static File askOutputFilePopup(final String initialFileName, final Node owner, final FileChooser.ExtensionFilter... filters) { + final FileChooser fileChooser = new FileChooser(); + + final File previousPath = getPreviousPath(ExportUtils.class); + if(previousPath!=null && previousPath.exists()){ + fileChooser.setInitialDirectory(previousPath); + } + + // Set some usefull filters + if (filters != null && filters.length > 0) { + fileChooser.getExtensionFilters().addAll(filters); + fileChooser.setSelectedExtensionFilter(filters[0]); + } + + if (initialFileName != null && !initialFileName.isEmpty()) { + fileChooser.setInitialFileName(initialFileName); + } + + final File outputFile = fileChooser.showSaveDialog(owner.getScene().getWindow()); + + if(outputFile==null) return null; + setPreviousPath(ExportUtils.class, outputFile.getParentFile()); + + return outputFile; + } + + /** + * Try to find a combobox under given node scene tree. + * @param source The node to search into. + * @return The first found combobox, or nothing. + */ + public static Optional findComboBox(final Node source) { + if (source instanceof ComboBox) { + return Optional.of((ComboBox)source); + } else if (source instanceof Parent) { + final ObservableList nodes = ((Parent)source).getChildrenUnmodifiable(); + Optional opt; + for (final Node n : nodes) { + opt = findComboBox(n); + if (opt.isPresent()) + return opt; + } + } + + return Optional.empty(); + } + + public static void prepareSpinners(final Node source) { + if (source instanceof Spinner) { + final Spinner spinner = (Spinner) source; + spinner.getEditor().textProperty().removeListener(SPINNER_LISTENER); + spinner.getEditor().textProperty().addListener(SPINNER_LISTENER); + final SpinnerValueFactory valueFactory = spinner.getValueFactory(); + if (valueFactory instanceof SpinnerValueFactory.DoubleSpinnerValueFactory) { + final SpinnerValueFactory.DoubleSpinnerValueFactory dvf = (SpinnerValueFactory.DoubleSpinnerValueFactory) valueFactory; + dvf.setAmountToStepBy(0.01); + dvf.setConverter(new SpinnerConverter(spinner)); + // HACK : To force display refresh immediately, we introduce temporary value change. + final Double oldValue = dvf.getValue(); + if (oldValue == null || oldValue.isNaN() || oldValue.isInfinite()) + dvf.setValue(0.0); + else dvf.setValue(oldValue + 1); + dvf.setValue(oldValue); + } + + } else if (source instanceof Parent) { + final ObservableList nodes = ((Parent)source).getChildrenUnmodifiable(); + for (final Node n : nodes) { + prepareSpinners(n); + } + } + } + + private static void spinnerTextChanged(final ObservableValue obs, final String oldValue, final String newValue) { + if (obs instanceof ReadOnlyProperty) { + final Object bean = ((ReadOnlyProperty)obs).getBean(); + if (bean instanceof Node) { + Parent parent = ((Node) bean).getParent(); + if (parent instanceof Spinner) { + final Spinner spinner = (Spinner) parent; + try { + spinner.getValueFactory().setValue(spinner.getValueFactory().getConverter().fromString(newValue)); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.FINE, "Cannot convert seized text to numeric value", e); + } + } + } + } + } + + /** + * Check column and row indices to determine if we must edit next column or + * next row. If next cell is not editable, we test the next one, etc. + * + * @param editingCell Cell currently edited. If null, this method does nothing. + */ + public static void editNextCell(final TablePosition editingCell) { + if (editingCell == null) + return; + final TableView tableView = editingCell.getTableView(); + TableColumn targetCol; + int rowIndex = editingCell.getRow(); + int colIndex = editingCell.getColumn(); + final int maxRow = tableView.getItems().size() - 1; + final int maxCol = tableView.getColumns().size() - 1; + do { + if (colIndex >= maxCol) { + rowIndex++; + colIndex = 0; + } else { + colIndex++; + } + + if (rowIndex < 0 || rowIndex > maxRow) { + targetCol = null; + } else { + targetCol = tableView.getColumns().get(colIndex); + } + + } while (targetCol != null && !targetCol.isEditable()); + + tableView.edit(rowIndex, targetCol); + } + + public static Button createProtocolDocumentButton(final Protocol protocol) { + final Button button = new Button("Fiche protocole "+ protocol.getName()); + button.setGraphic(new ImageView(ICON_FILE_PDF)); + button.getStyleClass().add("transparent"); + button.setOnAction(event -> { + final String fileName = protocol.getName() + ".pdf"; + Session.getInstance().getBean(DocManager.class).openDocument(protocol.getClass().getResource(fileName)); + }); + return button; + } + + public static Button createModelButton(final Protocol protocol) { + final Button button = new Button("Format protocole "+ protocol.getRemarks() +" ("+ protocol.getName() +")"); + button.setGraphic(new ImageView(ICON_FILE_TEXT)); + button.getStyleClass().add("transparent"); + button.setOnAction(event -> generateModelZip(protocol, button)); + return button; + } + + public static void generateModelZip(final Protocol protocol, final Node owner) { + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(protocol.getName() +".zip", owner, zipFilter); + if (outputFile == null) { + return; + } + + final Path parentPath = Paths.get(outputFile.getParentFile().getAbsolutePath()); + final UUID uuid = UUID.randomUUID(); + final Path folderPath = parentPath.resolve(uuid.toString()); + + final Class statementClass = protocol.getDataType(); + try { + Files.createDirectory(folderPath); + + final Path formatPath = folderPath.resolve("format.csv"); + final CSVEncoder encoder = new CSVEncoder<>(formatPath, statementClass); + encoder.encode(new ArrayList<>(), true); + + final Path readmePath = createReadMeFile(statementClass, folderPath); + ZipUtilities.zip(outputFile.toPath(), ZipOutputStream.DEFLATED, 9, null, formatPath, readmePath); + + Files.deleteIfExists(formatPath); + Files.deleteIfExists(readmePath); + Files.deleteIfExists(folderPath); + } catch (IOException | IntrospectionException ex) { + Rhomeo.LOGGER.log(Level.INFO, "Cannot generate format file", ex); + } + } + + private static Path createReadMeFile(final Class statementClass, final Path outputFolderPath) throws IOException, IntrospectionException { + final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(statementClass); + + final Path formatPath = outputFolderPath.resolve("readme.txt"); + Files.createFile(formatPath); + String description = null; + try (final BufferedWriter bw = Files.newBufferedWriter(formatPath, StandardOpenOption.APPEND)) { + final BeanInfo beanInfo = Introspector.getBeanInfo(statementClass, Object.class); + for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + if (bundleAvailable) { + try { + description = InternationalResource.getResourceString(statementClass, desc.getName(), "tooltip"); + } catch (MissingResourceException ex) { + description = null; + } + if (description == null || description.isEmpty()) { + try { + description = InternationalResource.getResourceString(statementClass, desc.getName(), "label"); + } catch (MissingResourceException ex) { + description = null; + } + } + bw.write(desc.getName()); + bw.write("="); + if (description == null || description.isEmpty()) { + Rhomeo.LOGGER.log(Level.WARNING, "No resources for key " + desc.getName() + ".label or " + desc.getName() + ".tooltip in bundle for class " + + statementClass.getCanonicalName()); + } else { + bw.write(description); + } + bw.newLine(); + } + } + + // Check if additional content is available for the format readme. + try (final InputStream in = statementClass.getResourceAsStream("readme.additional"); + final InputStreamReader tmpReader = new InputStreamReader(in, StandardCharsets.UTF_8); + final BufferedReader reader = new BufferedReader(tmpReader)) { + + String line; + while ((line = reader.readLine()) != null) { + bw.newLine(); + bw.write(line); + } + + } catch (NullPointerException e) { + LOGGER.log(Level.FINE, "No additional text defined for data description.", e); + } + } + return formatPath; + } + + /** + * A special string converter for Spinners. The aim is to avoid changes in + * spinner's editor when value is updated, but matches editor text. Because + * without any check, spinner will force editor text, which could bother + * user seizing. + */ + private static class SpinnerConverter extends StringConverter { + + private final Spinner target; + + public SpinnerConverter(Spinner target) { + this.target = target; + } + + @Override + public String toString(Double object) { + final String text = target.getEditor().getText(); + Object fromString = DOUBLE_CONVERTER.fromString(text); + if ((fromString instanceof Number) && (target.getValue() instanceof Number) && ((Number)fromString).doubleValue() == ((Number)target.getValue()).doubleValue()) + return text; + else return DOUBLE_CONVERTER.toString(object); + } + + @Override + public Double fromString(String string) { + return (Double) DOUBLE_CONVERTER.fromString(string); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardMenuPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardMenuPane.java new file mode 100644 index 0000000..38dcf51 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardMenuPane.java @@ -0,0 +1,1282 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.LinearRing; +import com.vividsolutions.jts.geom.MultiPolygon; +import com.vividsolutions.jts.geom.Polygon; +import fr.cenra.rhomeo.DocManager; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.BeanUtils; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.data.county.County; +import fr.cenra.rhomeo.core.list.HumidZoneType; +import fr.cenra.rhomeo.core.list.OdonateZoneBio; +import fr.cenra.rhomeo.core.list.OrthoptereZoneBio; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.core.data.site.SiteImpl; +import fr.cenra.rhomeo.core.data.site.DuplicatedKeyException; +import fr.cenra.rhomeo.core.data.site.SiteRepository; + +import java.beans.IntrospectionException; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.logging.Level; +import java.util.regex.Matcher; +import javafx.application.Platform; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.concurrent.Task; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.TextField; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.stage.FileChooser; +import javafx.stage.Modality; +import javafx.stage.PopupWindow; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.StringConverter; +import javax.annotation.PostConstruct; +import javax.xml.stream.XMLStreamException; +import org.apache.sis.storage.DataStoreException; +import org.controlsfx.control.StatusBar; +import org.geotoolkit.data.FeatureCollection; +import org.geotoolkit.data.FeatureIterator; +import org.geotoolkit.data.FeatureStoreUtilities; +import org.geotoolkit.data.bean.BeanFeature; +import org.geotoolkit.data.kml.model.KmlException; +import org.geotoolkit.data.query.Query; +import org.geotoolkit.data.query.QueryBuilder; +import org.geotoolkit.data.shapefile.ShapefileFeatureStore; +import org.geotoolkit.display2d.GO2Utilities; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.feature.Property; +import org.geotoolkit.gui.javafx.layer.FXFeatureTable; +import org.geotoolkit.gui.javafx.render2d.FXCoordinateBar; +import org.geotoolkit.gui.javafx.render2d.FXGeoToolBar; +import org.geotoolkit.gui.javafx.render2d.FXMap; +import org.geotoolkit.gui.javafx.render2d.FXNavigationBar; +import org.geotoolkit.gui.javafx.render2d.FXScaleBarDecoration; +import org.geotoolkit.gui.javafx.render2d.navigation.FXPanHandler; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.map.FeatureMapLayer; +import org.geotoolkit.map.MapBuilder; +import org.geotoolkit.map.MapContext; +import org.geotoolkit.map.MapLayer; +import org.opengis.filter.Id; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.TransformException; +import org.opengis.util.FactoryException; +import org.opengis.util.GenericName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import static fr.cenra.rhomeo.core.RhomeoCore.CODE_PATTERN; +import fr.cenra.rhomeo.core.data.county.CountyRepository; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import javafx.beans.binding.Bindings; +import org.apache.sis.referencing.CommonCRS; +import org.geotoolkit.data.kml.model.AbstractGeometry; +import org.geotoolkit.data.kml.model.Kml; +import org.geotoolkit.data.kml.model.MultiGeometry; +import org.geotoolkit.data.kml.xml.KmlReader; +import org.geotoolkit.feature.FeatureBuilder; +import org.geotoolkit.feature.FeatureTypeBuilder; +import org.geotoolkit.feature.type.AttributeDescriptor; +import org.geotoolkit.feature.type.FeatureType; +import org.geotoolkit.feature.type.PropertyDescriptor; +import org.geotoolkit.geometry.jts.JTS; + +/** + * Dashboard panel menu handling protocol and site choice. + * Will be put in the top part of the {@link FXDashboardPane}. + * + * @author Cédric Briançon (Geomatys) + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class FXDashboardMenuPane extends BorderPane implements InternationalResource { + + @FXML private ImageView uiConfigSiteImg; + @FXML private ImageView uiCaretSiteImg; + @FXML private Label uiSelectedSiteLbl; + + /** + * Protocols banner. + */ + @FXML private HBox protocolsBannerBox; + + /** + * Selected site banner. + */ + @FXML private ComboBox uiSitesList; + @FXML private ToggleButton uiInfoSiteToggleBtn; + @FXML private BorderPane uiProtocolSitePane; + @FXML private HBox uiToCollapseButtonsSiteHbox; + @FXML private ToggleButton uiNewSiteToggleBtn; + @FXML private BorderPane uiSiteInfoPane; + + @FXML private GridPane uiInfoSiteGrid; + + /** + * Fields for info site form. + */ + @FXML private TextField uiNameSiteTxt; + @FXML private TextField uiReferentSiteTxt; + @FXML private Button uiImportContourBtn; + @FXML private ComboBox uiDepartmentSiteComboBox; + @FXML private TextField uiStructureSiteTxt; + @FXML private ComboBox uiTypeZhSiteComboBox; + @FXML private ComboBox uiZbOdoSiteComboBox; + @FXML private ComboBox uiZbOrthoSiteComboBox; + + /** + * Buttons for selected site. + */ + @FXML private HBox uiInfoSelectedSiteBtnsBox; + @FXML private ToggleButton uiEditSelectedSiteToggleBtn; + @FXML private Button uiDownloadSelectedSiteBtn; + @FXML private Button uiDeleteSelectedSiteBtn; + + /** + * Action buttons on selected / new site. + */ + @FXML private Button uiCancelBtn; + @FXML private Button uiCreateSiteBtn; + + @Autowired + private Session session; + + @Autowired + private SiteRepository siteRepository; + + @Autowired(required = false) + private CountyRepository countyRepository; + + @Autowired + private DocManager docManager; + + /** + * Geometry of the selected / new site. + */ + private final ObjectProperty geomToAdd = new SimpleObjectProperty<>(); + + /** + * Selected site property. + */ + private final ObjectProperty selectedSiteProperty; + + /** + * List of loaded counties. Can be null if an error happens while loading counties. + */ + private Map counties; + + private final BooleanBinding odoTypeFilled; + private final BooleanBinding odoTypeNotEditable; + private final BooleanBinding odoTypeNotEditableAndNotNew; + private final BooleanBinding orthoTypeFilled; + private final BooleanBinding orthoTypeEditable; + private final BooleanBinding orthoTypeEditableOrNewSite; + private final BooleanBinding notEditing; + private final BooleanBinding notNewSite; + + public FXDashboardMenuPane() { + Rhomeo.loadFXML(this, FXDashboardMenuPane.class); + + uiConfigSiteImg.setImage(Rhomeo.ICON_COG); + uiCaretSiteImg.setImage(Rhomeo.ICON_CARET_UP); + + // Stores the site panel open or not property. + final BooleanProperty showSitesPaneProperty = new SimpleBooleanProperty(true); + + uiProtocolSitePane.visibleProperty().bind(showSitesPaneProperty); + uiProtocolSitePane.managedProperty().bind(uiProtocolSitePane.visibleProperty()); + + // Bind selected site + selectedSiteProperty = new SimpleObjectProperty<>(this, "selected site"); + uiSitesList.valueProperty().addListener(this::siteNameChanged); + selectedSiteProperty.addListener(this::siteChanged); + selectedSiteProperty.addListener(((observable, oldValue, newValue) -> { + if (oldValue != null) { + uiSelectedSiteLbl.textProperty().unbind(); + } + + if (newValue != null && newValue.getName() != null && !newValue.getName().isEmpty()) { + uiSelectedSiteLbl.textProperty().bind( + new SimpleStringProperty("SITE : ").concat(uiSitesList.getEditor().textProperty())); + } else { + uiSelectedSiteLbl.setText(null); + } + })); + + // Display selected site name only if one has been selected and the site panel is collapsed. + uiSelectedSiteLbl.visibleProperty().bind( + showSitesPaneProperty.not().and(selectedSiteProperty.isNotNull())); + uiSelectedSiteLbl.managedProperty().bind(uiSelectedSiteLbl.visibleProperty()); + + notNewSite = uiNewSiteToggleBtn.selectedProperty().not(); + + // Display info site button if an existing one has been selected + uiInfoSiteToggleBtn.visibleProperty().bind(selectedSiteProperty.isNotNull().and(notNewSite)); + + // Allow to collapse site information pane + uiToCollapseButtonsSiteHbox.setOnMouseClicked(event -> { + final boolean showPane = showSitesPaneProperty.get(); + if (showPane) { + uiCaretSiteImg.setImage(Rhomeo.ICON_CARET_DOWN); + } else { + uiCaretSiteImg.setImage(Rhomeo.ICON_CARET_UP); + } + showSitesPaneProperty.setValue(!showPane); + }); + + // Sub panel that should open only if its main panel (uiProtocolSitePane) is already opened + // and either user request to create a new site or to view info on an existing one. + uiSiteInfoPane.visibleProperty().bind(uiNewSiteToggleBtn.selectedProperty().or(uiInfoSiteToggleBtn.selectedProperty().and(uiInfoSiteToggleBtn.visibleProperty()))); + uiSiteInfoPane.managedProperty().bind(uiSiteInfoPane.visibleProperty()); + + // Display action buttons only in the new site mode or editing an existing site mode + uiCreateSiteBtn.visibleProperty().bind(uiNewSiteToggleBtn.selectedProperty().or(uiEditSelectedSiteToggleBtn.selectedProperty())); + uiCreateSiteBtn.managedProperty().bind(uiCreateSiteBtn.visibleProperty()); + uiCancelBtn.visibleProperty().bind(uiCreateSiteBtn.visibleProperty()); + uiCancelBtn.managedProperty().bind(uiCancelBtn.visibleProperty()); + + /* + * When new site button state change, we initiate or cancel complete form. + */ + uiNewSiteToggleBtn.selectedProperty().addListener((obs, old, newVal) -> { + final Site selected = selectedSiteProperty.get(); + final boolean isNewSite = selected != null && (selected.getName() == null || selected.getName().trim().isEmpty()); + if (newVal) { + /* Before setting a new site, we ensure user won't lose data on + * a previously edited object. + */ + if (!isNewSite) { + if (confirmCancelling(selected)) { + final Site newSite = new SiteImpl(null); + selectedSiteProperty.set(newSite); + } else { + uiNewSiteToggleBtn.setSelected(false); // a rollback has been decided + } + } + } else { + // If the button has been de-activated, and we're editing a new + // site, we clear selection. However, if user ask for a rollback, + // we re-activate the button. + if (isNewSite) { + if (confirmCancelling(selected)) { + selectedSiteProperty.set(null); + } else { + uiNewSiteToggleBtn.setSelected(true); // a rollback has been decided + } + } + } + uiCreateSiteBtn.setText(newVal ? getResourceString("create-site") : getResourceString("save")); + uiImportContourBtn.setText(newVal ? getResourceString("import-contour") : getResourceString("view-contour")); + }); + + // Add PDF file images in the site form + final Button typeZhBtn = createPdfButton("Typologie_Habitats.pdf"); + uiInfoSiteGrid.add(typeZhBtn, 6, 1); + final Button typeZbOdonateBtn = createPdfButton("zones_biogeo_odonates.pdf"); + uiInfoSiteGrid.add(typeZbOdonateBtn, 6, 2); + final Button typeZbOrthoBtn = createPdfButton("zones_biogeo_orthopteres.pdf"); + uiInfoSiteGrid.add(typeZbOrthoBtn, 6, 3); + + // Button info for selected site + uiInfoSiteToggleBtn.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + uiCreateSiteBtn.setText(getResourceString("save")); + uiImportContourBtn.setText(getResourceString("view-contour")); + uiImportContourBtn.setGraphic(new ImageView(Rhomeo.ICON_EYE_GRAY)); + uiEditSelectedSiteToggleBtn.setSelected(false); + uiInfoSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_INFO_BLUE)); + } else { + uiInfoSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_INFO_GREY)); + } + }); + uiInfoSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_INFO_GREY)); + uiInfoSiteToggleBtn.setTooltip(new Tooltip(getResourceString("view-info-site-tooltip"))); + + // Panel containing buttons for selected site. + uiInfoSelectedSiteBtnsBox.visibleProperty().bind(uiInfoSiteToggleBtn.selectedProperty().and(notNewSite)); + uiInfoSelectedSiteBtnsBox.managedProperty().bind(uiInfoSelectedSiteBtnsBox.visibleProperty()); + // Buttons images for selected site actions. + uiDownloadSelectedSiteBtn.setGraphic(new ImageView(Rhomeo.ICON_DOWNLOAD_BLUE)); + uiDownloadSelectedSiteBtn.setTooltip(new Tooltip(getResourceString("download-selected-site-tooltip"))); + uiDeleteSelectedSiteBtn.setGraphic(new ImageView(Rhomeo.ICON_TRASH_BLUE)); + uiDeleteSelectedSiteBtn.setTooltip(new Tooltip(getResourceString("delete-selected-site-tooltip"))); + uiEditSelectedSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_PENCIL_BLUE)); + uiEditSelectedSiteToggleBtn.setTooltip(new Tooltip(getResourceString("edit-selected-site-tooltip"))); + uiEditSelectedSiteToggleBtn.selectedProperty().addListener((observable, oldValue, newValue) -> { + if (newValue) { + uiEditSelectedSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_PENCIL_WHITE)); + } else { + uiEditSelectedSiteToggleBtn.setGraphic(new ImageView(Rhomeo.ICON_PENCIL_BLUE)); + } + }); + + geomToAdd.addListener((observable, oldValue, newValue) -> { + if (uiNewSiteToggleBtn.isSelected()) { + uiImportContourBtn.setGraphic(newValue != null ? new ImageView(Rhomeo.ICON_CHECK_GRAY) : null); + } + }); + + // Bind editable form fields + notEditing = uiEditSelectedSiteToggleBtn.selectedProperty().not(); + uiNameSiteTxt.disableProperty().bind(notEditing.and(notNewSite)); + uiReferentSiteTxt.disableProperty().bind(notEditing.and(notNewSite)); + uiStructureSiteTxt.disableProperty().bind(notEditing.and(notNewSite)); + // These combo boxes are only editable in the case of a new site. + uiDepartmentSiteComboBox.disableProperty().bind(notNewSite); + uiTypeZhSiteComboBox.disableProperty().bind(notNewSite); + // The two fields below are editable until the target site values are empty (see Gitlab issue #37). + odoTypeFilled = Bindings.createBooleanBinding(() -> { + final Site ss = selectedSiteProperty.get(); + if (ss == null) + return false; + final Optional odoOpt = RhomeoCore.getByCode(OdonateZoneBio.class, ss.getOdonateType()); + return odoOpt.isPresent() && !OdonateZoneBio.ZGLOBAL.equals(odoOpt.get()); + }, selectedSiteProperty, uiZbOdoSiteComboBox.valueProperty()); + odoTypeNotEditable = notEditing.or(odoTypeFilled); + odoTypeNotEditableAndNotNew = notNewSite.and(odoTypeNotEditable); + uiZbOdoSiteComboBox.disableProperty().bind(odoTypeNotEditableAndNotNew); + orthoTypeFilled = Bindings.createBooleanBinding(() -> { + final Site ss = selectedSiteProperty.get(); + if (ss == null) + return false; + return RhomeoCore.getByCode(OrthoptereZoneBio.class, ss.getOrthoptereType()).isPresent(); + }, selectedSiteProperty, uiZbOrthoSiteComboBox.valueProperty()); + orthoTypeEditable = notEditing.or(orthoTypeFilled); + orthoTypeEditableOrNewSite = notNewSite.and(orthoTypeEditable); + uiZbOrthoSiteComboBox.disableProperty().bind(orthoTypeEditableOrNewSite); + + // Set converters to avoid bad behavior on seizure + uiTypeZhSiteComboBox.setConverter(new StringConverter() { + @Override + public String toString(HumidZoneType object) { + return object == null? null : object.toString(); + } + + @Override + public HumidZoneType fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + final Matcher matcher = CODE_PATTERN.matcher(string); + if (matcher.find()) { + return HumidZoneType.getByCode(matcher.group()); + } + + return null; + } + }); + + uiZbOdoSiteComboBox.setConverter(new StringConverter() { + @Override + public String toString(OdonateZoneBio object) { + return object == null? null : object.toString(); + } + + @Override + public OdonateZoneBio fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + final Matcher matcher = CODE_PATTERN.matcher(string); + if (matcher.find()) { + return OdonateZoneBio.getByCode(matcher.group()); + } + + return null; + } + }); + + uiZbOrthoSiteComboBox.setConverter(new StringConverter() { + @Override + public String toString(OrthoptereZoneBio object) { + return object == null? null : object.toString(); + } + + @Override + public OrthoptereZoneBio fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + final Matcher matcher = CODE_PATTERN.matcher(string); + if (matcher.find()) { + return OrthoptereZoneBio.getByCode(matcher.group()); + } + + return null; + } + }); + + // Fill lists + Rhomeo.initComboBox(uiTypeZhSiteComboBox, FXCollections.observableArrayList(HumidZoneType.values())); + Rhomeo.initComboBox(uiZbOdoSiteComboBox, FXCollections.observableArrayList(OdonateZoneBio.values())); + Rhomeo.initComboBox(uiZbOrthoSiteComboBox, FXCollections.observableArrayList(OrthoptereZoneBio.values())); + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + private void postConstruct() { + Rhomeo.initComboBox(uiSitesList, siteRepository.findNames()); + final EventHandler filter = evt -> { + if (MouseEvent.MOUSE_PRESSED.equals(evt.getEventType()) || + MouseEvent.MOUSE_CLICKED.equals(evt.getEventType())) { + final Site site = siteProperty().get(); + if (site == null || site.equals(prepareSiteFromForm(null))) + return; + + if (confirmCancelling(site)) { + selectedSiteProperty.set(null); + uiSitesList.requestFocus(); + } else { + evt.consume(); + } + } + }; + uiSitesList.addEventFilter(MouseEvent.ANY, filter); + + // Load county choice + if (countyRepository == null) { + uiDepartmentSiteComboBox.disableProperty().unbind(); + uiDepartmentSiteComboBox.setDisable(true); + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de charger la liste des départements. Il sera impossible de créer de nouveaux sites."); + } else { + counties = countyRepository.getAll(); + final String countyCode = siteProperty().get() == null ? null : siteProperty().get().getCountyCode(); + Rhomeo.initComboBox(uiDepartmentSiteComboBox, FXCollections.observableArrayList(counties.values()).sorted(), countyCode == null? null : counties.get(countyCode)); + uiDepartmentSiteComboBox.setConverter(countyRepository.getStringConverter()); + } + + session.getProtocols().stream() + .sorted() + .forEach(protocol -> { + final Button protocolButton = new Button(protocol.getName()); + protocolButton.getStyleClass().add(Rhomeo.CSS_PROTOCOL_BUTTON); + + // Prepare tooltip destined to display bound indicators. + final StringJoiner chainBuilder = new StringJoiner(" / "); + session.getIndicators().stream() + .filter(indicator -> indicator.getProtocol() == protocol) + .sorted() + .forEach(indicator -> chainBuilder.add(indicator.getTitle().concat(" (").concat(indicator.getRemarks()).concat(")"))); + final Tooltip tt = new Tooltip(chainBuilder.toString()); + tt.getStyleClass().add(Rhomeo.CSS_PROTOCOL_POPUP_BODY); + tt.setAnchorLocation(PopupWindow.AnchorLocation.CONTENT_BOTTOM_LEFT); + // Force tooltip to show on the upper right corner of the + // associated protocol button. + tt.setOnShowing(evt -> { + final Bounds parentBounds = protocolButton.getBoundsInLocal(); + final Point2D screen = protocolButton.localToScreen(parentBounds.getMaxX(), parentBounds.getMinY()); + tt.setAnchorX(screen.getX() > 10? screen.getX() -10 : screen.getX()); + tt.setAnchorY(screen.getY() + 10); + }); + + + protocolButton.setTooltip(tt); + + // Configure protocol button behavior. + selectedSiteProperty.addListener((observable, oldValue, newValue) -> { + protocolButton.setDisable(newValue == null || !protocol.isCompatible(newValue)); + }); + protocolButton.setDisable(true); + protocolButton.setOnAction(event -> { + startEdition(selectedSiteProperty.get(), protocol); + }); + protocolsBannerBox.getChildren().add(protocolButton); + protocolsBannerBox.disableProperty().bind(uiNewSiteToggleBtn.selectedProperty()); + }); + } + + /** + * Expose the selected site property. + * + * @return a property, never {@code null}. + */ + public ObjectProperty siteProperty() { + return selectedSiteProperty; + } + + /** + * Expose the new site selected property. + * + * @return a property, never {@code null} + */ + public BooleanProperty newSiteProperty() { + return uiNewSiteToggleBtn.selectedProperty(); + } + + /** + * Switch to the selected protocol view. + * + * @param site The selected site, should not be {@code null} + * @param protocol The protocol to show, should not be {@code null} + */ + public void startEdition(final Site site, final Protocol protocol) { + // Defines the selected context + session.startEdition(site, protocol); + session.requestWorkflowStep(WorkflowStep.DATASET); + } + + /** + * Exports the current site as : + * - either a .zip file containing shapefile resources, + * - or a .kml file. + */ + @FXML + private void downloadSelectedSite() throws DataStoreException, XMLStreamException, IOException, KmlException, TransformException { + + final Site site = selectedSiteProperty.get(); + + if(site==null) return; + + final FileChooser.ExtensionFilter[] filters = new FileChooser.ExtensionFilter[] { + new FileChooser.ExtensionFilter("Archive zip", "*.zip"), + new FileChooser.ExtensionFilter("Shapefile", "*.shp"), + new FileChooser.ExtensionFilter("Keyhole Markup Language", "*.kml"), + new FileChooser.ExtensionFilter("Tous", "*") + }; + final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, filters); + if(outputFile==null) return; + + Rhomeo.setPreviousPath(ExportUtils.class, outputFile.getParentFile()); + + if(outputFile.getName().endsWith(".shp")){ + ExportUtils.writeShp(site, outputFile); + } + else if(outputFile.getName().endsWith(".kml")){ + ExportUtils.writeKml(site, outputFile); + } + else if(outputFile.getName().endsWith(".zip")){ + ExportUtils.writeZip(site, outputFile); + } + else { + Rhomeo.showAlert(Alert.AlertType.ERROR, getResourceString("alert-site-download")); + } + } + + /** + * Delete selected site if the user validates this operation. + */ + @FXML + private void deleteSelectedSite() { + final Site selectedSite = selectedSiteProperty.get(); + final String siteName = selectedSite.getName(); + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, + getFormatedResourceString("alert-site-deleted", siteName), + ButtonType.YES, ButtonType.NO); + alert.setResizable(true); + final ButtonType res = alert.showAndWait().get(); + if (res == ButtonType.YES) { + siteRepository.delete(siteName); + try { + session.serializeDeleteSite(siteName); + } catch (IOException | IntrospectionException | ReflectiveOperationException ex) { + throw new RhomeoRuntimeException(ex); + } + cancel(); + } + } + + /** + * Import of a site into the system. Ask user for a shapefile as contour for this site. + * + * @throws MalformedURLException if the specified shapefile path is invalid + * @throws DataStoreException if unable to read the shapefile specified + */ + @FXML + private void importSite() throws MalformedURLException, DataStoreException { + uiNewSiteToggleBtn.setSelected(true); + // If no rollback has been performed, we proceed to the import. + if (uiNewSiteToggleBtn.isSelected()) { + if (!importShapefile()) { + cancel(); + } + } + } + + /** + * Ask user for a shapefile to use as contour for the site, or view the selected site SHP. + * + * @throws MalformedURLException if the specified shapefile path is invalid + * @throws DataStoreException if unable to read the shapefile specified + */ + @FXML + private void importOrViewContour() throws MalformedURLException, DataStoreException { + // Create / import new contour mode + if (uiNewSiteToggleBtn.isSelected()) { + importShapefile(); + + } else { + // View the selected site contour + final Stage viewShpStage = createShpViewStage(); + viewShpStage.initOwner(this.getScene().getWindow()); + viewShpStage.show(); + viewShpStage.requestFocus(); + } + } + + private boolean importShapefile() throws MalformedURLException { + final FileChooser fileChooser = new FileChooser(); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("ShapeFile (*.shp)", "*.shp", "*.SHP"), + new FileChooser.ExtensionFilter("Keyhole Markup Language (*.kml)", "*.kml", "*.KML") + ); + final File prevPath = Rhomeo.getPreviousPath(FXDashboardMenuPane.class); + if (prevPath != null) { + fileChooser.setInitialDirectory(prevPath); + } + final File file = fileChooser.showOpenDialog(getScene().getWindow()); + if (file != null) { + Rhomeo.setPreviousPath(FXDashboardMenuPane.class, file.getParentFile()); + } else { + Rhomeo.LOGGER.log(Level.INFO, "Un fichier .shp doit être choisi."); + return false; + } + + final Task chooseFeature = choose(file.toURI()); + final Stage loadingStage = new Stage(StageStyle.TRANSPARENT); + loadingStage.initModality(Modality.APPLICATION_MODAL); + loadingStage.initOwner(this.getScene().getWindow()); + final ProgressIndicator pi = new ProgressIndicator(); + pi.setBackground(Background.EMPTY); + final Scene scene = new Scene(pi); + scene.setFill(null); + loadingStage.setScene(scene); + loadingStage.show(); + chooseFeature.runningProperty().addListener((obs, oldValue, newValue) -> { + if (!newValue) + loadingStage.close(); + }); + + chooseFeature.setOnFailed((evt) -> Platform.runLater(() + -> Rhomeo.showAlert(Alert.AlertType.ERROR, "Une erreur s'est produite lors de la lecture du fichier.") + )); + + chooseFeature.setOnSucceeded((evt) -> Platform.runLater(() -> { + final Feature feature = chooseFeature.getValue(); + if (feature == null) { + return; + } + + Geometry tmpGeom = feature.getDefaultGeometryProperty() == null? null : (Geometry) feature.getDefaultGeometryProperty().getValue(); + if (tmpGeom instanceof LinearRing) { + tmpGeom = tmpGeom.getFactory().createPolygon((LinearRing) tmpGeom); + } + if (tmpGeom instanceof Polygon) { + tmpGeom = new MultiPolygon(new Polygon[]{(Polygon) tmpGeom}, tmpGeom.getFactory()); + } + + if (tmpGeom instanceof MultiPolygon) { + geomToAdd.set((MultiPolygon) tmpGeom); + } else { + Rhomeo.showAlert(Alert.AlertType.ERROR, "Impossible d'importer la donnée car sa géométrie n'est pas un polygone."); + } + + // update fx UI + final Property nameProp = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.NAME, "nom", "nom_site", "nomSite", "siteName", "site_name"); + if (nameProp != null && nameProp.getValue() != null) { + uiNameSiteTxt.setText(nameProp.getValue().toString()); + } + final Property refProp = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.REFERENT); + if (refProp != null && refProp.getValue() != null) { + uiReferentSiteTxt.setText(refProp.getValue().toString()); + } + final Property orgProp = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.ORG); + if (orgProp != null && orgProp.getValue() != null) { + uiStructureSiteTxt.setText(orgProp.getValue().toString()); + } + final Property dep = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.COUNTY, "dept"); + County sourceCounty = dep == null || dep.getValue() == null ? null : counties.get(dep.getValue().toString().trim()); + if (geomToAdd.get() != null && countyRepository != null) { + List matching = countyRepository.get(geomToAdd.get()); + if (!matching.isEmpty() && !matching.contains(sourceCounty)) + sourceCounty = matching.get(0); + } + + if (sourceCounty != null) { + uiDepartmentSiteComboBox.setValue(sourceCounty); + } else { + uiDepartmentSiteComboBox.setValue(null); + } + + final Property type = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.TYPE, "code_ty_01", "type_zone"); + if (type != null && type.getValue() != null && !type.getValue().toString().isEmpty()) { + uiTypeZhSiteComboBox.getSelectionModel().select(HumidZoneType.getByCode(type.getValue().toString())); + } + final Property odo = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.ODONATE); + if (odo != null && odo.getValue() != null && !odo.getValue().toString().isEmpty()) { + uiZbOdoSiteComboBox.getSelectionModel().select(OdonateZoneBio.getByCode(odo.getValue().toString())); + } + final Property ortho = getByNameIgnoreCase(feature, Rhomeo.FT_PROPERTIES.ORTHOPTERE); + if (ortho != null && ortho.getValue() != null && !ortho.getValue().toString().isEmpty()) { + uiZbOrthoSiteComboBox.getSelectionModel().select(OrthoptereZoneBio.getByCode(ortho.getValue().toString())); + } + })); + + TaskManager.INSTANCE.submit(chooseFeature); + return true; + } + + private Property getByNameIgnoreCase(final Feature source, final Rhomeo.FT_PROPERTIES p, final String... fallback) { + for (final Property prop : source.getProperties()) { + if (prop.getName().tip().toString().equalsIgnoreCase(p.name())) + return prop; + } + + if (fallback != null && fallback.length > 0) { + final HashSet lcNames = new HashSet<>(fallback.length); + for (final String str : fallback) { + if (str != null && !str.isEmpty()) + lcNames.add(str.toLowerCase()); + } + + for (final Property prop : source.getProperties()) { + if (lcNames.contains(prop.getName().tip().toString().toLowerCase())) + return prop; + } + } + + return null; + } + + private Task choose(final URI shpFile) { + return new TaskManager.MockTask<>("Lecture d'un fichier", () -> { + Feature feature = null; + final CoordinateReferenceSystem siteCRS = Rhomeo.getSiteCRS(); + + // First, we try to read a KML file. If it fails, we'll try shapefile format. + Exception kmlError; + try { + KmlReader kmlReader = new KmlReader(); + kmlReader.setInput(shpFile); + kmlReader.setUseNamespace(false); + return mapKml(kmlReader.read(), siteCRS); + } catch (Exception e) { + kmlError = e; + } + + try (final ShapefileFeatureStore shpStore = new ShapefileFeatureStore(shpFile, "no namespace")) { + final Set names = shpStore.getNames(); + if (names != null && !names.isEmpty()) { + final GenericName layerName = names.iterator().next(); + final Query baseQuery = QueryBuilder.all(layerName); + final FeatureCollection col = shpStore.createSession(false) + .getFeatureCollection(baseQuery); + + if (col.getFeatureType().getCoordinateReferenceSystem() == null) { + Rhomeo.showAlert(Alert.AlertType.ERROR, "Aucune projection n'a été trouvée dans le fichier. Il est impossible d'importer les données."); + throw new IllegalArgumentException("No valid CRS in file ".concat(shpFile.toString())); + } + + if (col.isEmpty()) { + feature = null; + } else if (col.size() == 1) { + try (final FeatureIterator it = col.subCollection(QueryBuilder.reprojected(layerName, siteCRS)).iterator()) { + if (it.hasNext()) { + feature = it.next(); + } + } + } else { + final FeatureMapLayer tmpLayer = MapBuilder.createFeatureLayer(col); + final FXFeatureTable choiceTable = new FXFeatureTable(); + choiceTable.setLoadAll(true); + choiceTable.setEditable(false); + choiceTable.init(tmpLayer); // TODO : pagination + + feature = Rhomeo.fxRunAndWait(() -> { + final Alert a = new Alert(Alert.AlertType.CONFIRMATION, null, ButtonType.CANCEL, ButtonType.OK); + a.getDialogPane().setContent(choiceTable); + a.setHeaderText("Le fichier contient plusieurs entrées. Veuillez choisir celle à importer."); + a.setResizable(true); + if (ButtonType.OK.equals(a.showAndWait().orElse(ButtonType.CANCEL))) { + final Id selectionFilter = tmpLayer.getSelectionFilter(); + if (selectionFilter != null) { + final QueryBuilder builder = new QueryBuilder(layerName); + builder.setFilter(selectionFilter); + builder.setCRS(siteCRS); + try (final FeatureIterator it = col.subCollection(builder.buildQuery()).iterator()) { + if (it.hasNext()) { + return it.next(); + } + } + } + } + return null; + }); + } + } + } catch (Exception e) { + e.addSuppressed(kmlError); + throw e; + } + + return feature; + }); + } + + private Feature mapKml(final Kml kml, final CoordinateReferenceSystem targetCRS) throws Exception { + final Feature kmlFeature = kml.getAbstractFeature(); + final FeatureTypeBuilder builder = new FeatureTypeBuilder(); + builder.setName("site"); + GenericName geomName = null; + for (final PropertyDescriptor pType : kmlFeature.getType().getDescriptors()) { + if (AbstractGeometry.class.isAssignableFrom(pType.getType().getBinding())) { + builder.add(pType.getName(), Geometry.class); + builder.setDefaultGeometry(pType.getName()); + geomName = pType.getName(); + } else if (pType instanceof AttributeDescriptor) { + builder.add(pType.getName(), pType.getType().getBinding(), 1, 1, true, null); + } + } + + final FeatureType targetType = builder.buildSimpleFeatureType(); + final FeatureBuilder target = new FeatureBuilder(targetType); + for (final Property p : kmlFeature.getProperties()) { + if (p.getValue() == null) + continue; + if (p.getName().equals(geomName)) { + final Geometry geom = convert(p.getValue()); + if (geom != null) { + JTS.setCRS(geom, CommonCRS.WGS84.normalizedGeographic()); + target.setPropertyValue(p.getName(), JTS.convertToCRS(geom, targetCRS)); + } + } else { + target.setPropertyValue(p.getName(), p.getValue()); + } + } + + return target.buildFeature(kmlFeature.getIdentifier().getID()); + } + + /** + * Note : Return only polygons or multi-polygons. + * @param source The object to convert into polygon or multi-polygon + * @return Converted geometry, or null. + * @throws Exception + */ + private static Geometry convert(final Object source) { + if (source instanceof Polygon) + return (Geometry) source; + else if (source instanceof MultiGeometry) { + final MultiGeometry kmlMulti = (MultiGeometry) source; + final List geoms = new ArrayList<>(0); + + //loop on geometry to add id on a GeometryList + for (AbstractGeometry abstractGeometry : kmlMulti.getGeometries()) { + if (abstractGeometry instanceof Polygon) { + geoms.add((Polygon) abstractGeometry); + } + } + return new GeometryFactory().createMultiPolygon(geoms.toArray(new Polygon[geoms.size()])); + + } else + return null; + } + + /** + * Creates a {@linkplain Stage stage} with a map showing the currently selected site geometry. + * + * @return A stage with the map, never {@code null} + */ + private Stage createShpViewStage() { + final Site currentSite = selectedSiteProperty.get(); + // Should not be null when called here + if (currentSite == null) { + throw new IllegalArgumentException("Currently selected site was null here, but shouldn't be"); + } + + final Stage stage = Rhomeo.newStage(); + stage.setResizable(true); + stage.setTitle(currentSite.getName()); + stage.setWidth(1024); + stage.setHeight(768); + + final FXMap map = new FXMap(false); + map.addDecoration(new FXScaleBarDecoration()); + map.setHandler(new FXPanHandler(false)); + final FXCoordinateBar coordBar = new FXCoordinateBar(map); + coordBar.setCrsButtonVisible(false); + // Remove the first item among left items in the status bar, should be the timeline button + ((StatusBar)coordBar.getChildren().get(0)).getLeftItems().remove(0); + + final FXNavigationBar navBar = new FXNavigationBar(map); + // Remove the go to coordinate button + ((HBox)navBar.getItems().get(0)).getChildren().remove(4); + final FXGeoToolBar geoToolBar = new FXGeoToolBar(map); + final BorderPane fillPane = new BorderPane(); + fillPane.setMaxWidth(Double.MAX_VALUE); + + final GridPane topgrid = new GridPane(); + navBar.setMaxHeight(Double.MAX_VALUE); + geoToolBar.setMaxHeight(Double.MAX_VALUE); + topgrid.add(navBar, 0, 0); + topgrid.add(geoToolBar, 1, 0); + final ColumnConstraints col2 = new ColumnConstraints(); + col2.setHgrow(Priority.ALWAYS); + topgrid.getColumnConstraints().addAll(new ColumnConstraints(), col2); + + // If counties are available, we display them on the map. + final MapContext context = MapBuilder.createContext(); + + if (countyRepository != null) { + try { + context.layers().add(MapBuilder.createFeatureLayer( + countyRepository.getFeatures(), GO2Utilities.STYLE_FACTORY.style(GO2Utilities.STYLE_FACTORY.lineSymbolizer()) + )); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.WARNING, "Cannot show county layer", e); + } + } + + final BeanFeature feature = new BeanFeature(currentSite, BeanUtils.createMapping(Site.class, null)); + final MapLayer layer = MapBuilder.createFeatureLayer(FeatureStoreUtilities.collection(feature)); + context.layers().add(layer); + context.setAreaOfInterest(layer.getBounds()); + map.getContainer().setContext(context); + try { + map.getCanvas().setObjectiveCRS(Rhomeo.getSiteCRS()); + map.getCanvas().setVisibleArea(layer.getBounds()); + } catch (FactoryException | TransformException | java.awt.geom.NoninvertibleTransformException e) { + throw new IllegalArgumentException("Unable to defines canvas CRS", e); + } + + final Scene scene = new Scene(new BorderPane(map, topgrid, null, coordBar, null)); + stage.setScene(scene); + + return stage; + } + + /** + * Create or update a site, depending if we are viewing the information panel as a new or existing site. + */ + @FXML + private void createOrUpdateSite() { + // Check data validity + if (!verifyMandatoryParameters()) { + return; + } + + Site site = selectedSiteProperty.get(); + final String oldName = site == null? null : site.getName(); + site = prepareSiteFromForm((SiteImpl)site); + + try { + // If the site had no name initially, it's a new one. Otherwise, it + // already existed. + if (oldName == null || uiNewSiteToggleBtn.isSelected()) { + siteRepository.create(site); + + } else { + siteRepository.update(site, oldName); + try { + session.serializeUpdateSiteName(site, oldName); + } catch (IOException | IntrospectionException | ReflectiveOperationException ex) { + throw new RhomeoRuntimeException(ex); + } + } + + // Update selection will set the editor in read-only mode, and ensure + // that combo-box is aware of the created site. + selectedSiteProperty.set(null); + selectedSiteProperty.set(site); + + } catch (DuplicatedKeyException e) { + Rhomeo.showAlert(Alert.AlertType.WARNING, getFormatedResourceString("alert-site-createOrUpdate", site.getName())); + } + } + + /** + * Verify that all mandatory parameters are filled. + * + * @return {@code True} if all parameters are filled, {@code false} if one is missing. + */ + private boolean verifyMandatoryParameters() { + // Verify mandatory fields + boolean result = true; + if (uiNameSiteTxt.getText() == null || uiNameSiteTxt.getText().trim().isEmpty()) { + uiNameSiteTxt.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + Rhomeo.showAlert(Alert.AlertType.WARNING, "Un nom doît avoir été renseigné pour créer un nouveau site."); + result = false; + } else { + uiNameSiteTxt.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + } + + if (geomToAdd.get() == null) { + uiImportContourBtn.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + if (result) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Un contour doît avoir été importé pour créer un nouveau site."); + result = false; + } + } else { + uiImportContourBtn.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + } + + if (uiDepartmentSiteComboBox.getSelectionModel().isEmpty()) { + uiDepartmentSiteComboBox.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + if (result) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Un département doît avoir été sélectionné pour créer un nouveau site."); + result = false; + } + } else { + uiDepartmentSiteComboBox.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + } + + if (uiTypeZhSiteComboBox.getSelectionModel().isEmpty()) { + uiTypeZhSiteComboBox.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + if (result) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Un type de zone humide doît avoir été sélectionné pour créer un nouveau site."); + result = false; + } + } else { + uiTypeZhSiteComboBox.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + } + return result; + } + + /** + * Cancel site creation. + */ + @FXML + private void cancel() { + if (confirmCancelling(siteProperty().get())) { + selectedSiteProperty.set(null); + } + } + + /** + * Clears the site form. + */ + private void clearForm() { + uiNameSiteTxt.setText(""); + uiReferentSiteTxt.setText(""); + uiStructureSiteTxt.setText(""); + uiTypeZhSiteComboBox.setValue(null); + uiDepartmentSiteComboBox.setValue(null); + uiZbOdoSiteComboBox.setValue(null); + uiZbOrthoSiteComboBox.setValue(null); + geomToAdd.set(null); + + uiNameSiteTxt.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + uiImportContourBtn.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + uiDepartmentSiteComboBox.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + uiTypeZhSiteComboBox.getStyleClass().remove(Rhomeo.CSS_ERROR_FIELD); + } + + /** + * Generates a new {@linkplain SiteImpl site} using values filled in the site information form. + * + * @param site site to update if not {@code null}. If {@code null}, create a new one. + * @return A new site, or the same instance with values updated from the form. + * @throws IllegalArgumentException if the geometry is invalid. + */ + private SiteImpl prepareSiteFromForm(final SiteImpl site) { + final SiteImpl siteToPrepare; + if (site == null) { + siteToPrepare = new SiteImpl(geomToAdd.get()); + } else { + siteToPrepare = site; + siteToPrepare.setGeometry(geomToAdd.get()); + } + siteToPrepare.setName(uiNameSiteTxt.getText()); + siteToPrepare.setReferent(uiReferentSiteTxt.getText()); + siteToPrepare.setOrganization(uiStructureSiteTxt.getText()); + + final County selectedItem = uiDepartmentSiteComboBox.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + siteToPrepare.setCountyCode(selectedItem.getCode()); + } + + if (!uiTypeZhSiteComboBox.getSelectionModel().isEmpty()) { + siteToPrepare.setZoneType(uiTypeZhSiteComboBox.getSelectionModel().getSelectedItem().getCode()); + } + if (!uiZbOdoSiteComboBox.getSelectionModel().isEmpty()) { + siteToPrepare.setOdonateType(uiZbOdoSiteComboBox.getSelectionModel().getSelectedItem().getCode()); + } + if (!uiZbOrthoSiteComboBox.getSelectionModel().isEmpty()) { + siteToPrepare.setOrthoptereType(uiZbOrthoSiteComboBox.getSelectionModel().getSelectedItem().getCode()); + } + + return siteToPrepare; + } + + /** + * Fill the site information form using values coming from a site. + * Reverse of {@link #prepareSiteFromForm(SiteImpl)}. + * + * @param site site + */ + private void fillFormFromSite(final Site site) { + uiNameSiteTxt.setText(site.getName()); + uiReferentSiteTxt.setText(site.getReferent()); + uiStructureSiteTxt.setText(site.getOrganization()); + geomToAdd.set(site.getGeometry()); + + final String cc = site.getCountyCode(); + County sourceCounty = cc == null || counties == null? null : counties.get(cc.trim()); + if (site.getGeometry() != null && countyRepository != null) { + List matching = countyRepository.get(site.getGeometry()); + if (!matching.isEmpty() && !matching.contains(sourceCounty)) + sourceCounty = matching.get(0); + } + + if (sourceCounty != null) { + uiDepartmentSiteComboBox.setValue(sourceCounty); + } else { + uiDepartmentSiteComboBox.setValue(null); + } + + if (site.getZoneType() != null && !site.getZoneType().isEmpty()) { + uiTypeZhSiteComboBox.getSelectionModel().select(HumidZoneType.getByCode(site.getZoneType())); + } + if (site.getOdonateType() != null && !site.getOdonateType().isEmpty()) { + uiZbOdoSiteComboBox.getSelectionModel().select(OdonateZoneBio.getByCode(site.getOdonateType())); + } + if (site.getOrthoptereType() != null && !site.getOrthoptereType().isEmpty()) { + uiZbOrthoSiteComboBox.getSelectionModel().select(OrthoptereZoneBio.getByCode(site.getOrthoptereType())); + } + } + + private void siteNameChanged(final ObservableValue obs, String oldSite, String newSite) { + Site site = selectedSiteProperty.get(); + if (site != null && Rhomeo.equivalent(newSite, site.getName())) + return; + + if (newSite == null || newSite.trim().isEmpty()) + selectedSiteProperty.set(null); + else + selectedSiteProperty.set(siteRepository.findOne(newSite)); + } + + private synchronized void siteChanged(final ObservableValue obs, Site oldValue, Site newValue) { + clearForm(); + + if (newValue != null) { + fillFormFromSite(newValue); + if (newValue.getName() != null) { + uiNewSiteToggleBtn.setSelected(false); + } + uiSitesList.setValue(newValue.getName()); + } else { + uiNewSiteToggleBtn.setSelected(false); + uiSitesList.setValue(null); + } + } + + private boolean confirmCancelling(final Site toCancel) { + return Rhomeo.fxRunAndWait(() -> { + if (toCancel != null && !toCancel.equals(prepareSiteFromForm(null))) { + final Alert modifications = new Alert(Alert.AlertType.CONFIRMATION, getResourceString("unsaved-modification"), ButtonType.NO, ButtonType.YES); + modifications.setResizable(true); + if (ButtonType.NO.equals(modifications.showAndWait().orElse(ButtonType.NO))) { + return false; + } + } + + return true; + }); + } + + /** + * Generates a PDF button that will open the PDF file when clicked. + * + * @param pdfPath Path of the PDF to open. + * @return A button, never {@code null} + */ + private Button createPdfButton(final String pdfPath) { + final Button button = new Button(); + button.setGraphic(new ImageView(Rhomeo.ICON_FILE_PDF)); + button.getStyleClass().add("transparent"); + button.setOnAction(event -> + docManager.openDocument(FXDashboardMenuPane.class.getResource(pdfPath)) + ); + return button; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardPane.java new file mode 100644 index 0000000..4579439 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardPane.java @@ -0,0 +1,256 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.DashboardResultsManager; +import fr.cenra.rhomeo.core.result.ResultStorage; +import fr.cenra.rhomeo.core.util.ExportUtils; +import javafx.fxml.FXML; +import javafx.scene.layout.BorderPane; +import javafx.stage.FileChooser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.Button; +import javafx.scene.control.TablePosition; +import org.apache.commons.net.ftp.FTPClient; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.context.annotation.Scope; + +/** + * Dashboard panel displayed initially when opening the application. + * + * @author Cédric Briançon (Geomatys) + */ +@Component +@Scope("prototype") +public class FXDashboardPane extends BorderPane implements InternationalResource { + /** + * Body panel containing the data table and some action buttons. + */ + @FXML private BorderPane uiDashboardBodyPane; + + @FXML + private Button uiPublish; + + @FXML + private Button uiExport; + + @Autowired + private FXDashboardMenuPane menuPane; + + @Autowired + private Session session; + + @Autowired + private DashboardResultsManager resultsManager; + + @Autowired + private ResultStorage resultStorage; + + FXDashboardResultsTable resultTable; + + public FXDashboardPane() { + Rhomeo.loadFXML(this, FXDashboardPane.class); + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + private void postConstruct() { + setTop(menuPane); + + resultTable = session.createPrototype(FXDashboardResultsTable.class, true); + uiDashboardBodyPane.setCenter(resultTable); + resultTable.siteProperty().bind(menuPane.siteProperty()); + + uiDashboardBodyPane.visibleProperty().bind(menuPane.siteProperty().isNotNull().and(menuPane.newSiteProperty().not())); + uiDashboardBodyPane.managedProperty().bind(uiDashboardBodyPane.visibleProperty()); + + final ObservableList selectedCells = resultTable.getSelectionModel().getSelectedCells(); + uiPublish.disableProperty().bind(Bindings.createBooleanBinding(() -> { + if (selectedCells.size() != 1) { + return true; + } + + TablePosition pos = selectedCells.get(0); + if (pos.getTableColumn() instanceof IndicatorSiteColumn) { + final String indicName = ((IndicatorSiteColumn)pos.getTableColumn()).getIndicator().getName(); + final Boolean published = pos.getTableView().getItems().get(pos.getRow()).isPublished(indicName); + return published == null? true : published; + } + + return true; + }, selectedCells)); + + uiExport.disableProperty().bind(Bindings.createBooleanBinding(() -> { + return (selectedCells.size() != 1 || !(selectedCells.get(0).getTableColumn() instanceof IndicatorSiteColumn)); + }, selectedCells)); + } + + public ObjectProperty siteProperty() { + return menuPane.siteProperty(); + } + + @FXML + public void publishData(final ActionEvent evt) { + final ObservableList selectedCells = resultTable.getSelectionModel().getSelectedCells(); + if (resultTable == null || selectedCells.size() != 1) + return; + + final DashboardResultItem item = getSelectedItem(); + final IndicatorValuesByYearItem rowItem = resultTable.getItems().get(selectedCells.get(0).getRow()); + if (item == null || item.isPublished()) + return; + + TaskManager.INSTANCE.submit("Publication ".concat(item.getIndicator()), () -> { + final FTPClient ftp = session.connectResultFTP(); + try { + resultsManager.publishResults(menuPane.siteProperty().get(), Collections.singletonList(item), ftp); + } finally { + ftp.disconnect(); + } + + return true; + }).setOnSucceeded((evt2) -> Platform.runLater(() -> { + // Update publication state. + rowItem.getValueForIndicator(item.getIndicator()).setPublished(true); + resultTable.getItems().set(resultTable.getItems().indexOf(rowItem), rowItem); + })); + } + + @FXML + public void exportData() { + if (resultTable == null || resultTable.getSelectionModel().getSelectedCells().size() != 1) + return; + + final DashboardResultItem item = getSelectedItem(); + if (item == null) + return; + + final Indicator indic = ((IndicatorSiteColumn) resultTable.getSelectionModel().getSelectedCells().get(0).getTableColumn()).getIndicator(); + final String initialFileName = new StringBuilder(item.getSite()).append('_').append(item.getIndicator()).append('_').append(item.getYear()).append(".zip").toString(); + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter); + if (outputFile == null) { + return; + } + + TaskManager.INSTANCE.submit("Export de résultats", () -> { + try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputFile.toPath())) { + final Path root = fs.getRootDirectories().iterator().next(); + Optional opt = resultStorage.getMainResults(item); + if (opt.isPresent()) + Files.copy(opt.get(), root.resolve(opt.get().getFileName().toString())); + opt = resultStorage.getAdditionalResults(item); + if (opt.isPresent()) + Files.copy(opt.get(), root.resolve(opt.get().getFileName().toString())); + + // Read metadata from file, and recover memory objects needed for + // re-writing as text. + final Map[] readMds = resultStorage.readMetadata(item) + .map(ctx -> ctx.toDataContext(session)) + .map(ctx -> new Map[]{session.transform(ctx.getReferences()), ctx.getUserData()}) + .orElse(new Map[]{Collections.EMPTY_MAP, Collections.EMPTY_MAP}); + + ExportUtils.writeMetadataText( + root.resolve("metadata.txt"), + menuPane.siteProperty().get(), + indic.getProtocol(), + readMds[0], + Collections.singleton(indic), + readMds[1], + Collections.EMPTY_SET + ); + } + return true; + }); + } + + /** + * + * @return A dashboard item reflecting data pointed by currently selected cell. Can be null. + */ + private DashboardResultItem getSelectedItem() { + if (resultTable == null) { + return null; + } + final ObservableList selectedCells = resultTable.getSelectionModel().getSelectedCells(); + if (resultTable.getSelectionModel().getSelectedCells().size() != 1) { + return null; + } + + TablePosition pos = selectedCells.get(0); + if (!(pos.getTableColumn() instanceof IndicatorSiteColumn)) { + return null; + } + + final Site site = menuPane.siteProperty().get(); + final String siteName = site.getName(); + final IndicatorSiteColumn col = (IndicatorSiteColumn) pos.getTableColumn(); + IndicatorValuesByYearItem tmpItem = resultTable.getItems().get(pos.getRow()); + final Indicator indic = col.getIndicator(); + final String indicName = indic.getName(); + return new DashboardResultItem( + tmpItem.getYear(), siteName, indicName, col.getCellData(tmpItem), tmpItem.isPublished(indicName) + ); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardResultsTable.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardResultsTable.java new file mode 100644 index 0000000..947bb2e --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDashboardResultsTable.java @@ -0,0 +1,234 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.DashboardResultsManager; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.image.ImageView; +import org.geotoolkit.gui.javafx.util.FXTableCell; +import org.geotoolkit.gui.javafx.util.FXTableView; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.TableCell; +import fr.cenra.rhomeo.api.ui.DashboardMenuItem; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import java.text.DecimalFormat; +import java.util.Optional; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; +import javax.annotation.PostConstruct; + +/** + * Dashboard table presenting results for the selected site. + * + * @author Cédric Briançon (Geomatys) + */ +@Component("fr.cenra.rhomeo.fx.FXDashboardResultsTable") +@Scope("prototype") +public class FXDashboardResultsTable extends FXTableView { + + @Autowired + private Session session; + + @Autowired + private DashboardResultsManager resultsManager; + + @Autowired + private List menuItems; + + private final ObjectProperty siteProperty = new SimpleObjectProperty<>(); + + /** + * Generate a table for the specified site. + * + * @param byCell true if the table has to use the selection by cell mode, false for generic by line selection. + */ + public FXDashboardResultsTable(final boolean byCell) { + setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + if (byCell) { + getSelectionModel().setCellSelectionEnabled(true); + getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + } + + siteProperty.addListener(this::siteChanged); + } + + + public ObjectProperty siteProperty() { + return siteProperty; + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + public void postConstruct() { + final ContextMenu menu = new ContextMenu(); + setContextMenu(menu); + for (final DashboardMenuItem menuItem : menuItems) { + final Optional item = menuItem.createItem(this); + if(item.isPresent()){ + menu.getItems().add(item.get()); + } + } + } + + private void siteChanged(final ObservableValue obs, final Site oldSite, final Site newSite) { + getColumns().clear(); + if (newSite == null) { + setItems(null); + setPlaceholder(new Label("Aucun site séléctionné")); + } else { + try { + setItems(resultsManager.getDashboardResultsByYear(newSite)); + } catch (Exception ex) { + throw new RhomeoRuntimeException(ex); + } + + if (getItems().isEmpty()) { + setPlaceholder(new Label("Aucun résultat attaché au site")); + } else { + final TableColumn yearCol = new TableColumn<>("Année"); + yearCol.setCellValueFactory(value -> new SimpleIntegerProperty(value.getValue().getYear()).asObject()); + getColumns().add(yearCol); + + final List indicators = session.getIndicators(); + final List protocols = session.getProtocols(); + + final List columnsToAdd = new ArrayList<>(); + + protocols.stream() + .filter(protocol -> protocol.isCompatible(newSite)) + .sorted() + .forEach(protocol + -> indicators.stream() + .filter(indicator -> indicator.getProtocol() == protocol) + .sorted() + .forEach(indicator -> { + final IndicatorSiteColumn siteColumn = new IndicatorSiteColumn(indicator); + siteColumn.setCellValueFactory(this::getCellValueFactory); + siteColumn.setCellFactory(this::getCellFactory); + columnsToAdd.add(siteColumn); + }) + ); + Collections.sort(columnsToAdd); + getColumns().addAll(columnsToAdd); + } + } + } + + private TableCell getCellFactory(final TableColumn input) { + // hack #61 display integers for I04/I07 + final DecimalFormat format; + if (input instanceof IndicatorSiteColumn && + (((IndicatorSiteColumn)input).getIndicator().getName().matches("I0(4|7)"))) { + format = Rhomeo.SIMPLE_DECIMAL_FORMAT; + } else { + format = Rhomeo.SECOND_DECIMAL_FORMAT; + } + final FXTableCell cell = new FXTableCell() { + + @Override + protected void updateItem(Double item, boolean empty) { + super.updateItem(item, empty); + setContentDisplay(ContentDisplay.RIGHT); + if (empty || item == null || Double.isNaN(item)) { + setText(null); + setGraphic(null); + } else { + final IndicatorValuesByYearItem itemLine = getItems().get(getIndex()); + if (itemLine == null) { + setText(null); + setGraphic(null); + } else { + setText(format.format(item)); + if (input instanceof IndicatorSiteColumn) { + final Boolean isPublished = itemLine.isPublished(((IndicatorSiteColumn) input).getIndicator().getName()); + if (isPublished != null && !isPublished) { + setGraphic(new ImageView(Rhomeo.ICON_NOT_PUBLISHED)); + } + } + } + } + } + }; + cell.setAlignment(Pos.CENTER_RIGHT); + return cell; + } + + private ObservableValue getCellValueFactory(final TableColumn.CellDataFeatures feature) { + final TableColumn col = feature.getTableColumn(); + final String indicName; + if (col instanceof IndicatorSiteColumn) { + indicName = ((IndicatorSiteColumn) col).getIndicator().getName(); + } else { + indicName = null; + } + + final IndicatorValuesByYearItem.PublishedValue valueForIndicator + = indicName == null ? null : feature.getValue().getValueForIndicator(indicName); + + if (valueForIndicator == null || valueForIndicator.getValue() == null) { + return new SimpleObjectProperty<>(); + } + + return new SimpleObjectProperty<>(valueForIndicator.getValue()); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.java new file mode 100644 index 0000000..89b0210 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.java @@ -0,0 +1,186 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.api.InternationalDescription; +import fr.cenra.rhomeo.api.preferences.PasswordKey; +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; +import fr.cenra.rhomeo.api.ui.PreferencePane; +import java.util.HashMap; +import java.util.Map; +import javafx.beans.property.StringProperty; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; + +/** + * + * A default preference pane based on key/value structure. + * + * Each preference is displayed as a row of a {@link GridPane} in which the key + * is displayed as a {@link Label} on the left side, and the value is displayed + * in a {@link TextField} on the right side. + * + * Preferences are provided by a {@link PreferenceGroup} which must be set only + * once. + * + * @author Samuel Andrés (Geomatys) + * + * @param The TYPE of {@link PreferenceGroup} keys. + */ +public class FXDefaultPreferenceGroupPane extends GridPane implements PreferencePane { + + private PreferenceGroup preferenceGroup; + private final Map valueFields = new HashMap<>(); + + /** + * Builds a {@link FXDefaultPreferenceGroupPane} based on key/value structure of + * a {@link PreferenceGroup}. + * + * @param preferenceGroup + */ + public FXDefaultPreferenceGroupPane(final PreferenceGroup preferenceGroup){ + this(); + setPreferenceGroup(preferenceGroup); + } + + /** + * Default constructor used by Spring to produce new prototype. + * + * This constructor does not fill the pane for any {@link PreferenceGroup}. + * + * The method {@link #setPreferenceGroup(PreferenceGroup)} must be used to fill the pane. + */ + public FXDefaultPreferenceGroupPane(){ + final String css = FXDefaultPreferenceGroupPane.class.getResource(FXDefaultPreferenceGroupPane.class.getSimpleName()+".css").toExternalForm(); + getStylesheets().add(css); + getStyleClass().add("pane"); + } + + @Override + public PreferenceGroup getPreferenceGroup() { + return preferenceGroup; + } + + /** + * Sets the preference group. + * + * Must be called only once. + * + * If the current object has been built with the default constructor (for + * instance by Spring autowiring), it must be called explicitly. On the + * contrary, this method is implicitly called by the constructor using a + * {@link PreferenceGroup} parameter. + * + * For each key of the {@link PreferenceGroup}, this method adds a new row + * to the pane, with two columns. The first one contains a label relative to + * the preference key. The second one contains a text field to edit the + * corresponding preference value. + * + * @param preferenceGroup + */ + public final void setPreferenceGroup(final PreferenceGroup preferenceGroup){ + + if(this.preferenceGroup!=null) throw new IllegalStateException("The preference group has already been set."); + + this.preferenceGroup = preferenceGroup; + int row = 0; + for(final T key : preferenceGroup.getKeys()){ + final Label label = new Label(key.getLabel()); + final String comment = key.getDescription(); + if(comment!=null && !comment.equals("")){ + label.setTooltip(new Tooltip(comment)); + } + + final Node editor = createEditor(key); + addRow(row++, label, editor); + } + } + + private Node createEditor(final T key) { + if (key instanceof PasswordKey) { + final Button editBtn = new Button("Modifier"); + final HBox container = new HBox(5, editBtn); + editBtn.setOnAction((evt) -> { + final PasswordField field = new PasswordField(); + valueFields.put(key, field.textProperty()); + final Button cancelBtn = new Button("Annuler"); + cancelBtn.setCancelButton(true); + cancelBtn.setOnAction(evt2 -> { + valueFields.remove(key); + container.getChildren().clear(); + container.getChildren().add(editBtn); + }); + container.getChildren().clear(); + container.getChildren().addAll(field, cancelBtn); + }); + return container; + } else { + final TextField textField = new TextField(preferenceGroup.getPreference(key)); + textField.setPrefWidth(300); + valueFields.put(key, textField.textProperty()); + return textField; + } + } + + /** + * Save the preferences. + */ + @Override + public void save(){ + if(preferenceGroup!=null){ + for(final T key : preferenceGroup.getKeys()){ + final StringProperty value = valueFields.get(key); + if(value!=null){ + preferenceGroup.setPreference(key, value.getValue()); + } + } + } + } + + @Override + public Node getEditor() { + return this; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.java new file mode 100644 index 0000000..c21243e --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.java @@ -0,0 +1,674 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.Process; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.ui.FilterTable; +import fr.cenra.rhomeo.api.ui.FilterTableSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.util.SimplePredicateChain; +import fr.cenra.rhomeo.fx.edition.FXTrackingPointTable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.DoubleBinding; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.SelectionMode; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + + +/** + * + * Default JavaFX pane to set processing parameters. + * + * For a given {@link Dataset} and a given {@link Protocol}, this pane allows to + * choose the {@link Indicator}(s) and the {@link TrackingPoint}(s) the processing + * must be computed for. + * + * @author Samuel Andrés (Geomatys) + * + * @param + */ +@Component("fr.cenra.rhomeo.fx.FXDefaultProcessingPane") +@Scope("prototype") +public class FXDefaultProcessingPane extends VBox implements InternationalResource { + + @FXML private GridPane uiIndicatorPane; + private final Collection indicatorSelections = new ArrayList<>(); + + @FXML private FlowPane uiYearPane; + private final Collection yearSelections = new ArrayList<>(); + + @FXML private BorderPane uiTrackingPointPane; + + private FilterTable uiSelectionTable; + private final TableColumn uiSelectionColumn; + + private final Map filterSelection = new HashMap<>(); + private final Function selectionComputer; + + @FXML + private VBox uiProgressBox; + + @FXML + private Label uiProgressTitle; + + @FXML + private ProgressBar uiProgress; + + @FXML + private Label uiProgressMessage; + + @FXML + private HBox uiBtnBox; + + private Dataset dataset; + private Protocol protocol; + private ProcessContext processContext; + private Site site; + + private final ChangeListener selectionListener = this::selectionChanged; + private final SimpleObjectProperty>> globalTask = new SimpleObjectProperty<>(); + + private SimplePredicateChain processFilter; + + @Autowired + private Session session; + + @Autowired + private List pointTableSpis; + + public FXDefaultProcessingPane() { + Rhomeo.loadFXML(this, FXDefaultProcessingPane.class); + + uiProgressBox.setVisible(false); + globalTask.addListener((obs, oldTask, newTask) -> { + if (oldTask != null) { + uiProgressBox.visibleProperty().unbind(); + uiProgressBox.setVisible(false); + uiProgressTitle.textProperty().unbind(); + uiProgressMessage.textProperty().unbind(); + uiProgress.progressProperty().unbind(); + uiIndicatorPane.disableProperty().unbind(); + uiTrackingPointPane.disableProperty().unbind(); + uiBtnBox.disableProperty().unbind(); + } else { + uiProgressBox.visibleProperty().bind(newTask.runningProperty()); + uiProgressTitle.textProperty().bind(newTask.titleProperty()); + uiProgressMessage.textProperty().bind(newTask.messageProperty()); + uiProgress.progressProperty().bind(newTask.progressProperty()); + uiIndicatorPane.disableProperty().bind(newTask.runningProperty()); + uiTrackingPointPane.disableProperty().bind(newTask.runningProperty()); + uiBtnBox.disableProperty().bind(newTask.runningProperty()); + } + }); + + uiSelectionColumn = new TableColumn(ResourceBundle.getBundle(FXDefaultProcessingPane.class.getCanonicalName()).getString("selectionColumn")); + selectionComputer = input -> { + final BooleanProperty bp = new SimpleBooleanProperty(input, "selection", false); + bp.addListener(selectionListener); + bp.set(true); + + return bp; + }; + } + + @PostConstruct + public void postConstruct() { + dataset = session.getDataset(); + ArgumentChecks.ensureNonNull("dataset", dataset); + + final DataContext context = session.getDataContext(); + protocol = context.getProtocol(); + ArgumentChecks.ensureNonNull("protocol", protocol); + + site = context.getSite(); + ArgumentChecks.ensureNonNull("site", site); + + /* + 1- Indicator pane. + ================*/ + final List sortedIndicators = new ArrayList<>(session.getIndicators()); + sortedIndicators.removeIf(i -> !protocol.equals(i.getProtocol())); + Collections.sort(sortedIndicators); + initIndicatorPane(sortedIndicators); + + /* + 2- Year pane. + ============*/ + final List years = new ArrayList<>(); + for(final TrackingPoint trackingPoint : dataset.getTrackingPoints()){ + final int year = trackingPoint.getYear(); + + // Do not add the same year twice… + if(!years.contains(year)){ + years.add(year); + } + } + + // Sort years… + Collections.sort(years); + initYearPane(years); + + /* + 3- Tracking point pane. + =====================*/ + initTrackingPointPane(); + + /* + 4- LISTENERS. + =====================*/ + uiSelectionTable.getItems().addListener(this::tableContentChanged); + for (final IndicatorSelection is : indicatorSelections) { + is.selectedProperty.addListener(selectionListener); + } + + // FXTrackingPointPane must only display tracking point which year is selected into FXYearPane + yearSelections.forEach(yearSelection -> + yearSelection.selectedProperty().addListener(selectionListener) + ); + + /* + 4- RESTORE PROCESS CONTEXT. + =====================*/ + // Start by extracting previous indicators / years / filters + processContext = session.getProcessContext(); + final Set previousIndics = new HashSet<>(processContext.getIndicators()); + final HashSet previousYears = new HashSet<>(); + processContext.getTrackingPoints().forEach(tp -> previousYears.add(tp.getYear())); + if (processContext.getFilter() instanceof SimplePredicateChain) + processFilter = (SimplePredicateChain) processContext.getFilter(); + else { + processFilter = new SimplePredicateChain(); + if (processContext.getFilter() != null) + processFilter.predicates.add(processContext.getFilter()); + processContext.setFilter(processFilter); + } + final Set previousFilters = new HashSet(processFilter.predicates); + + // Restore selected indicators. + for (final IndicatorSelection is : indicatorSelections) + is.selectedProperty.set(previousIndics.contains(is.indicator)); + + // Find activated years + for (final YearSelection ys : yearSelections) + ys.selectedProperty.set(previousYears.contains(ys.year)); + + // Set same filter selection + if (!previousFilters.isEmpty()) + for (final Predicate p : uiSelectionTable.getItems()) + filterSelection.computeIfAbsent(p, selectionComputer).set(previousFilters.contains(p)); + } + + /** + * + * @return The selected {@link Indicator}s. + */ + public Collection getIndicators() { + return indicatorSelections.stream() + .filter(is -> is.selectedProperty().get()) + .map(IndicatorSelection::getIndicator) + .collect(Collectors.toList()); + } + + //////////////////////////////////////////////////////////////////////////// + // indicator pane // indicator pane // indicator pane // indicator pane + + /** + * Init sub-pane for displaying indicators. + */ + private void initIndicatorPane(final List indicators){ + + for(int i=0; i years){ + for (final Integer year : years) { + final YearSelection yearSelection = new YearSelection(year); + yearSelections.add(yearSelection); + final CheckBox checkBox = new CheckBox(); + checkBox.setAllowIndeterminate(false); + final Label label = new Label(); + label.setText(year.toString()); + yearSelection.selectedProperty.bindBidirectional(checkBox.selectedProperty()); + uiYearPane.getChildren().add(new HBox(checkBox, label)); + } + } + + /** + * A simple class associating a selection boolean state to a year. + */ + private static class YearSelection extends Selection { + + private final int year; + + public YearSelection(final int year){ + this.year = year; + } + + public int getYear(){return year;} + } + + + //////////////////////////////////////////////////////////////////////////// + // trackingPoint pane // trackingPoint pane // trackingPoint pane + + /** + * Init sub-pane for displaying {@link TrackingPoint}s. + */ + private void initTrackingPointPane(){ + for (final FilterTableSpi spi : pointTableSpis) { + if (spi.getProtocol() == session.getDataContext().getProtocol()) { + uiSelectionTable = spi.createEmptyTable(); + break; + } + } + + if (uiSelectionTable == null) { + uiSelectionTable = new FXTrackingPointTable(session); + } + + uiSelectionTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + uiSelectionTable.getColumns().add(0, (TableColumn) uiSelectionColumn); + uiSelectionTable.setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE); + + uiSelectionColumn.setCellValueFactory(param -> filterSelection.computeIfAbsent(param.getValue(), selectionComputer)); + uiSelectionColumn.setCellFactory(param -> { + final TableCell cell = new CheckBoxTableCell<>(); + cell.setEditable(true); + return cell; + }); + uiSelectionColumn.setEditable(true); + uiSelectionTable.setEditable(true); + uiSelectionTable.getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + uiTrackingPointPane.setCenter(uiSelectionTable); + } + + /** + * Filter dataset tracking points according to current year selection, to set + * {@link #uiTrackingPointPane} content. If no year is selected, all points + * are activated. + */ + private void resetSelection() { + uiSelectionTable.getSelectionModel().clearSelection(); + final Set selectedYears = yearSelections.stream() + .filter(ys -> ys.selectedProperty.get()) + .map(ys -> ys.year) + .collect(Collectors.toSet()); + + if (selectedYears.isEmpty()) { + uiSelectionTable.setPreFilter((input) -> false); + } else if (selectedYears.size() == yearSelections.size()) { + uiSelectionTable.setPreFilter(null); + } else { + final Function yearChecker = tp -> selectedYears.contains(tp.getYear()); + final Predicate preFilter = st -> { + return dataset.findPoint(st) + .map(yearChecker) + .orElse(false); + }; + uiSelectionTable.setPreFilter(preFilter); + } + } + + private void tableContentChanged(final ListChangeListener.Change c) { + while (c.next()) { + if (c.wasRemoved()) { + for (final Predicate p : c.getRemoved()) { + filterSelection.computeIfAbsent(p, selectionComputer).set(false); + } + } + + if (c.wasAdded()) { + for (final Predicate p : c.getAddedSubList()) { + filterSelection.computeIfAbsent(p, selectionComputer).set(true); + } + } + } + } + + //////////////////////////////////////////////////////////////////////////// + // Utilities // Utilities // Utilities // Utilities // Utilities // + + /** + * Utility abstract class providing a selection boolean state. + */ + private static abstract class Selection { + protected final BooleanProperty selectedProperty = new SimpleBooleanProperty(this, "selection", false); + + public ReadOnlyBooleanProperty selectedProperty(){ + return selectedProperty; + } + } + + /** + * Update UI/related components when either year, indicator or filter + * selection change. + * + * @param obs Observable selection which change. For this method to actually + * do something, the given observable must be a {@link ReadOnlyProperty} + * with non-null {@link ReadOnlyProperty#getBean() } property. + * @param oldValue + * @param newValue + */ + private void selectionChanged(final ObservableValue obs, Boolean oldValue, Boolean newValue) { + final Object bean; + if (obs instanceof ReadOnlyProperty) { + bean = ((ReadOnlyProperty)obs).getBean(); + } else { + bean = null; + } + + if (bean instanceof YearSelection) { + // Synchronize tracking points state with corresponding year. + resetSelection(); + + } else if (bean instanceof IndicatorSelection) { + final Indicator i = ((IndicatorSelection)bean).getIndicator(); + if (Boolean.TRUE.equals(newValue)) { + processContext.getIndicators().add(i); + } else { + processContext.getIndicators().remove(i); + } + } else if (bean instanceof Predicate) { + if (Boolean.TRUE.equals(newValue)) { + processFilter.predicates.add((Predicate) bean); + } else { + processFilter.predicates.remove((Predicate) bean); + } + processContext.setFilter(null); + processContext.setFilter(processFilter); + } + } + + //////////////////////////////////////////////////////////////////////////// + // Process management // Process management // Process management // + + @FXML + void nextStep(ActionEvent event) { + final Computer computer = new Computer(); + globalTask.set(computer); + + computer.stateProperty().addListener(obs -> { + if (computer.isDone()) { + switch (computer.getState()) { + case FAILED: + Rhomeo.showAlert(Alert.AlertType.ERROR, getFormatedResourceString("process-failed")); + break; + case SUCCEEDED: + session.setResults(computer.getValue()); + globalTask.set(null); + session.requestWorkflowStep(WorkflowStep.RESULT); + break; + } + } + }); + + TaskManager.INSTANCE.submit(computer); + } + + @FXML + void cancelProcess(ActionEvent event) { + final Task> task = globalTask.get(); + if (task != null) + task.cancel(true); + } + + /** + * Wrap processes generated by requested indicators, execute them and wait + * for completion of the entire process set. + */ + private class Computer extends Task> { + + private final List processes = new ArrayList<>(); + + @Override + protected Set call() throws Exception { + updateProgress(-1, -1); + updateTitle(getResourceString("process.prepare")); + for (final Indicator indicator : getIndicators()) { + processes.addAll(indicator.createProcesses(processContext)); + } + + session.getAdditionalValues().clear(); + if (processes.size() < 1) { + throw new IllegalStateException("Nothing to process."); + + } else if (processes.size() == 1) { + final Process wrapped = processes.get(0); + Platform.runLater(() -> { + wrapped.titleProperty().addListener((obs) -> updateTitle(wrapped.getTitle())); + wrapped.messageProperty().addListener((obs) -> updateTitle(wrapped.getMessage())); + wrapped.workDoneProperty().addListener((obs) -> updateProgress(wrapped.getWorkDone(), wrapped.getTotalWork())); + }); + + wrapped.run(); + return (Set) wrapped.get(); + + } else { + /* + * Compute a maximal progress for this process aggregator. + * If any process has no total work defined, the aggregator total is + * the number of processes to execute. Otherwise, its the sum of all + * process to execute. + * + * The same is done for the "work done" property. If the aggregated + * total is undefined, the work done is the number of finished + * processes. Otherwise, it's the sum of work done of each process. + */ + final Observable[] totals = new Observable[processes.size()]; + final Observable[] worksDone = new Observable[processes.size()]; + for (int i = 0; i < processes.size(); i++) { + totals[i] = processes.get(i).totalWorkProperty(); + worksDone[i] = processes.get(i).workDoneProperty(); + } + + final DoubleBinding totalBinding = Bindings.createDoubleBinding(() -> { + double result = 0; + double processTotal; + for (final Process p : processes) { + processTotal = p.getTotalWork(); + if (processTotal < 0) { + return (double) totals.length; + } + result += processTotal; + } + + return result; + }, totals); + + final DoubleBinding workDoneBinding = Bindings.createDoubleBinding(() -> { + double result = 0; + double processWork; + final boolean doCount = totalBinding.get() < 0; + for (final Process p : processes) { + if (doCount) { + if (p.isDone()) + result++; + } else { + processWork = p.getWorkDone(); + if (processWork >= 0) { + result += processWork; + } + } + } + + return result; + }, worksDone); + + Platform.runLater(() -> { + workDoneBinding.addListener((obs) -> updateProgress(workDoneBinding.get(), totalBinding.get())); + }); + + final Set indices = new HashSet<>(); + updateTitle(getResourceString("process.go")); + + for (final Process p : processes) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + TaskManager.INSTANCE.submit(p); + } + + for (final Process p : processes) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + indices.addAll(p.get()); + } + + return indices; + } + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (final Process p : processes) { + p.cancel(true); + } + return super.cancel(mayInterruptIfRunning); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultResultPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultResultPane.java new file mode 100644 index 0000000..38a48d5 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXDefaultResultPane.java @@ -0,0 +1,520 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalDescription; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.data.Warning; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.AbstractParametrableIndex; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.IndexMapper; +import fr.cenra.rhomeo.api.result.IndexSpi; +import fr.cenra.rhomeo.api.ui.AdditionalResultSpi; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.result.ResultWriter; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.FileSystem; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Set; +import java.util.logging.Level; +import javafx.application.Platform; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.ToggleButton; +import javafx.scene.image.ImageView; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; + + +/** + * The result pane controller. + * + * @author Samuel Andrés (Geomatys) + * + */ +@Component("fr.cenra.rhomeo.fx.FXDefaultResultPane") +@Scope("prototype") +public class FXDefaultResultPane extends VBox implements InternationalResource { + + /** + * Key (or key-part) that must be used to retrieve specific headers from + * {@link IndexSpi} and or base classes implementing {@link InternationalResource}, + * to complementary table headers. + */ + public static final String COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY = "result-pane.complementary-table"; + + /** + * Fields taken into account to build columns mapping base classes into + * complementary tables. + */ + private static final List BASE_FIELDS = Arrays.asList("name", "year"); + + @FXML private Label uiSiteLbl; + @FXML private TableView> uiSitePane; + @FXML private GridPane uiInvalidateGridPane; + @FXML private TableColumn, Integer> uiSiteYearColumn; + @FXML private ToggleButton uiDisplayValuesToggleBtn; + @FXML private ScrollPane uiAdditionalValuesScrollPane; + @FXML private VBox uiAdditionalValuesBox; + @FXML private VBox uiTableBox; + + @FXML private Label uiYearsLbl; + @FXML private Label uiIndicatorLbl; + @FXML private Label uiDataStatusLbl; + @FXML private Label uiAdditionalLabel; + + @Autowired + private Session session; + + @Autowired + private Validator validator; + + @Autowired + private ResultWriter resultWriter; + + @Autowired + private List additionalSpis; + + private final SimpleBooleanProperty isInvalid = new SimpleBooleanProperty(false); + + /** + * + */ + public FXDefaultResultPane() { + Rhomeo.loadFXML(this, FXDefaultResultPane.class); + uiSitePane.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + uiSitePane.setFixedCellSize(25); + uiAdditionalLabel.visibleProperty().bind(Bindings.isNotEmpty(uiAdditionalValuesBox.getChildren())); + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + public void postConstruct() { + final Set results = session.getResults(); + ArgumentChecks.ensureNonNull("results", results); + + /* + 1- Schedule. + ==========*/ + + // Determine indicators and schedule results. + + final Map> mainResults = new HashMap<>(); + final ArrayList mainSpis = new ArrayList<>(); + final List indicators = new ArrayList<>(); + final Map> complementaryResults = new HashMap<>(); + final Map> byYearComplementary = new HashMap<>(); + final Map> indexSpis = new HashMap<>(); + for(final Index index : results){ + + // Seach for index indicators. + final Indicator indicator = index.getSpi().getIndicator(); + if(!indicators.contains(indicator)){ + indicators.add(indicator); + } + + // Detect main indices for each indicator. + if (index.getSpi().isPrimary()) { + final int year = index.getYear(); + final IndexMapper mapper = mainResults.computeIfAbsent(year, (input) -> new IndexMapper<>(input)); + if(!mainSpis.contains(index.getSpi())) + mainSpis.add(index.getSpi()); + mapper.setValue(index.getSpi().getName(), index.getValue()); + } + // Schedule AbstractParametrableIndexes by base type. + else if(index instanceof AbstractParametrableIndex) { + final AbstractParametrableIndex parametrableIndex = (AbstractParametrableIndex) index; + final Class baseClass = parametrableIndex.getBaseClass(); + final Object base = parametrableIndex.getBase(); + final IndexSpi indexSpi = parametrableIndex.getSpi(); + + if(complementaryResults.get(baseClass)==null){ + complementaryResults.put(baseClass, new HashMap<>()); + } + if(complementaryResults.get(baseClass).get(base)==null){ + complementaryResults.get(baseClass).put(base, new IndexMapper<>(base)); + } + final IndexMapper indexMapper = complementaryResults.get(baseClass).get(base); + indexMapper.setValue(indexSpi.getName(), parametrableIndex.getValue()); + + // + if(indexSpis.get(baseClass)==null) indexSpis.put(baseClass, new ArrayList<>()); + final List spis = indexSpis.get(baseClass); + if(!spis.contains(indexSpi)) spis.add(indexSpi); + + } else { + final int year = index.getYear(); + final IndexMapper mapper = byYearComplementary.computeIfAbsent(year, (input) -> new IndexMapper<>(input)); + final List spis = indexSpis.computeIfAbsent(Integer.class, i -> new ArrayList<>()); + if(!spis.contains(index.getSpi())) spis.add(index.getSpi()); + mapper.setValue(index.getSpi().getName(), index.getValue()); + } + } + + // Sort indicators. + Collections.sort(indicators); + + /* + 2- Main indicators. + =================*/ + + // Build header message listing indicators + final StringBuilder sbIndicator = new StringBuilder(); + sbIndicator.append("Indicateur(s) : "); + + final Protocol currentProtocol = session.getDataContext().getProtocol(); + boolean firstIndic = true; + for(final Indicator indicator : indicators){ + if (!firstIndic) { + sbIndicator.append(", "); + } + + sbIndicator.append(indicator.getName()).append(" - ").append(indicator.getRemarks()); + firstIndic = false; + } + sbIndicator.append(" - ").append(currentProtocol.getRemarks()); + + // Create columns for main indicator pane. + uiSiteYearColumn.setCellValueFactory(value -> value.getValue().keyProperty()); + mainSpis.stream().sorted().forEach(spi -> { + // Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name) + final TableColumn, Number> column = new TableColumn<>(getIndexSpiColumnName(spi)); + column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY); + column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName())); + uiSitePane.getColumns().add(column); + }); + uiSitePane.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + // Feed main indicator pane. + final ObservableList> items = FXCollections.observableArrayList(mainResults.values()); + uiSitePane.setItems(items); + uiSitePane.prefHeightProperty().bind(Bindings.size(items).multiply(uiSitePane.fixedCellSizeProperty()).add(40)); + + // Summary + uiYearsLbl.setGraphic(new ImageView(Rhomeo.ICON_CIRCLE_GRAY)); + final StringBuilder sbYears = new StringBuilder(); + sbYears.append("Année(s) : "); + boolean firstYear = true; + final List years = new ArrayList<>(mainResults.keySet()); + Collections.sort(years); + for (final Integer year : years) { + if (!firstYear) { + sbYears.append(", "); + } + sbYears.append(year); + firstYear = false; + } + uiYearsLbl.setText(sbYears.toString()); + + uiIndicatorLbl.setGraphic(new ImageView(Rhomeo.ICON_CIRCLE_GRAY)); + uiIndicatorLbl.setText(sbIndicator.toString()); + + isInvalid.addListener((obj) -> { + if (!isInvalid.get()) { + uiDataStatusLbl.setGraphic(new ImageView(Rhomeo.ICON_CHECK_GREEN)); + uiDataStatusLbl.setText("Les données sont conformes au protocole : "+ currentProtocol.getRemarks()); + } else { + uiDataStatusLbl.setGraphic(new ImageView(Rhomeo.ICON_TIMES_RED)); + uiDataStatusLbl.setText("Les données ne sont pas conformes au protocole : "+ currentProtocol.getRemarks()); + } + }); + isInvalid.set(true); + + uiInvalidateGridPane.visibleProperty().bind(isInvalid); + uiInvalidateGridPane.managedProperty().bind(uiInvalidateGridPane.visibleProperty()); + + uiTableBox.visibleProperty().bind(isInvalid.not().or(uiDisplayValuesToggleBtn.selectedProperty())); + uiTableBox.managedProperty().bindBidirectional(uiSiteLbl.visibleProperty()); + + // Verify validation. + validate(); + + /* + 3- Other indicators. + ==================*/ + + // A table for complementary results displayed by year. + if (!byYearComplementary.isEmpty()) { + final TableView> table = new TableView<>(FXCollections.observableArrayList(byYearComplementary.values())); + final TableColumn, Integer> yearColumn = new TableColumn("Année"); + yearColumn.setCellValueFactory(input -> input.getValue() == null ? null : input.getValue().keyProperty()); + table.getColumns().add(yearColumn); + // index value columns + indexSpis.get(Integer.class).stream().sorted().forEach(spi -> { + // Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name) + final TableColumn, Number> column = new TableColumn<>(getIndexSpiColumnName(spi)); + column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY); + column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName())); + table.getColumns().add(column); + }); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + uiAdditionalValuesBox.getChildren().add(table); + } + + // Create one TableView by base class. + for(final Class baseClass : complementaryResults.keySet()){ + try { + final TableView tableView = buildIndexMapperTableView(baseClass, indexSpis.get(baseClass)); + final ObservableList additionalItems = FXCollections.observableArrayList(complementaryResults.get(baseClass).values()); + tableView.setItems(additionalItems); + tableView.setFixedCellSize(25); + tableView.setMinHeight(70); + tableView.setMaxHeight(300); + tableView.prefHeightProperty().bind(Bindings.size(additionalItems).multiply(tableView.fixedCellSizeProperty()).add(40)); + uiAdditionalValuesBox.getChildren().add(tableView); + } catch (IntrospectionException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Could not create tableview mapping {0}.", baseClass); + } + } + + final Button nextStepButton = new Button(getResourceString("proceed")); + nextStepButton.getStyleClass().add("contrast"); + nextStepButton.setOnAction(value -> { + session.requestWorkflowStep(WorkflowStep.FINALIZATION); + }); + nextStepButton.visibleProperty().bind(isInvalid.not()); + nextStepButton.managedProperty().bind(nextStepButton.visibleProperty()); + final HBox bottomHBox = new HBox(nextStepButton); + bottomHBox.setAlignment(Pos.CENTER_RIGHT); + bottomHBox.setFillHeight(true); + bottomHBox.setPrefSize(USE_PREF_SIZE, USE_PREF_SIZE); + getChildren().add(bottomHBox); + + for (AdditionalResultSpi spi : additionalSpis) { + spi.getResultNode(session.getAdditionalValues()).ifPresent(node -> uiAdditionalValuesBox.getChildren().add(node)); + } + } + + /** + * Verify dataset validation. + */ + private void validate() { + final Runnable taskInit = () -> TaskManager.INSTANCE.submit("Validation du lot de données", () -> { + final Set> violations = validator.validate(session.getProcessContext().getData()); + final Iterator> it = violations.iterator(); + while (it.hasNext()) { + if (!it.next().getConstraintDescriptor().getPayload().contains(Warning.class)) { + return true; + } + } + + return false; + + }).setOnSucceeded(evt -> Platform.runLater(() -> isInvalid.set((boolean)evt.getSource().getValue()))); + if (Platform.isFxApplicationThread()) + taskInit.run(); + else Platform.runLater(taskInit); + } + + @FXML + public void exportData() { + final String initialFileName = session.getDataContext().getSite().getName().concat(".zip"); + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter); + if (outputFile == null) { + return; + } + + TaskManager.INSTANCE.submit("Export de résultats", () -> { + try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputFile.toPath())) { + resultWriter.prepareWriting(fs.getRootDirectories().iterator().next()).writeMetadataText().writeResults().writeAdditionalValues(); + } + return true; + }); + } + + /** + * Builds a complementary table for indexes associated to a given base class (for instance {@link TrackingPoint}). + * + * This method use introspection on the given class, but it only displays information from fields which name + * are specified in {@link FXDefaultResultPane#BASE_FIELDS}. + * + * @param + * @param baseClass + * @return + * + * @throws IntrospectionException + */ + private TableView> buildIndexMapperTableView(final Class baseClass, final List indexSpis) throws IntrospectionException { + + final BeanInfo beanInfo = Introspector.getBeanInfo(baseClass); + + final TableView> tableView = new TableView<>(); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + // Index base columns + for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + + final String dName = desc.getName(); + if(BASE_FIELDS.contains(dName)){ + + final TableColumn, ?> col = new TableColumn<>(getBaseColumnName(baseClass, dName)); + col.setCellValueFactory(value -> { + try { + final Method reader = desc.getReadMethod(); + if(reader!=null && value!=null && value.getValue()!=null && value.getValue() instanceof IndexMapper){ + final T target = ((IndexMapper) value.getValue()).getKey(); + if(target!=null){ + return new SimpleObjectProperty(reader.invoke(target)); + } + else return null; + } + else return null; + } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException ex) { + RhomeoCore.LOGGER.log(Level.INFO, "Unable to build CellValueFactory for property {0}.", dName); + return null; + } + }); + tableView.getColumns().add(col); + } + } + + // index value columns + indexSpis.stream().sorted().forEach(spi -> { + // Sometimes the displayed column name seems to be from the indexSpi itself, sometimes from its indicator (i.e. a specific name) + final TableColumn, Number> column = new TableColumn<>(getIndexSpiColumnName(spi)); + column.setCellValueFactory(value -> value.getValue().valueProperty(spi.getName())); + column.setCellFactory(Rhomeo.ROUNDING_CELL_FACTORY); + tableView.getColumns().add(column); + }); + + return tableView; + } + + /** + * Builds a name for the column mapping a given base class field in a complementary table. + * + * @param baseClass + * @param propertyName + * @return + */ + private static String getBaseColumnName(final Class baseClass, final String propertyName){ + + // If indexSpi implements InternationalResource, try to use specific property + if(InternationalResource.class.isAssignableFrom(baseClass)){ + + // 1- Try to retrieve some resource specific to a complementary result table. + try{ + return InternationalResource.getResourceString(baseClass, propertyName, COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY); + } + catch(MissingResourceException ex1){ + + // 2- Try to retrive resource mapping the property name. + try{ + return InternationalResource.getResourceString(baseClass, propertyName); + } + catch(MissingResourceException ex2){ + RhomeoCore.LOGGER.log(Level.FINEST, "Missing ressource to display column name from {0}", InternationalResource.class.getSimpleName()); + } + } + } + + // If no better can be done, return the property name. + return propertyName; + } + + /** + * Builds a name for the column mapping a given {@link IndexSpi} in a complementary table. + * + * @param indexSpi + * @return + */ + private static String getIndexSpiColumnName(final IndexSpi indexSpi){ + + // If indexSpi implements InternationalResource, try to use specific property + if(indexSpi instanceof InternationalResource){ + try{ + return ((InternationalResource) indexSpi).getResourceString(COMPLEMENTARY_TABLE_HEADER_PROPERTY_KEY); + } + catch(MissingResourceException e){ + RhomeoCore.LOGGER.log(Level.FINEST, "Missing ressource to display column name from {0}", InternationalResource.class.getSimpleName()); + } + } + + // If previous try failed and indexSpi implements InternationalDescription, use InternationalDescription label. + if(indexSpi instanceof InternationalDescription) return ((InternationalDescription) indexSpi).getLabel(); + + // If no other solution is found, return indexSpi name. + return indexSpi.getTitle(); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFinalizationPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFinalizationPane.java new file mode 100644 index 0000000..0445b3b --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFinalizationPane.java @@ -0,0 +1,248 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.DashboardResultsManager; +import fr.cenra.rhomeo.core.result.ResultStorage; +import fr.cenra.rhomeo.core.result.ResultWriter; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; + +import javafx.event.ActionEvent; + + +/** + * @author Cédric Briançon (Geomatys) + */ +@Component("fr.cenra.rhomeo.fx.FXFinalizationPane") +@Scope("prototype") +public class FXFinalizationPane extends VBox implements InternationalResource { + @FXML + private Button addToDashboardBtn; + + @FXML + private Button publishBtn; + + @Autowired + private Session session; + + @Autowired + private DashboardResultsManager resultsManager; + + @Autowired + private ResultWriter resultWriter; + + @Autowired + private ResultStorage resultStorage; + + /** + * Stores if the results has been published or not. + */ + private final BooleanProperty publishedProperty = new SimpleBooleanProperty(false); + + /** + * Set to true once current results are attached to dashboard. + */ + private final BooleanProperty onDashboardProperty = new SimpleBooleanProperty(false); + + public FXFinalizationPane() { + Rhomeo.loadFXML(this, FXFinalizationPane.class); + } + + /** + * Get back to the dashboard to start a new entry of results. + */ + @FXML + private void newForm() { + session.requestWorkflowStep(null); + } + + @FXML + private void exportResults() { + final String initialFileName = new StringBuilder(session.getDataContext().getSite().getName()) + .append('_').append(session.getDataContext().getProtocol().getName()).append(".zip") + .toString(); + + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(initialFileName, this, zipFilter); + if (outputFile == null) { + return; + } + + TaskManager.INSTANCE.submit("Export de résultats", () -> { + final Path outputPath = outputFile.toPath(); + Files.deleteIfExists(outputPath); + try (final FileSystem fs = RhomeoCore.toZipFileSystem(outputPath)) { + resultWriter.prepareWriting(fs.getRootDirectories().iterator().next()) + .writeMetadataText() + .writeResults() + .writeAdditionalValues(); + } + return true; + }); + } + + /** + * Add current results to the dashboard for the current site / protocol. + */ + @FXML + private void addToDashboard() { + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, + getFormatedResourceString("alert-add-to-dashboard"), + ButtonType.YES, ButtonType.NO); + alert.setResizable(true); + final ButtonType res = alert.showAndWait().get(); + if (res == ButtonType.YES) { + final TaskManager.MockTask task = new TaskManager.MockTask("Sauvegarde des résultats", () -> { + resultStorage.store(); + resultsManager.addDashboardResultsItems(getResults()); + return true; + }); + + addToDashboardBtn.disableProperty().bind(task.runningProperty()); + + task.setOnSucceeded(evt -> Platform.runLater(() -> { + addToDashboardBtn.disableProperty().unbind(); + addToDashboardBtn.setDisable(true); + onDashboardProperty.set(true); + })); + + TaskManager.INSTANCE.submit(task); + } + } + + /** + * Get current results. + * + * @return + */ + private List getResults() { + final List items = new ArrayList<>(); + + final Set results = session.getResults(); + if (results != null && !results.isEmpty()) { + final DataContext context = session.getDataContext(); + results.forEach(result -> { + if (result.getSpi().getName().equals(result.getSpi().getIndicator().getDefaultIndex())) { + // Do not keep tracking point index, just results for a year. + items.add(new DashboardResultItem(result.getYear(), context.getSite().getName(), + result.getSpi().getIndicator().getName(), result.getValue() == null ? null : result.getValue().doubleValue(), + publishedProperty.get())); + } + }); + } + + return items; + } + + @FXML + private void publishResults(final ActionEvent evt) { + final List results = getResults(); + final Task publish = publishSiteResults(); + publishBtn.disableProperty().bind(publish.runningProperty()); + publish.setOnSucceeded(event -> Platform.runLater(() -> { + publishedProperty.set(true); + publishBtn.disableProperty().unbind(); + publishBtn.setDisable(true); + if (onDashboardProperty.get()) { + TaskManager.INSTANCE.submit("Mise à jour des résultats locaux", () -> { + for (final DashboardResultItem item : results) + item.setPublished(true); + resultsManager.updateResults(results); + return true; + }); + } + })); + + publish.setOnFailed(event -> Platform.runLater(() -> Rhomeo.showAlert(Alert.AlertType.ERROR, "La publication des résultats a échouée !"))); + + TaskManager.INSTANCE.submit(publish); + } + + /** + * Publish site results on the FTP. + * Structure on the FTP is: + *
+     * results
+     * |- county code
+     * |--|- SHA1 geometry site
+     * |--|--|- protocol
+     * |--|--|--|- UUID
+     * |--|--|--|--|- site.geojson
+     * |--|--|--|--|- metadata.json
+     * |--|--|--|--|- result.csv
+     * ...
+     * 
+ * + * @return Publication task + */ + private Task publishSiteResults() { + return new TaskManager.MockTask<>("Publication de résultats", () -> { + resultsManager.publishResults(); + return true; + }); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFullSpecificResultPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFullSpecificResultPane.java new file mode 100644 index 0000000..90c8831 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXFullSpecificResultPane.java @@ -0,0 +1,266 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.result.ResultStorage; +import fr.cenra.rhomeo.core.result.SimpleLocationIndex; +import fr.cenra.rhomeo.core.result.SimpleYearIndex; +import java.beans.IntrospectionException; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.layout.VBox; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * UI to display detailed information about all computed information for a given + * year and {@link Indicator}. This includes the value of the main indicator + * {@link Index} and the potential ones. + * + * @author Samuel Andrés (Geomatys) + */ +@Component("fr.cenra.rhomeo.fx.FXFullSpecificResultPane") +@Lazy +@Scope("prototype") +public class FXFullSpecificResultPane extends VBox { + + private final IndicatorValuesByYearItem selectedItem; + + private final Indicator indicator; + + private final String siteName; + + @Autowired + private ResultStorage resultStorage; + + public FXFullSpecificResultPane(final IndicatorValuesByYearItem selectedItem, final Indicator indicator, final String siteName) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException { + this.selectedItem = selectedItem; + this.indicator = indicator; + this.siteName = siteName; + setPadding(new Insets(10.)); + setSpacing(10.); + setPrefSize(USE_COMPUTED_SIZE, USE_COMPUTED_SIZE); + } + + @PostConstruct + private void postConstruct() throws IOException, IntrospectionException, InstantiationException, IllegalAccessException { + final String indicatorName = indicator.getName(); + final IndicatorValuesByYearItem.PublishedValue pub = selectedItem.getValueForIndicator(indicatorName); + + if (pub != null) { + final DashboardResultItem dItem = new DashboardResultItem(selectedItem.getYear(), siteName, indicatorName, pub.getValue(), pub.isPublished()); + final ArrayList result = new ArrayList<>(); + final Optional mainResults = resultStorage.getMainResults(dItem); + if (mainResults.isPresent()) { + result.addAll((List) readResult(mainResults.get())); + } + + final Optional otherResults = resultStorage.getAdditionalResults(dItem); + if (otherResults.isPresent()) { + result.addAll((List) readResult(otherResults.get())); + } + + buildTables(result); + } + } + + /** + * Create the table view containing the result value list mapping their class. + * + * @param result + * @throws IntrospectionException + */ + private void buildTables(List result) throws IntrospectionException { + final List locValues = new ArrayList<>(); + final Iterator it = result.iterator(); + SimpleYearIndex current; + while (it.hasNext()) { + current = it.next(); + if (current instanceof SimpleLocationIndex) { + locValues.add((SimpleLocationIndex) current); + it.remove(); + } + } + + if (!result.isEmpty()) { + final TableView table = buildSimpleTableView(FXCollections.observableList((List)result)); + getChildren().add(table); + } + + if (!locValues.isEmpty()) { + final TableView table = buildLocationTableView(FXCollections.observableList(locValues)); + getChildren().add(table); + } + } + + /** + * Builds a {@link TableView} for tuples indexed by year. + * + * @param tuples + * @return + * @throws IntrospectionException + * @see SimpleYearIndex + */ + private TableView buildSimpleTableView(ObservableList values) throws IntrospectionException { + final TableView table = new TableView<>(); + addCommonColumns(table); + table.setItems(values); + initResizePolicy(table); + return table; + } + + /** + * Configure input table resize strategy, by setting preferred height + * property to follow table item number. + * + * @param table The modified table. + */ + private void initResizePolicy(final TableView table) { + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + table.setMinHeight(50); + table.setFixedCellSize(30); + table.prefHeightProperty().bind(Bindings.createDoubleBinding(() -> table.getFixedCellSize() * table.getItems().size() + 30, table.getItems(), table.fixedCellSizeProperty())); + } + + private void addCommonColumns(final TableView table) { + final TableColumn titleColumn = new TableColumn<>("Nom"); + titleColumn.setEditable(false); + titleColumn.setCellValueFactory(FXFullSpecificResultPane::getTitle); + + final TableColumn valueColumn = new TableColumn<>("Valeur"); + valueColumn.setEditable(false); + valueColumn.setCellFactory(RhomeoCore.ROUNDING_CELL_FACTORY); + valueColumn.setCellValueFactory(FXFullSpecificResultPane::getValue); + + table.getColumns().add((TableColumn)titleColumn); + table.getColumns().add((TableColumn)valueColumn); + } + + /** + * Builds a {@link TableView} for tuples indexed by year and location. + * + * @param tuples + * @return + * @throws IntrospectionException + * @see SimpleLocationIndex + */ + private TableView buildLocationTableView(ObservableList values) throws IntrospectionException { + final TableView table = new TableView<>(); + addCommonColumns(table); + + final TableColumn locColumn = new TableColumn<>(); + if (indicator.getProtocol().getName().equalsIgnoreCase("P04")) + locColumn.setText("Habitat"); + else + locColumn.setText("Point de suivi"); + locColumn.setCellValueFactory(FXFullSpecificResultPane::getLocation); + + table.getColumns().add(1, locColumn); + + table.setItems(values); + initResizePolicy(table); + return table; + } + + /** + * Get the results stored in the file which path is given as a parameter. + * + * The results are retrieved as a list of either instances of {@link SimpleLocationIndex} + * or instances of {@link SimpleYearIndex}. The method returns an entry which + * the value it the list of results, and the key is the instances' class. + * + * @param resultsPath + * @return + * @throws IOException + * @throws IntrospectionException + * @throws InstantiationException + * @throws IllegalAccessException + */ + private static List readResult(final Path resultsPath) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException { + try { + return new CSVDecoder(resultsPath, SimpleLocationIndex.class).decode(); + } catch (Exception ex) { + RhomeoCore.LOGGER.info("Impossible de lire en tant que résultat localisé. Essai de lecture en résultat non localisé."); + return new CSVDecoder(resultsPath, SimpleYearIndex.class).decode(); + } + } + + private static ObservableValue getTitle(final TableColumn.CellDataFeatures cdf) { + final SimpleYearIndex index = cdf.getValue(); + if (index == null) + return null; + return new SimpleStringProperty(index.getName()); + } + + private static ObservableValue getValue(final TableColumn.CellDataFeatures cdf) { + final SimpleYearIndex index = cdf.getValue(); + if (index == null) + return null; + return new SimpleObjectProperty<>(index.getValue()); + } + + private static ObservableValue getLocation(final TableColumn.CellDataFeatures cdf) { + final SimpleLocationIndex index = cdf.getValue(); + if (index == null) + return null; + return new SimpleStringProperty(index.getLocation()); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXInitialDialogPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXInitialDialogPane.java new file mode 100644 index 0000000..b754512 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXInitialDialogPane.java @@ -0,0 +1,127 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.prefs.Preferences; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import org.geotoolkit.gui.javafx.util.TaskManager; + +/** + * Panel for the initial dialog when opening the application. + * + * @author Cédric Briançon (Geomatys) + */ +public class FXInitialDialogPane extends BorderPane { + + public static final String DISPLAY_RULE_KEY = "display_disclaimer_on_startup"; + + @FXML + private Label uiVersion; + + @FXML + private ImageView uiWarning; + + @FXML + private CheckBox displayOnStartup; + + @FXML + private Hyperlink uiEMail; + + @FXML + private Hyperlink uiHref; + + @FXML + private Button uiClose; + + private final Preferences displayPref; + + public FXInitialDialogPane() { + Rhomeo.loadFXML(this, FXInitialDialogPane.class); + displayPref = Preferences.userNodeForPackage(FXInitialDialogPane.class); + displayOnStartup.setSelected(!displayPref.getBoolean(DISPLAY_RULE_KEY, true)); + displayOnStartup.selectedProperty().addListener((obs, oldValue, newValue) -> { + displayPref.putBoolean(DISPLAY_RULE_KEY, !newValue); + }); + + uiWarning.setImage(Rhomeo.ICON_WARNING_ORANGE); + + uiEMail.setOnAction(evt -> Rhomeo.mail(uiEMail.getText())); + uiHref.setOnAction(evt -> { + try { + Rhomeo.browse(new URL(uiHref.getText())); + } catch (final MalformedURLException e) { + // mimic task failure + TaskManager.INSTANCE.submit("Ouverture d'un lien", () -> { + if (e != null) + throw e; + else + return false; + }); + } + }); + + final String version = Rhomeo.getVersion(); + if (version == null || version.isEmpty()) + uiVersion.setText("Version de développement"); + else + uiVersion.setText("Version ".concat(version)); + + Platform.runLater(() -> uiClose.requestFocus()); + } + + /** + * Close the dialog box when clicking on the accept button. + */ + @FXML + private void accept() { + final Stage stage = (Stage) this.getScene().getWindow(); + stage.close(); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXMainPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXMainPane.java new file mode 100644 index 0000000..7a29d00 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXMainPane.java @@ -0,0 +1,158 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.core.Session; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.scene.control.MenuButton; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.stage.Modality; +import javafx.stage.Stage; +import org.geotoolkit.gui.javafx.util.ProgressMonitor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import org.geotoolkit.gui.javafx.util.TaskManager; + +/** + * Main application panel. + * + * @author Cédric Briançon (Geomatys) + */ +@Component +public class FXMainPane extends BorderPane implements InternationalResource { + + @FXML + private MenuButton uiMenuButton; + + @Autowired + private FXRhomeoWizard wizard; + + @Autowired + private Session session; + + public FXMainPane() { + Rhomeo.loadFXML(this, FXMainPane.class); + initProgressMonitor(); + + uiMenuButton.setGraphic(new ImageView(Rhomeo.ICON_BARS_WHITE)); + uiMenuButton.setFocusTraversable(false); + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + private void postConstruct() { + setCenter(wizard); + } + + /** + * Inserts the {@link ProgressMonitor} before the main menu button. + * HACK : Add an item in progress monitor list, allowing to clear list of + * tasks in error. + */ + private void initProgressMonitor() { + final ProgressMonitor progressMonitor = new ProgressMonitor(TaskManager.INSTANCE); + progressMonitor.getStylesheets().add(FXDashboardPane.class.getResource("/fr/cenra/rhomeo/ProgressMonitor.css").toString()); + + final HBox parent = ((HBox) uiMenuButton.getParent()); + parent.getChildren().add(parent.getChildren().size()-1, progressMonitor); + } + + /** + * Open preferences window. + */ + @FXML + private void showPreferences() { + final Stage prefStage = Rhomeo.newStage(); + prefStage.setTitle(getResourceString("preferences")); + prefStage.initModality(Modality.APPLICATION_MODAL); + final Scene scene = new Scene(session.createPrototype(FXRhomeoPreferencePane.class)); + prefStage.setScene(scene); + prefStage.setMinWidth(530); + prefStage.setMinHeight(230); + prefStage.show(); + prefStage.requestFocus(); + } + + /** + * Open references documents page. + */ + @FXML + private void showRefDocuments() { + final Stage prefStage = Rhomeo.newStage(); + prefStage.setTitle(getResourceString("ref-docs")); + prefStage.initModality(Modality.APPLICATION_MODAL); + final Scene scene = new Scene(session.createPrototype(FXRhomeoReferencePane.class)); + scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH); + prefStage.setScene(scene); + prefStage.setMinWidth(800); + prefStage.setMinHeight(600); + prefStage.show(); + prefStage.requestFocus(); + } + + /** + * Open the about dialog. + */ + @FXML + private void showAboutDialog() { + final Stage popup = Rhomeo.createWelcomePopup(); + popup.initOwner(this.getScene().getWindow()); + + popup.show(); + popup.requestFocus(); + } + + /** + * Quit the application. + */ + @FXML + private void quit() { + session.close(); + System.exit(0); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.java new file mode 100644 index 0000000..ecd8a51 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.java @@ -0,0 +1,273 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.ReferenceManager; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.ToggleGroup; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import org.geotoolkit.gui.javafx.util.FXTableView; +import org.geotoolkit.gui.javafx.util.TaskManager; + +import java.beans.IntrospectionException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Handle available versions for a reference. + * + * @param the reference to display available versions. + * @author Cédric Briançon (Geomatys) + */ +public class FXProtocolReferencesPane extends BorderPane { + @FXML + private Label uiRefNameLbl; + + private final FXTableView referencesTable; + + private final ReferenceDescription description; + + private static final String CSS_BUTTON_CLASS = "install-button"; + + public FXProtocolReferencesPane(final ReferenceDescription description) throws IntrospectionException { + this.description = description; + + Rhomeo.loadFXML(this); + uiRefNameLbl.setText("Référentiel "+ description.getTitle()); + + referencesTable = new FXTableView<>(); + referencesTable.setEditable(true); + referencesTable.setMinHeight(90); + referencesTable.setPrefHeight(getMinHeight()); + referencesTable.setMaxHeight(280); + referencesTable.setFixedCellSize(25); + + final TableColumn availableCol = new TableColumn<>("Versions disponibles"); + availableCol.setCellValueFactory(value -> new SimpleStringProperty(String.valueOf(value.getValue().getVersion()))); + referencesTable.getColumns().add(availableCol); + referencesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + final TableColumn installedCol = new TableColumn<>("Installées"); + installedCol.setCellValueFactory(value -> value.getValue().installedProperty); + installedCol.setCellFactory(param -> + new TableCell() { + @Override + protected void updateItem(Boolean item, boolean empty) { + setText(null); + setGraphic(null); + setAlignment(Pos.CENTER); + if (item != null && !item) { + final Button installBtn = new Button("Installer"); + installBtn.getStyleClass().add(CSS_BUTTON_CLASS); + installBtn.setOnAction(event -> { + final ReferenceManager referenceManager; + try { + referenceManager = ReferenceManager.getOrCreate(description); + } catch (IntrospectionException ex) { + throw new RhomeoRuntimeException(ex); + } + + final Task installTask = referenceManager.install(((ReferenceLineItem)getTableRow().getItem()).getVersion()); + installTask.setOnSucceeded(success -> { + ((ReferenceLineItem)getTableRow().getItem()).setInstalled(true); + }); + installBtn.disableProperty().bind(installTask.runningProperty()); + TaskManager.INSTANCE.submit(installTask); + }); + setGraphic(installBtn); + } else if (!empty && item != null){ + setGraphic(new ImageView(Rhomeo.ICON_CHECK_GREEN)); + } + } + } + ); + referencesTable.getColumns().add(installedCol); + + final TableColumn selectedCol = new TableColumn<>("Sélectionnées"); + selectedCol.setCellValueFactory(value -> value.getValue().selectedProperty); + final ToggleGroup group = new ToggleGroup(); + selectedCol.setCellFactory(param -> new RadioButtonTableCell(group)); + selectedCol.setEditable(true); + referencesTable.getColumns().add(selectedCol); + + final DataContext dc = Session.getInstance().getDataContext(); + final ReferenceManager referenceManager = ReferenceManager.getOrCreate(description); + + final Set installedVersions = referenceManager.getInstalledVersions(); + final Set distantVersions = referenceManager.getDistantVersions(); + final HashSet allVersions = new HashSet<>(installedVersions); + allVersions.addAll(distantVersions); + + final Version selectedVersion = dc.getReferences().get(description.getReferenceType()); + final ObservableList items = FXCollections.observableArrayList(); + for (final Version ver : allVersions) { + items.add(new ReferenceLineItem(ver, installedVersions.contains(ver), ver.equals(selectedVersion))); + } + Collections.sort(items); + referencesTable.setItems(FXCollections.observableList(items)); + referencesTable.setPrefHeight((items.size() + 1) * referencesTable.getFixedCellSize()); + + setCenter(referencesTable); + } + + public ObservableList getItems() { + return referencesTable.itemsProperty().get(); + } + + public Class getReferenceClass() { + return description.getReferenceType(); + } + + private class RadioButtonTableCell extends TableCell { + private RadioButton radioButton; + private BooleanProperty selProperty; + + /** + * Cell containing a radio button in the given group. + * + * @param group + */ + public RadioButtonTableCell(final ToggleGroup group) { + radioButton = new RadioButton(); + if (group != null) { + radioButton.setToggleGroup(group); + } + } + + @Override + protected void updateItem(Boolean item, boolean empty) { + super.updateItem(item, empty); + + if (empty) { + setText(null); + setGraphic(null); + } else { + // First remove potential binding + if (selProperty != null) { + radioButton.selectedProperty().unbindBidirectional(selProperty); + } + setGraphic(radioButton); + setAlignment(Pos.CENTER); + radioButton.visibleProperty().bind(((ReferenceLineItem)getTableRow().getItem()).installedProperty); + + final ObservableValue obsValue = getTableColumn().getCellObservableValue(getIndex()); + if (obsValue instanceof BooleanProperty) { + selProperty = (BooleanProperty)obsValue; + // ensures the radio button selected value will follow the item value + radioButton.selectedProperty().bindBidirectional(selProperty); + } + } + } + } + + class ReferenceLineItem implements Comparable { + private Version version; + private BooleanProperty installedProperty; + private BooleanProperty selectedProperty; + + public ReferenceLineItem() { + } + + public ReferenceLineItem(Version version, boolean installed, boolean selected) { + this.version = version; + this.installedProperty = new SimpleBooleanProperty(installed); + this.selectedProperty = new SimpleBooleanProperty(selected); + } + + public Version getVersion() { + return version; + } + + public void setVersion(Version version) { + this.version = version; + } + + public boolean isInstalled() { + return installedProperty.get(); + } + + public void setInstalled(boolean installed) { + this.installedProperty.set(installed); + } + + public boolean isSelected() { + return selectedProperty.get(); + } + + public void setSelected(boolean selected) { + this.selectedProperty.set(selected); + } + + @Override + public int compareTo(ReferenceLineItem o) { + if (version == null) { + return 1; + } + + if (o == null) { + return -1; + } + + return version.compareTo(o.getVersion()); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoPreferencePane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoPreferencePane.java new file mode 100644 index 0000000..373891d --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoPreferencePane.java @@ -0,0 +1,144 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; +import fr.cenra.rhomeo.api.ui.PreferencePane; +import fr.cenra.rhomeo.core.Session; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import javafx.scene.control.Button; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * Preference editor. This panel aggregate editors for all available + * {@link PreferenceGroup}. A discovery system allows to find the most + * appropriate editor for a given group. If we cannot find any, we use a default + * editor. + * + * @author Samuel Andrés (Geomatys) + */ +@Component("fr.cenra.rhomeo.fx.FXRhomeoPreferencePane") +@Scope("prototype") +public class FXRhomeoPreferencePane extends BorderPane implements InternationalResource { + + @Autowired + private Map preferenceGroups; + + @Autowired + private List availableEditors; + + @Autowired + private Session session; + + private final String css; + + private final List preferencePanes = new ArrayList<>(); + + public FXRhomeoPreferencePane() { + css = FXDefaultPreferenceGroupPane.class.getResource(FXRhomeoPreferencePane.class.getSimpleName() + ".css").toExternalForm(); + getStylesheets().add(css); + } + + @PostConstruct + private void postConstruct() { + + // Creating one tab for each preference group. + final TabPane tabPane = new TabPane(); + final List prefGroups = new ArrayList<>(preferenceGroups.values()); + Collections.sort(prefGroups); + + PreferencePane prefEditor; + for (final PreferenceGroup preferenceGroup : prefGroups) { + prefEditor = null; + for (final PreferencePane editor : availableEditors) { + if (editor.getPreferenceGroup().equals(preferenceGroup)) { + prefEditor = editor; + break; + } + } + + // If no specific editor has been found, a default one is used. + if (prefEditor == null) { + if (preferenceGroup.getKeys().isEmpty()) { + // A group without keys does not need to be displayed + continue; + } + + final FXDefaultPreferenceGroupPane preferencePane = new FXDefaultPreferenceGroupPane(); + preferencePane.setPreferenceGroup(preferenceGroup); + prefEditor = preferencePane; + } + + preferencePanes.add(prefEditor); + final Tab tab = new Tab(preferenceGroup.getLabel(), prefEditor.getEditor()); + tab.setTooltip(new Tooltip(preferenceGroup.getDescription())); + tabPane.getTabs().add(tab); + } + setCenter(tabPane); + + // Creating preference controls + final Button ok = new Button(getResourceString("validate")); + ok.setOnAction(e -> { + for (final PreferencePane preferencePane : preferencePanes) { + preferencePane.save(); + } + getScene().getWindow().hide(); + }); + + final Button cancel = new Button(getResourceString("cancel")); + cancel.setOnAction(e -> getScene().getWindow().hide()); + + final HBox hBox = new HBox(cancel, ok); + hBox.getStylesheets().add(css); + hBox.getStyleClass().add("hBox"); + setBottom(hBox); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoReferencePane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoReferencePane.java new file mode 100644 index 0000000..90c49f2 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXRhomeoReferencePane.java @@ -0,0 +1,96 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Protocol; +import java.util.Collections; +import java.util.List; +import javafx.fxml.FXML; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.stage.Stage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +/** + * @author Cédric Briançon (Geomatys) + */ +@Component("fr.cenra.rhomeo.fx.FXRhomeoReferencePane") +@Scope("prototype") +public class FXRhomeoReferencePane extends BorderPane { + + @FXML + private GridPane uiProtocolsBtnGrid; + + @FXML + private GridPane uiModelsBtnGrid; + + @Autowired + private List protocols; + + public FXRhomeoReferencePane() { + Rhomeo.loadFXML(this, FXRhomeoReferencePane.class); + } + + /** + * Initializes JavaFX components after spring components are done loading. + */ + @PostConstruct + private void postConstruct() { + Collections.sort(protocols); + + int modelIndex = 0; + for(int i=0; i editorSpis; + + private DataContext context; + private Site site; + private Protocol protocol; + private ProtocolEditorSpi editorSpi; + + private final Map stepBtns; + + private final String stopBtnOriginalText; + + public FXRhomeoWizard() { + Rhomeo.loadFXML(this, FXRhomeoWizard.class); + // Index buttons by step + final Map tmpMap = new HashMap<>(); + tmpMap.put(WorkflowStep.DATASET, uiStep1Btn); + tmpMap.put(WorkflowStep.PROCESS, uiStep2Btn); + tmpMap.put(WorkflowStep.RESULT, uiStep3Btn); + tmpMap.put(WorkflowStep.FINALIZATION, uiStep4Btn); + stepBtns = Collections.unmodifiableMap(tmpMap); + for (final Map.Entry entry : stepBtns.entrySet()) { + entry.getValue().setOnAction(evt -> session.requestWorkflowStep(entry.getKey())); + } + + uiHeader.managedProperty().bind(uiHeader.visibleProperty()); + + final ImageView im = new ImageView(Rhomeo.ICON_EXCHANGE_GRAY); + im.setRotate(90); + uiRefUsedBtn.setGraphic(im); + uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON_STOP_RED)); + // HACK #77 : User want a change on the button which stops process on a + // specific step, so we keep reference to its original text now. + stopBtnOriginalText = uiStopProcessBtn.getText(); + + // Prepare display of arrows linking step buttons. Each arrow state is bound + // to the step button following it. + final ImageView imgArrow1 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT); + imgArrow1.setDisable(true); + imgArrow1.visibleProperty().bind(uiStep2Btn.visibleProperty()); + imgArrow1.managedProperty().bind(imgArrow1.visibleProperty()); + final ImageView imgArrow2 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT); + imgArrow2.setDisable(true); + imgArrow2.visibleProperty().bind(uiStep3Btn.visibleProperty()); + imgArrow2.managedProperty().bind(imgArrow2.visibleProperty()); + final ImageView imgArrow3 = new ImageView(Rhomeo.ICON_LONG_ARROW_RIGHT); + imgArrow3.setDisable(true); + imgArrow3.visibleProperty().bind(uiStep4Btn.visibleProperty()); + imgArrow3.managedProperty().bind(imgArrow3.visibleProperty()); + // Limit height of arrow image, which is really too big (lot of empty space). + final Rectangle2D arrowDisplay = new Rectangle2D( + 0, Rhomeo.ICON_LONG_ARROW_RIGHT.getHeight() / 4, + Rhomeo.ICON_LONG_ARROW_RIGHT.getWidth(), Rhomeo.ICON_LONG_ARROW_RIGHT.getHeight() / 2); + imgArrow1.setViewport(arrowDisplay); + imgArrow2.setViewport(arrowDisplay); + imgArrow3.setViewport(arrowDisplay); + + uiProtocolStepsGrid.add(imgArrow1, 2, 0); + uiProtocolStepsGrid.add(imgArrow2, 4, 0); + uiProtocolStepsGrid.add(imgArrow3, 6, 0); + } + + @PostConstruct + private void init() { + session.workflowStepProperty().addListener(this::stepChanged); + unloadDataContext(); + } + + private void loadDataContext() { + this.context = session.getDataContext(); + this.site = context.getSite(); + this.protocol = context.getProtocol(); + initializeReferences(); + for (final ProtocolEditorSpi spi : editorSpis) { + if (spi.getProtocol() == protocol) { + editorSpi =spi; + break; + } + } + + // Deactivate step buttons for workflow parts marked as non available + // by the input SPI. + if (editorSpi != null && !editorSpi.getStepsToIgnore().isEmpty()) { + for (final WorkflowStep step : editorSpi.getStepsToIgnore()) { + final Button btn = stepBtns.get(step); + if (btn != null) { + btn.setVisible(false); + btn.setManaged(false); + } + } + } + + uiSelectedSiteLbl.setText(site.getName()); + uiSelectedProtocolLbl.setText(protocol.getName()); + uiProtocolChosenBox.getChildren().add(uiSelectedProtocolLbl); + uiRefUsedBtn.setDisable(protocol.getReferenceTypes().isEmpty()); + + final List indicators = session.getIndicators(); + indicators.stream() + .filter(indicator -> indicator.getProtocol() == protocol) + .sorted() + .forEach(indicator -> { + final Label lbl = new Label(indicator.getName()); + lbl.getStyleClass().add(Rhomeo.CSS_INDICATOR_LABEL); + uiProtocolChosenBox.getChildren().add(lbl); + }); + + final Button pdfBtn = Rhomeo.createProtocolDocumentButton(protocol); + pdfBtn.setText(null); + pdfBtn.setPrefHeight(28); + pdfBtn.setMaxHeight(USE_PREF_SIZE); + pdfBtn.setMinHeight(USE_PREF_SIZE); + uiProtocolChosenBox.getChildren().add(pdfBtn); + uiHeader.setVisible(true); + + // Generate protocol background + final String bgPath = "/fr/cenra/rhomeo/images/" + protocol.getName().toLowerCase() + ".png"; + if (FXRhomeoWizard.class.getResource(bgPath) != null) { + try (final InputStream stream = FXRhomeoWizard.class.getResourceAsStream(bgPath)) { + final Background backgroundProtocol = new Background(new BackgroundImage( + new Image(stream), + BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, + new BackgroundPosition(Side.LEFT, 0, true, Side.BOTTOM, 0, true), BackgroundSize.DEFAULT)); + setBackground(backgroundProtocol); + } catch (IOException e) { + Rhomeo.LOGGER.log(Level.FINE, "Cannot load a background image !", e); + } + } + + // #59 Reset results if user change reference version. + session.getDataContext().getReferences().addListener((Observable obs) -> { + if (session.getWorkflowStep() == WorkflowStep.RESULT || session.getWorkflowStep() == WorkflowStep.FINALIZATION) + session.requestWorkflowStep(WorkflowStep.PROCESS); + + session.setResults(null); + stepBtns.get(WorkflowStep.RESULT).setDisable(true); + stepBtns.get(WorkflowStep.FINALIZATION).setDisable(true); + }); + } + + private void unloadDataContext() { + final Site tmpSite = site; + context = null; + site = null; + protocol = null; + uiProtocolChosenBox.getChildren().clear(); + uiHeader.setVisible(false); + editorSpi = null; + + for (final Button stepBtn : stepBtns.values()) { + stepBtn.setDisable(true); + stepBtn.setVisible(true); + stepBtn.getStyleClass().removeAll(Rhomeo.CSS_CURRENT_STEP); + } + + final FXDashboardPane dashboard = session.getBean(FXDashboardPane.class); + dashboard.siteProperty().set(tmpSite); + + // TODO : set site + uiBody.setCenter(dashboard); + setBackground(Background.EMPTY); + } + + /** + * On {@link FXRhomeoWizard#uiRefUsedBtn} action. + */ + @FXML + private void showUsedRef() { + final DataContext dc = session.getDataContext(); + if (dc == null) { + return; + } + final Protocol protocol = dc.getProtocol(); + if (protocol == null) { + return; + } + + final Stage stage = Rhomeo.newStage(); + stage.setTitle("Choix des référentiels"); + final VBox vbox = new VBox(10); + vbox.getStyleClass().add("white-box"); + + protocol.getReferenceTypes().forEach(description -> { + try { + final FXProtocolReferencesPane protocolReferences = new FXProtocolReferencesPane(description); + vbox.getChildren().add(protocolReferences); + VBox.setVgrow(protocolReferences, Priority.SOMETIMES); + } catch (IntrospectionException ex) { + throw new RhomeoRuntimeException(ex); + } + }); + + final Button validBtn = new Button("Valider"); + validBtn.getStyleClass().add("white-button"); + validBtn.setOnAction(event -> { + // #59 If user already computed indices, changing reference versions + // would delete his results, so we warn him. + if (!stepBtns.get(WorkflowStep.RESULT).isDisabled()) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Si vous changez de version de référentiel maintenant, vos résultats seront supprimés. Continuer ?", ButtonType.NO, ButtonType.YES); + alert.setResizable(true); + if (ButtonType.NO.equals(alert.showAndWait().orElse(ButtonType.NO))) + return; + } + vbox.getChildren().stream() + .filter(child -> child instanceof FXProtocolReferencesPane) + .map(child -> (FXProtocolReferencesPane)child) + .forEach(protoRefs -> + protoRefs.getItems().stream() + .filter(o -> o instanceof FXProtocolReferencesPane.ReferenceLineItem && ((FXProtocolReferencesPane.ReferenceLineItem) o).isSelected()) + .forEach(o -> dc.getReferences().put(protoRefs.getReferenceClass(), ((FXProtocolReferencesPane.ReferenceLineItem) o).getVersion())) + ); + try { + contextManager.writeDataContext(dc); + } catch (IOException ex) { + throw new RhomeoRuntimeException(ex); + } finally { + stage.close(); + } + }); + + final Button cancelBtn = new Button("Annuler"); + cancelBtn.setCancelButton(true); + cancelBtn.setOnAction(evt -> stage.close()); + cancelBtn.getStyleClass().add("white-button"); + + final HBox validHbox = new HBox(5, cancelBtn, validBtn); + validHbox.setAlignment(Pos.CENTER_RIGHT); + vbox.getChildren().add(validHbox); + + final Scene scene = new Scene(vbox); + scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH); + scene.getStylesheets().add("/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.css"); + stage.setScene(scene); + stage.setMinHeight(protocol.getReferenceTypes().size() * 130 + 80); + stage.setHeight(stage.getMinHeight()); + stage.setResizable(true); + stage.initModality(Modality.APPLICATION_MODAL); + stage.initStyle(StageStyle.UNDECORATED); + stage.sizeToScene(); + stage.showAndWait(); + } + + @FXML + private void stopProcess() { + // HACK #78 : Before stopping session, we check dataset state, for the + // user to be able to save its data before stopping. + final Node center = uiBody.getCenter(); + if (center instanceof FXDatasetPane) { + Task saveTask = ((FXDatasetPane) center).checkSaveChange().orElse(null); + if (saveTask != null) + saveTask.setOnSucceeded(evt -> { + if (Boolean.TRUE.equals(evt.getSource().getValue())) + Platform.runLater(() -> session.requestWorkflowStep(null)); + }); + else + session.requestWorkflowStep(null); + } else + session.requestWorkflowStep(null); + } + + private void stepChanged(final ObservableValue obs, final WorkflowStep oldStep, final WorkflowStep newStep) { + // HACK #77 : User want a change on stop button look and feel at final step. + // We reset its state before doing anything else. + uiStopProcessBtn.setText(stopBtnOriginalText); + uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON_STOP_RED)); + + if (newStep == null) { + // Clear and hide edition display. Go back to the dashboard. + unloadDataContext(); + + } else { + + if (oldStep == null) { + // New edition started + loadDataContext(); + } else { + Button btn = stepBtns.get(oldStep); + if (btn != null && btn.getStyleClass().contains(Rhomeo.CSS_CURRENT_STEP)) { + btn.getStyleClass().remove(Rhomeo.CSS_CURRENT_STEP); + } + } + + // Init current step UI + Button btn = stepBtns.get(newStep); + btn.setDisable(false); + btn.getStyleClass().add(Rhomeo.CSS_CURRENT_STEP); + + Node body = createNode(newStep); + if (body == null) { + switch (newStep) { + case DATASET: + body = session.getBean(FXDatasetPane.class); + break; + + case PROCESS: + body = session.getBean(FXDefaultProcessingPane.class); + break; + + case RESULT: + body = session.getBean(FXDefaultResultPane.class); + break; + + case FINALIZATION: + body = session.getBean(FXFinalizationPane.class); + // HACK #77 : Change look and feel of stop button. + uiStopProcessBtn.setText("Retour au tableau de bord"); + uiStopProcessBtn.setGraphic(new ImageView(Rhomeo.ICON)); + break; + } + } + + uiBody.setCenter(body); + } + } + + private Node createNode(final WorkflowStep step) { + final Node node; + if (editorSpi != null) { + node = editorSpi.getEditor(step); + } else { + node = null; + } + + return node; + } + + /** + * Initialize references lists for further use. + * Note : For each reference of the current protocol, we update list of + * installed and available versions. If none is installed, we propose to the + * user to install the most up to date. + * + * @throws IntrospectionException + */ + private void initializeReferences() { + if (protocol == null || protocol.getReferenceTypes().isEmpty()) + return; + + for (final ReferenceDescription description : protocol.getReferenceTypes()) { + try { + final ReferenceManager referenceManager = ReferenceManager.getOrCreate(description); + final Task task = referenceManager.refresh(); + task.setOnSucceeded(event -> { + // If we haven't any version installed for this reference, then we propose to install the newest. + if (referenceManager.getInstalledVersions().isEmpty() && !referenceManager.getDistantVersions().isEmpty()) { + Platform.runLater(() -> { + final StringBuilder builder = new StringBuilder("Aucune version du référentiel suivant n'est installée :") + .append(System.lineSeparator()) + .append(description.getTitle()) + .append(System.lineSeparator()) + .append(System.lineSeparator()) + .append("Voulez-vous télécharger la plus récente ?"); + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, builder.toString(), ButtonType.NO, ButtonType.YES); + alert.setHeaderText("Référentiel non installé"); + alert.setResizable(true); + // If agreed, we download newest version. When it's done, we set it as used version. + if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) { + final Version max = Collections.max((Set) referenceManager.getDistantVersions()); + TaskManager.INSTANCE.submit(referenceManager.install(max)).setOnSucceeded(evt -> { + final DataContext dc = session.getDataContext(); + if (dc != null) + Platform.runLater(() -> dc.getReferences().put(description.getReferenceType(), max)); + }); + } + }); + } else { + final Version selectedVersion = context.getReferences().get(description.getReferenceType()); + // If at least one local version exists for this reference and nothing has been selected, then select the biggest one. + // This way, for each reference a version should be selected. + if (selectedVersion == null && !referenceManager.getInstalledVersions().isEmpty()) { + final Version max = Collections.max((Set) referenceManager.getInstalledVersions()); + context.getReferences().put(description.getReferenceType(), max); + } + } + }); + + task.setOnFailed(event -> { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot update reference versions !", event.getSource().getException()); + final Version selectedVersion = context.getReferences().get(description.getReferenceType()); + // If at least one local version exists for this reference and nothing has been selected, then select the biggest one. + // This way, for each reference a version should be selected. + if (selectedVersion == null && !referenceManager.getInstalledVersions().isEmpty()) { + final Version max = Collections.max((Set) referenceManager.getInstalledVersions()); + context.getReferences().put(description.getReferenceType(), max); + } + }); + + TaskManager.INSTANCE.submit(task); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.WARNING, "Cannot initialize reference !", e); + } + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/FXSplashScreen.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXSplashScreen.java new file mode 100644 index 0000000..cdeddd8 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/FXSplashScreen.java @@ -0,0 +1,60 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressBar; +import javafx.scene.layout.GridPane; + +/** + * Created by cedr on 06/04/16. + */ +public class FXSplashScreen extends GridPane { + @FXML public GridPane uiLoadingPane; + @FXML public Label uiProgressLabel; + @FXML public ProgressBar uiProgressBar; + @FXML public Button uiCancel; + + @FXML + public void closeApp() { + System.exit(0); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/GraphDashboardItem.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/GraphDashboardItem.java new file mode 100644 index 0000000..c8536e5 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/GraphDashboardItem.java @@ -0,0 +1,139 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import java.util.HashMap; +import javafx.beans.binding.Bindings; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.chart.NumberAxis; +import javafx.scene.chart.ScatterChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import org.springframework.stereotype.Component; +import fr.cenra.rhomeo.api.ui.DashboardMenuItem; +import java.util.Map; +import java.util.Optional; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class GraphDashboardItem implements DashboardMenuItem { + + private static final String GRAPH_TITLE = "Évolution des valeurs indicatrices"; + + @Override + public Optional createItem(final TableView target) { + final MenuItem graphItem = new MenuItem("Graphique d'évolution"); + graphItem.setOnAction(evt -> displayGraph(target)); + graphItem.visibleProperty().bind(Bindings.size(target.getSelectionModel().getSelectedIndices()).greaterThan(1)); + return Optional.of(graphItem); + } + + + /** + * Display the evolution graph in a modal window. + */ + private static void displayGraph(final TableView source) { + if (source.getScene() == null || source.getScene().getWindow() == null) + return; + final Stage chartStage = new Stage(StageStyle.UTILITY); + chartStage.initModality(Modality.WINDOW_MODAL); + chartStage.initOwner(source.getScene().getWindow()); + chartStage.setTitle(GRAPH_TITLE); + chartStage.setScene(new Scene(createChart(source))); + + chartStage.show(); + } + + /** + * Create a point chart to display selected indicator values from dashboard. + * @return A chart to display selected values. + */ + private static XYChart createChart(final TableView source) { + final Map> series = new HashMap<>(); + final ObservableList selectedCells = source.getSelectionModel().getSelectedCells(); + Double cellData; + int year; + IndicatorSiteColumn col; + int minYear = Integer.MAX_VALUE; + int maxYear = Integer.MIN_VALUE; + double minValue = Double.MAX_VALUE; + double maxValue = Double.MIN_VALUE; + for (final TablePosition pos : selectedCells) { + if (pos.getTableColumn() instanceof IndicatorSiteColumn) { + col = (IndicatorSiteColumn) pos.getTableColumn(); + cellData = col.getCellData(pos.getRow()); + if (cellData != null && Double.isFinite(cellData)) { + year = source.getItems().get(pos.getRow()).getYear(); + minYear = StrictMath.min(year, minYear); + maxYear = StrictMath.max(year, maxYear); + minValue = StrictMath.min(cellData, minValue); + maxValue = StrictMath.max(cellData, maxValue); + + series.computeIfAbsent(col.getIndicator().getTitle(), (title) -> { + final XYChart.Series s = new XYChart.Series<>(); + s.setName(title); + return s; + }).getData().add(new XYChart.Data<>(year, cellData)); + } + } + } + + final XYChart chart = new ScatterChart<>( + new NumberAxis("Année", minYear - 1, maxYear + 1, 5), + new NumberAxis("Valeur", minValue - 0.5, maxValue + 0.5, 0.5) + ); + + chart.setTitle(GRAPH_TITLE); + chart.getData().addAll(series.values()); + chart.getData().sort((o1, o2) -> o1.getName().compareTo(o2.getName())); + chart.setPadding(new Insets(10)); + return chart; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/IndicatorSiteColumn.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/IndicatorSiteColumn.java new file mode 100644 index 0000000..7ec7bde --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/IndicatorSiteColumn.java @@ -0,0 +1,299 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.data.ReferenceDescription; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.DashboardResultItem; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.result.ResultStorage; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringJoiner; +import java.util.function.Function; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.geometry.HPos; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import org.geotoolkit.gui.javafx.util.TaskManager; + +/** + * Table column for indicators. + * Column present in the {@link FXDashboardResultsTable}. + * + * @author Cédric Briançon (Geomatys) + */ +public class IndicatorSiteColumn extends TableColumn implements Comparable { + + private final Indicator indicator; + + private final ChangeListener itemPropertyChanged; + + private final InvalidationListener referenceCheckTrigger; + + private final ObjectProperty checkTaskProperty; + + /** + * HACK : Cannot find any property to put graphic right to text in a column + * header, so we put a label in the header to contain both. + */ + private final Label header; + + public IndicatorSiteColumn(final Indicator indicator) { + this.indicator = indicator; + header = new Label(indicator.getName()); + header.setContentDisplay(ContentDisplay.RIGHT); + setGraphic(header); + + itemPropertyChanged = this::itemPropertyChanged; + referenceCheckTrigger = obs -> checkReferences(); + + tableViewProperty().addListener(this::tableChanged); + tableViewProperty().addListener(referenceCheckTrigger); + + checkTaskProperty = new SimpleObjectProperty<>(); + checkTaskProperty.addListener((obs, oldVal, newVal) -> { + if (oldVal != null && oldVal.isRunning()) + oldVal.cancel(); + if (newVal != null && !newVal.isRunning()) + TaskManager.INSTANCE.submit(newVal); + + }); + } + + public Indicator getIndicator() { + return indicator; + } + + @Override + public int compareTo(IndicatorSiteColumn o) { + if (o == null) { + return -1; + } + + if (indicator == null) { + return 1; + } + return indicator.compareTo(o.getIndicator()); + } + + /** + * Update listeners when parent table changes. + * @param obs + * @param oldVal + * @param newVal + */ + private void tableChanged(final ObservableValue obs, TableView oldVal, TableView newVal) { + if (oldVal != null) { + oldVal.itemsProperty().removeListener(itemPropertyChanged); + oldVal.itemsProperty().removeListener(referenceCheckTrigger); + } + + if (newVal != null) { + newVal.itemsProperty().addListener(itemPropertyChanged); + newVal.itemsProperty().addListener(referenceCheckTrigger); + } + } + + /** + * Update listeners when parent table items change. + * @param obs + * @param oldVal + * @param newVal + */ + private void itemPropertyChanged(final ObservableValue obs, ObservableList oldVal, ObservableList newVal) { + if (oldVal != null) + oldVal.removeListener(referenceCheckTrigger); + + if (newVal != null) + newVal.addListener(referenceCheckTrigger); + } + + /** + * Browse column indices to find all reference versions used for their computing. + * If we find multiple versions for a same reference, we will display a little + * warning to the user. + */ + private void checkReferences() { + final ObservableList items; + if (getTableView() == null || (items = getTableView().getItems()) == null || items.isEmpty()) + return; + + final Site site; + if (getTableView() instanceof FXDashboardResultsTable) + site = ((FXDashboardResultsTable)getTableView()).siteProperty().get(); + else + site = null; + + if (site == null) + return; + + final ReferenceChecker checker = new ReferenceChecker(site, items); + checker.setOnSucceeded(evt -> Platform.runLater(() -> { + final Map>> md = checker.getValue(); + if (multipleVersionsInUse(md)) { + final ImageView imgView = new ImageView(Rhomeo.ICON_WARNING_ORANGE); + final Tooltip tt = new Tooltip("Les versions de référentiels ne sont pas homogènes."); + tt.setContentDisplay(ContentDisplay.BOTTOM); + tt.getStyleClass().add("info-popup"); + tt.setGraphic(createConflictView(md)); + final Label label = new Label(null, imgView); + label.setTooltip(tt); + header.setGraphic(label); + } + })); + + checker.setOnFailed(evt -> Platform.runLater(() -> { + // TODO. For now, do as if nothing happened + })); + + checkTaskProperty.set(checker); + } + + private VBox createConflictView(final Map>> md) { + final VBox vbox = new VBox(10); + vbox.setFillWidth(true); + vbox.setStyle("font-size: 12pt;"); + for (Map.Entry>> entry : md.entrySet()) { + if (entry.getValue().size() < 2) + continue; + final BorderPane bp = new BorderPane(); + final Label title = new Label(entry.getKey().getTitle()); + title.getStyleClass().add("section-title"); + bp.setTop(title); + final GridPane gp = new GridPane(); + bp.setCenter(gp); + gp.getColumnConstraints().addAll( + new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Priority.NEVER, HPos.LEFT, true), + new ColumnConstraints(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE, Priority.ALWAYS, HPos.CENTER, true) + ); + final Map> vMap = entry.getValue(); + int rowIndex = 0; + for (Map.Entry> vEntry : vMap.entrySet()) { + gp.add(new Label(vEntry.getKey().toString()), 0, rowIndex); + final StringJoiner sj = new StringJoiner(", "); + vEntry.getValue().forEach(val -> sj.add(String.valueOf(val))); + gp.add(new Label(sj.toString()), 1, rowIndex++); + } + vbox.getChildren().add(bp); + } + + return vbox; + } + + private boolean multipleVersionsInUse(final Map>> metadata) { + for (final Map.Entry>> entry : metadata.entrySet()) { + if (entry.getValue().size() > 1) + return true; + } + return false; + } + + /** + * Read metadata associated to given items and column indicator, to find all + * versions used for references. + */ + private class ReferenceChecker extends Task>>> { + + final List items; + final Site site; + + ReferenceChecker(final Site site, final List items) { + this.site = site; + this.items = items; + } + + @Override + protected Map>> call() throws Exception { + final Session session = Session.getInstance(); + final ResultStorage storage = session.getBean(ResultStorage.class); + + final Map>> refVersions = new HashMap<>(); + for (final IndicatorValuesByYearItem item : items) { + if (isCancelled()) + break; + final IndicatorValuesByYearItem.PublishedValue tmpVal = item.getValueForIndicator(indicator.getName()); + if (tmpVal != null) { + final DashboardResultItem tmpItem = new DashboardResultItem(item.getYear(), site.getName(), indicator.getName(), tmpVal.getValue(), tmpVal.isPublished()); + storage.readMetadata(tmpItem) + .map(sc -> sc.toDataContext(session)) + .map(ctx -> session.transform(ctx.getReferences())) + .ifPresent(map -> merge(map, refVersions, item.getYear())); + } + } + + return refVersions; + } + + void merge(final Map source, final Map>> dest, int year) { + for (final Map.Entry entry : source.entrySet()) { + dest.computeIfAbsent(entry.getKey(), MAP_CREATOR) + .computeIfAbsent(entry.getValue(), SET_CREATOR) + .add(year); + } + } + } + + private static final Function> SET_CREATOR = input -> new HashSet<>(); + private static final Function>> MAP_CREATOR = input -> new HashMap<>(); +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/MultiIndicatorsDashboardItem.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/MultiIndicatorsDashboardItem.java new file mode 100644 index 0000000..d616ab8 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/MultiIndicatorsDashboardItem.java @@ -0,0 +1,208 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.IndicatorValuesByYearItem; +import fr.cenra.rhomeo.api.ui.DashboardMenuItem; +import fr.cenra.rhomeo.core.RhomeoRuntimeException; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p04.I04; +import fr.cenra.rhomeo.protocol.p04.I07; +import fr.cenra.rhomeo.protocol.p08.I12; +import fr.cenra.rhomeo.protocol.p09.I13; +import java.beans.IntrospectionException; +import java.io.IOException; +import java.util.Optional; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +@Component +public class MultiIndicatorsDashboardItem implements DashboardMenuItem { + + @Autowired + private Session session; + + @Override + public Optional createItem(final TableView table) { + + if (!(table instanceof FXDashboardResultsTable)) + return Optional.empty(); + + final MenuItem graphItem = new MenuItem("Voir tous les indicateurs"); + graphItem.setOnAction(evt -> { + try { + displayStage((FXDashboardResultsTable) table); + } catch (IOException | IntrospectionException | InstantiationException | IllegalAccessException ex) { + throw new RhomeoRuntimeException("Unable to display results.", ex); + } + }); + graphItem.visibleProperty().bind(Bindings.size(table.getSelectionModel().getSelectedIndices()).isEqualTo(1) + .and(isValidIndice(table.getSelectionModel().getSelectedCells()))); + + return Optional.of(graphItem); + } + + /** + * Customized binding to test indicator column. + * + * @param op + * @return + */ + public static BooleanBinding isValidIndice(final ObservableList op) { + if (op == null) { + throw new NullPointerException("List cannot be null."); + } + + return new BooleanBinding() { + { + super.bind(op); + } + + @Override + public void dispose() { + super.unbind(op); + } + + @Override + protected boolean computeValue() { + if(op.size()!=1) return false; + final TablePosition cell = op.get(0); + final TableColumn tableColumn = cell.getTableColumn(); + if(tableColumn instanceof IndicatorSiteColumn && ((IndicatorSiteColumn) tableColumn).getIndicator()!=null){ + final String ind = ((IndicatorSiteColumn) tableColumn).getIndicator().getName(); + if (I04.NAME.equals(ind) || I07.NAME.equals(ind) || I12.NAME.equals(ind) || I13.NAME.equals(ind)) { + return tableColumn.getCellData(cell.getRow()) != null; + } + } + + return false; + } + + @Override + //@ReturnsUnmodifiableCollection + public ObservableList getDependencies() { + return FXCollections.singletonObservableList(op); + } + }; + } + + /** + * Display the evolution graph in a modal window. + */ + private void displayStage(final FXDashboardResultsTable table) throws IOException, IntrospectionException, InstantiationException, IllegalAccessException { + + if (table.getScene() == null + || table.getScene().getWindow() == null + || table.getSelectionModel().getSelectedCells()==null + || table.getSelectionModel().getSelectedCells().size()!=1) + return; + + final Site site = table.siteProperty().get(); + final TableColumn tableColumn = table.getSelectionModel().getSelectedCells().get(0).getTableColumn(); + + if(site!=null && site.getName()!=null && tableColumn!=null && tableColumn instanceof IndicatorSiteColumn){ + + final Indicator indicator = ((IndicatorSiteColumn) tableColumn).getIndicator(); + final IndicatorValuesByYearItem selectedItem = table.getSelectionModel().getSelectedItem(); + + final Stage chartStage = new Stage(StageStyle.TRANSPARENT); + chartStage.initModality(Modality.WINDOW_MODAL); + chartStage.initOwner(table.getScene().getWindow()); + + final FXFullSpecificResultPane fullResultPane = session.createPrototype(FXFullSpecificResultPane.class, + selectedItem, + indicator, + site.getName()); + + final Label label = new Label(indicator.getName() + " ("+selectedItem.getYear()+")"); + label.setAlignment(Pos.CENTER); + label.setMaxWidth(Double.MAX_VALUE); + label.getStyleClass().add("text-bold"); + + final BorderPane root = new BorderPane(); + root.setPrefSize(400, Region.USE_COMPUTED_SIZE); + root.getStyleClass().add("white-box"); + root.setTop(label); + root.setCenter(fullResultPane); + + final Button closeBtn = new Button("Fermer"); + closeBtn.setCancelButton(true); + closeBtn.setOnAction(evt -> chartStage.close()); + closeBtn.getStyleClass().add("white-button"); + final HBox hBox = new HBox(closeBtn); + hBox.setAlignment(Pos.CENTER_RIGHT); + root.setBottom(hBox); + + final Scene scene = new Scene(root); + scene.getStylesheets().add(Rhomeo.CSS_THEME_PATH); + scene.getStylesheets().add("/fr/cenra/rhomeo/fx/FXProtocolReferencesPane.css"); + + chartStage.setMaxHeight(400); + chartStage.setMaxWidth(700); + chartStage.setScene(scene); + chartStage.sizeToScene(); + chartStage.show(); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AbstractCustomStatementEditor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AbstractCustomStatementEditor.java new file mode 100644 index 0000000..530bace --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AbstractCustomStatementEditor.java @@ -0,0 +1,396 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.DataContext; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.data.TrackingPoint; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p02.P02; +import fr.cenra.rhomeo.protocol.p05.P05; +import fr.cenra.rhomeo.protocol.p06.P06; +import fr.cenra.rhomeo.protocol.p07.P07; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.beans.InvalidationListener; +import javafx.beans.Observable; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.DatePicker; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javax.annotation.PostConstruct; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * + * This pane is a factorisation of common attributes and functionalities of + * {@link P02}, {@link P05}, {@link P06} and {@link P07} {@link StatementEditor}s. + * + * It displays a summary of a "current" {@link TrackingPoint} one the one hand, + * and a list of {@link T} on the other hand. + * + * The pane inherits Borderpane and must contain at least : + * + * 1- {@link TrackingPoint} attribute summary : + * + * - a {@link TextField} ({@link AbstractCustomStatementEditor#uiName}) mapping + * the name of the current {@link TrackingPoint}. + * - a {@link DatePicker} field ({@link AbstractCustomStatementEditor#uiDate}), + * mapping the date of the current selected statement. + * - a {@link TextArea} field ({@link AbstractCustomStatementEditor#uiRemarks}), + * mapping the remarks of the current selected statement. + * + * 2- A {@link StatementTable} of the {@link T}s of the selected {@link TrackingPoint}. + * + * /!\ Inherited classes should be {@link Component}s, as a post-construct phase + * and an autowired field are defined here. + * + * @author Samuel Andrés (Geomatys) + * @param + */ +public abstract class AbstractCustomStatementEditor extends BorderPane implements StatementEditor { + + @Autowired + protected Session session; + + // Tracking point attributes + @FXML protected TextField uiName; + @FXML protected DatePicker uiDate; + @FXML protected TextArea uiRemarks; + + // Statement control + @FXML protected BorderPane uiObsContainer; + @FXML protected Button uiDelete; + protected StatementTable uiStatementTable; + + protected final Class dataType; + + /** + * An internal field we use to know if the typed name has changed when user + * exit the name field. + */ + private String oldName = null; + /** + * An internal field we use to know if the typed date has changed when user + * exit the date field. + */ + private LocalDate oldDate = null; + /** + * An internal field we use to know if the typed remarks have changed when + * user exit the remarks field. + */ + private String oldRemarks = null; + + private final StringProperty firstEditionColumn = new SimpleStringProperty(); + + protected AbstractCustomStatementEditor(final Class dataType) { + super(); + ArgumentChecks.ensureNonNull("Data type", dataType); + this.dataType = dataType; + } + + /** + * Default {@link PostConstruct} AbstractCustomStatementEditor. + */ + @PostConstruct + protected void init() { + try { + final Class dataType = session.getDataContext().getProtocol().getDataType(); + if (getDataType().isAssignableFrom(dataType)) { + // Default behaviour : ignore some statement attributes. + uiStatementTable = new StatementTable(dataType, getFieldsToIgnore()); + uiStatementTable.setMinHeight(30); + uiStatementTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + final InvalidationListener listener = (Observable obs) -> { + for (final TableColumn col : uiStatementTable.getColumns()) { + if (col.isEditable() && (col instanceof PropertyColumn)) { + firstEditionColumn.set(((PropertyColumn)col).descriptor.getName()); + break; + } + } + }; + uiStatementTable.getColumns().addListener(listener); + listener.invalidated(uiStatementTable.getColumns()); + + uiObsContainer.setCenter((Node) uiStatementTable); + + uiName.focusedProperty().addListener(this::nameFocusChanged); + uiDate.focusedProperty().addListener(this::dateFocusChanged); + uiRemarks.focusedProperty().addListener(this::remarksFocusChanged); + + uiStatementTable.itemsProperty().addListener(this::itemsChanged); + itemsChanged(uiStatementTable.itemsProperty(), null, null); + } + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot initialize statement table !", e); + final Label label = new Label("Impossible d'afficher la table des relevés."); + label.getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + uiObsContainer.setCenter(label); + } + } + + @Override + public ObservableList getItems() { + return uiStatementTable.getItems(); + } + + @Override + public void setItems(ObservableList items) { + uiStatementTable.setItems(items); + } + + @Override + public ObjectProperty> itemsProperty() { + return uiStatementTable.itemsProperty(); + } + + /** + * + * @return A list of properties from {@link #dataType} which must not be + * included into edition table. + */ + protected String[] getFieldsToIgnore() { + return new String[]{"trackingPoint", "date", "remarks"}; + } + + /** + * Default behaviour dataType return behaviour. + * + * Returns the data type of the {@link Protocol} of the current {@link Session} + * {@link DataContext}. + * + * @return + */ + @Override + public Class getDataType() { + return dataType; + } + + /** + * Default behaviour to create a new {@link T}. + * + * Creates a new statement initializing common attributes from {@link TrackingPoint}. + * + * @return + * @throws ReflectiveOperationException + */ + protected T initStatement() throws ReflectiveOperationException { + // TODO : try to acquire a prototype ? + final T data = (T) session.getDataContext().getProtocol().getDataType().newInstance(); + + if (uiName.getText() != null && !uiName.getText().trim().isEmpty()) { + data.setTrackingPoint(uiName.getText()); + } + + if (uiDate.getValue() != null) { + data.setDate(uiDate.getValue()); + } + + data.setRemarks(uiRemarks.getText()); + + return data; + } + + /** + * Default behaviour on adding {@link T} event. + * + * Creates a new statement and focus on it. + * + * @param evt + * @throws ReflectiveOperationException + */ + @FXML + protected void addStatement(final ActionEvent evt) throws ReflectiveOperationException { + if (uiStatementTable == null || uiStatementTable.getItems() == null) + return; + + final T data = initStatement(); + + uiStatementTable.getItems().add(data); + final TablePosition pos = new TablePosition(uiStatementTable, uiStatementTable.getItems().size() - 1, null); + + // HACK : It appears that table cells are refreshed on next pulse, so we wait for it before asking for focus. + TaskManager.INSTANCE.submit(() -> { + synchronized (pos) { + try { + pos.wait(100); + } catch (InterruptedException ex) { + Rhomeo.LOGGER.log(Level.WARNING, null, ex); + } + } + + Platform.runLater(() -> Rhomeo.editNextCell(pos)); + }); + } + + /** + * Default behaviour of deleting {@link T} event. + * + * @param evt + */ + @FXML + protected void deleteStatements(final ActionEvent evt) { + if (uiStatementTable == null || uiStatementTable.getItems() == null) + return; + + // Defensive copy + final List indices = new ArrayList<>(uiStatementTable.getSelectionModel().getSelectedIndices()); + for (final int index : indices) { + uiStatementTable.getItems().remove(index); + } + } + + @Override + public void focusOn(T item, String propertyName) { + uiStatementTable.focusOn(item, propertyName); + } + + /** + * Default behaviour on changing current {@link TrackingPoint}. + * + * @param obs + * @param oldItems + * @param newItems + */ + protected void itemsChanged(final ObservableValue obs, final List oldItems, final List newItems) { + uiDelete.disableProperty().unbind(); + if (newItems != null && !newItems.isEmpty()) { + final T first = newItems.get(0); + uiName.setText(first.getTrackingPoint()); + uiDate.setValue(first.getDate()); + uiRemarks.setText(first.getRemarks()); + + if (uiStatementTable != null) { + uiStatementTable.getSelectionModel().selectFirst(); + } + + } else { + uiName.setText(null); + uiDate.setValue(null); + uiRemarks.clear(); + } + + if (uiStatementTable != null) { + uiDelete.disableProperty().bind(uiStatementTable.getSelectionModel().selectedItemProperty().isNull()); + } + + uiName.requestFocus(); + } + + /** + * Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiName}. + * + * @param obs + * @param oldValue + * @param newValue + */ + protected void nameFocusChanged(final ObservableValue obs, final Boolean oldValue, final Boolean newValue) { + final String name = uiName.getText(); + if (Boolean.TRUE.equals(newValue)) { + oldName = name; + } else if (Boolean.FALSE.equals(newValue) && !Rhomeo.equivalent(oldName, name)) { + // Update statements. Use defensive copy because each change of name can update list of available items. + final Statement[] toUpdate = uiStatementTable.getItems().toArray(new Statement[uiStatementTable.getItems().size()]); + for (int i = 0 ; i < toUpdate.length ; i++) { + toUpdate[i].setTrackingPoint(name); + } + } + } + + /** + * Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiDate}. + * + * @param obs + * @param oldValue + * @param newValue + */ + protected void dateFocusChanged(final ObservableValue obs, final Boolean oldValue, final Boolean newValue) { + final LocalDate date = uiDate.getValue(); + if (Boolean.TRUE.equals(newValue)) { + oldDate = date; + } else if (!(oldDate == date || oldDate != null && date != null && oldDate.isEqual(date))) { + // Update statements. Use defensive copy because each change of name can update list of available items. + final Statement[] toUpdate = uiStatementTable.getItems().toArray(new Statement[uiStatementTable.getItems().size()]); + for (int i = 0 ; i < toUpdate.length ; i++) { + toUpdate[i].setDate(date); + } + } + } + + /** + * Default behaviour when the focus has changed on {@link AbstractCustomStatementEditor#uiName}. + * + * @param obs + * @param oldValue + * @param newValue + */ + protected void remarksFocusChanged(final ObservableValue obs, final Boolean oldValue, final Boolean newValue) { + final String remarks = uiRemarks.getText(); + if (Boolean.TRUE.equals(newValue)) { + oldRemarks = remarks; + } else if (Boolean.FALSE.equals(newValue) && !Rhomeo.equivalent(oldRemarks, remarks)) { + for (final Statement st : uiStatementTable.getItems()) { + st.setRemarks(remarks); + } + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AutoCommitableTableCell.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AutoCommitableTableCell.java new file mode 100644 index 0000000..54ea94d --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/AutoCommitableTableCell.java @@ -0,0 +1,53 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import javafx.scene.control.TableCell; + +/** + * A table cell which knows what value to commit when editing. + * @author Alexis Manin (Geomatys) + */ +public abstract class AutoCommitableTableCell extends TableCell { + /** + * Trigger a commit, as {@link #commitEdit(java.lang.Object) }. But its the + * cell which determines the value to commit. + */ + abstract void commitEdit(); +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ByNameAndDateFilter.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ByNameAndDateFilter.java new file mode 100644 index 0000000..fc29352 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ByNameAndDateFilter.java @@ -0,0 +1,125 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.time.LocalDate; +import java.util.Objects; +import java.util.function.Predicate; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class ByNameAndDateFilter implements Predicate, Comparable, Externalizable { + + protected static final long serialVersionUID = 1L; + + protected String name; + protected LocalDate date; + + public ByNameAndDateFilter() {} + + public ByNameAndDateFilter(String name, LocalDate date) { + this.name = name; + this.date = date; + } + + @Override + public boolean test(Statement t) { + if (!Objects.equals(name, t.getTrackingPoint())) + return false; + return RhomeoCore.checkEquality(date, t.getDate()); + } + + public String getName() { + return name; + } + + public LocalDate getDate() { + return date; + } + + @Override + public int compareTo(ByNameAndDateFilter o) { + final int nameComparison = getName().compareTo(o.getName()); + if (nameComparison != 0) + return nameComparison; + return getDate().compareTo(o.getDate()); + } + + @Override + public int hashCode() { + int hash = 3; + hash = 61 * hash + Objects.hashCode(this.name); + hash = 61 * hash + Objects.hashCode(this.date); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final ByNameAndDateFilter other = (ByNameAndDateFilter) obj; + if (!Objects.equals(this.name, other.name)) + return false; + return date == other.date || date != null && other.date != null && date.isEqual(other.date); + } + + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeUTF(name == null? "" : name); + RhomeoCore.writeDate(date, out); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + name = in.readUTF(); + date = RhomeoCore.readDate(in); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.java new file mode 100644 index 0000000..dcf209c --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.java @@ -0,0 +1,282 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import com.sun.javafx.PlatformUtil; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; +import fr.cenra.rhomeo.api.ui.PreferencePane; +import fr.cenra.rhomeo.core.CSVMapper; +import fr.cenra.rhomeo.core.CSVMappingBuilder; +import fr.cenra.rhomeo.preferences.csv.CSVKey; +import fr.cenra.rhomeo.preferences.csv.CSVPreferences; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.logging.Level; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.Window; +import javafx.util.StringConverter; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * A panel to edit CSV format preferences for dataset import / export. You can + * also acquire a {@link CSVMappingBuilder} from a configuration typed in a popup + * by calling {@link #showConfigurationForm(javafx.stage.Window) }. + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class CSVConfigurationPane extends VBox implements PreferencePane { + + private static final String INFO_TEXT = new StringBuilder("L'échappement des chaînes de caractère est optionnel. Le caractère d'échappement est \" et doit entourer la chaine complète.") + .append(System.lineSeparator()) + .append("Pour utiliser le caractère \" dans les messages échappés, il doit être doublé.") + .append(System.lineSeparator()) + .append("Ex : \"Ceci est un texte \"\"échappé\"\".\"") + .toString(); + + @FXML + private ComboBox uiEncoding; + + @FXML + private TextField uiText; + + @FXML + private CheckBox uiTab; + + @FXML + private Label uiInfo; + + @FXML + private Button uiCancel; + + @FXML + private Button uiApply; + + @Autowired + private CSVPreferences prefs; + + private CSVMappingBuilder builder; + + private CSVConfigurationPane() { + super(); + Rhomeo.loadFXML(this); + + Charset defaultCharset; + if (PlatformUtil.isWindows()) + try { + defaultCharset = Charset.forName("windows-1252"); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.FINE, "No charset found for windows-1252.", e); + defaultCharset = StandardCharsets.ISO_8859_1; + } + else + defaultCharset = StandardCharsets.UTF_8; + + Rhomeo.initComboBox(uiEncoding, FXCollections.observableArrayList(Charset.availableCharsets().values()).sorted(), defaultCharset); + uiEncoding.setConverter(new CharsetStringConverter()); + + uiText.disableProperty().bind(uiTab.selectedProperty()); + uiText.textProperty().addListener(this::textChanged); + + uiInfo.setGraphic(new ImageView(Rhomeo.ICON_INFO_BLUE)); + uiInfo.setText(INFO_TEXT); + } + + @PostConstruct + private void init() { + init(prefs.getPreference(CSVPreferences.CHARSET_KEY), prefs.getPreference(CSVPreferences.SEPARATOR_KEY)); + } + + private void init(final String charset, final String separator) { + // Restore default values from preferences. + if (separator.equals("\t")) { + uiTab.setSelected(true); + } else { + uiText.setText(separator); + } + + if (charset != null) + uiEncoding.setValue(Charset.forName(charset)); + } + + private void initButtons() { + uiApply.setVisible(true); + uiCancel.setVisible(true); + + uiApply.setOnAction(this::createBuilder); + uiApply.disableProperty().bind(Bindings.createBooleanBinding(() -> + uiText.getCharacters().length() != 1 && uiEncoding.getValue() == null + , uiText.textProperty(), uiEncoding.valueProperty())); + uiCancel.setCancelButton(true); + } + + /** + * Ensure that typed separator is a single character. + * + * @param obs + * @param oldValue + * @param newValue + */ + private void textChanged(final ObservableValue obs, final String oldValue, final String newValue) { + if (newValue != null && newValue.length() > 1) { + uiText.setText(newValue.substring(newValue.length() -1)); + } + } + + private void createBuilder(final ActionEvent e) { + if (uiEncoding.getValue() == null || (!uiTab.isSelected() && uiText.getText() == null || uiText.getText().isEmpty())) + return; + + final char separator = uiTab.isSelected()? '\t' : uiText.getText().charAt(uiText.getText().length() -1); + builder = new CSVMappingBuilder() + .withSeparator(separator) + .withEncoding(uiEncoding.getValue()); + } + + /** + * Show a dialog allowing user to configure the basic parameters for CSV + * mapping (separator, encoding). + * + * /!\ MUST BE CALLED FROM FX-THREAD. + * + * @return A csv mapper builder if user confirmed its configuration, Nothing + * if he cancelled. + */ + public static Optional showConfigurationForm() { + return showConfigurationForm(null); + } + + /** + * Show a dialog allowing user to configure the basic parameters for CSV + * mapping (separator, encoding). + * + * /!\ MUST BE CALLED FROM FX-THREAD. + * + * @param owner The owner of the dialog to display. If the owner is null, + * then displayed stage is application modal. Otherwise, the modality is + * limited only on owner window. + * @return A csv mapper builder if user confirmed its configuration, Nothing + * if he cancelled. + */ + public static Optional showConfigurationForm(final Window owner) { + final CSVConfigurationPane content = new CSVConfigurationPane(); + content.init(StandardCharsets.UTF_8.name(), String.valueOf(CSVMapper.DEFAULT_SEPARATOR)); + content.initButtons(); + + final Stage stage = new Stage(StageStyle.UTILITY); + final EventHandler btnListener = (evt) -> stage.close(); + content.uiApply.addEventHandler(ActionEvent.ACTION, btnListener); + content.uiCancel.addEventHandler(ActionEvent.ACTION, btnListener); + + if (owner != null) { + stage.initModality(Modality.WINDOW_MODAL); + stage.initOwner(owner); + } else { + stage.initModality(Modality.APPLICATION_MODAL); + } + + stage.setScene(new Scene(content)); + stage.setTitle("Paramètres de lecture"); + stage.showAndWait(); + return Optional.ofNullable(content.builder); + } + + @Override + public void save() { + if (uiText.getText() != null && !uiText.getText().isEmpty()) { + final char separator = uiTab.isSelected() ? '\t' : uiText.getText().charAt(uiText.getText().length() - 1); + prefs.setPreference(CSVPreferences.SEPARATOR_KEY, String.valueOf(separator)); + } + if (uiEncoding.getValue() != null) + prefs.setPreference(CSVPreferences.CHARSET_KEY, uiEncoding.getValue().name()); + + } + + @Override + public PreferenceGroup getPreferenceGroup() { + return prefs; + } + + @Override + public Node getEditor() { + return this; + } + + /** + * A simple converter between string and charset types. + */ + private static class CharsetStringConverter extends StringConverter { + + @Override + public String toString(Charset object) { + if (object == null) + return null; + return object.displayName(); + } + + @Override + public Charset fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) + return null; + return Charset.forName(string); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXDatasetPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXDatasetPane.java new file mode 100644 index 0000000..1c34352 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXDatasetPane.java @@ -0,0 +1,702 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.ObjectWrapper; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.api.ui.FilterTable; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.core.CSVDecoder; +import fr.cenra.rhomeo.core.CSVEncoder; +import fr.cenra.rhomeo.core.CSVMapper; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.fx.validation.FXValidationPane; +import fr.cenra.rhomeo.preferences.csv.CSVPreferences; +import java.io.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.function.Predicate; +import java.util.regex.Pattern; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.IntegerBinding; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.concurrent.Worker; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.SelectionMode; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javax.annotation.PostConstruct; +import javax.validation.ConstraintViolation; +import javax.validation.Path; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.internal.GeotkFX; +import org.geotoolkit.util.collection.CloseableIterator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import fr.cenra.rhomeo.api.ui.FilterTableSpi; +import fr.cenra.rhomeo.core.state.StateManager; +import java.util.concurrent.atomic.AtomicBoolean; +import javafx.beans.Observable; + +/** + * Default editor for {@link Datasset} edition. For {@link Statement} edition, + * we try to inject a {@link StatementEditor} specific to the input {@link Protocol#getDataType() + * }. + * + * IMPORTANT : Input {@link StatementEditor} does not use directly {@link Dataset#getItems() + * }, to avoid sudden changes in the editor caused by an event sourced from + * dataset. However, to keep a responsive dataset, we mirror editor data in + * dataset on the fly. It is needed for state saving (see {@link StateManager) and other mechanisms as validation. + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +@Lazy +public class FXDatasetPane extends VBox { + + private static final Pattern CSV_SUFFIX = Pattern.compile("(?i)\\.csv$"); + + @Autowired + private CSVPreferences csvPrefs; + + @Autowired + private Session session; + + @Autowired + private List pointTableSpis; + + private FilterTable uiGroupTable; + + private StatementEditor uiStatementEditor; + + @Autowired + private FXValidationPane uiValidator; + + @FXML + private Button uiDisplayDocumentation; + @FXML + private Button uiDeleteDataset; + + @FXML + private BorderPane uiPointListContainer; + + @FXML + private Button uiAddPoint; + @FXML + private Button uiDeletePoints; + + @FXML + private BorderPane uiPointEditorContainer; + + @FXML + private BorderPane uiValidationContainer; + + @FXML + private Button uiExportDataset; + + @FXML + private Button uiProceed; + + protected final ResourceBundle bundle; + + private final ListChangeListener itemListener = this::itemsChanged; + + private ObservableList dataList; + + private final AtomicBoolean changeDetected = new AtomicBoolean(false); + + public FXDatasetPane() { + bundle = ResourceBundle.getBundle(FXDatasetPane.class.getCanonicalName()); + Rhomeo.loadFXML(this, FXDatasetPane.class); + } + + @PostConstruct + private void init() { + ArgumentChecks.ensureNonNull("Session containing data", session); + ArgumentChecks.ensureNonNull("Data context", session.getDataContext()); + ArgumentChecks.ensureNonNull("Target protocol", session.getDataContext().getProtocol()); + ArgumentChecks.ensureNonNull("Target dataset", session.getDataset()); + + for (final FilterTableSpi spi : pointTableSpis) { + if (spi.getProtocol() == session.getDataContext().getProtocol()) { + uiGroupTable = spi.createEmptyTable(); + break; + } + } + + if (uiGroupTable == null) { + uiGroupTable = (FilterTable) new FXTrackingPointTable(session); + } + + uiGroupTable.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + uiGroupTable.setMinHeight(50); + uiGroupTable.setFixedCellSize(25); + final IntegerBinding size = Bindings.size(uiGroupTable.getItems()); + uiGroupTable.prefHeightProperty().bind( + Bindings.createDoubleBinding(() -> { + if (size.get() < 1) { + return 50d; + } + return size.get() * uiGroupTable.getFixedCellSize() + 30; + }, size, uiGroupTable.fixedCellSizeProperty()) + ); + + Optional optEditor = session.findEditor(session.getDataContext().getProtocol().getDataType()); + if (optEditor.isPresent()) { + uiStatementEditor = optEditor.get(); + if (uiStatementEditor instanceof Region) { + ((Region)uiStatementEditor).setMinHeight(50); + } + uiStatementEditor.itemsProperty().addListener(this::itemsPropertyChanged); + uiGroupTable.getSelectionModel().selectedItemProperty().addListener((obs, oldGroup, newGroup) -> { + if (newGroup == null) { + uiStatementEditor.setItems(FXCollections.observableArrayList()); + } else { + uiStatementEditor.setItems(FXCollections.observableArrayList(session.getDataset().getItems().filtered(newGroup))); + } + }); + + uiStatementEditor.setItems(FXCollections.observableArrayList()); + uiPointEditorContainer.setCenter((Node) uiStatementEditor); + } else { + uiAddPoint.setVisible(false); + uiAddPoint.setManaged(false); + } + + uiPointListContainer.setCenter(uiGroupTable); + uiPointEditorContainer.managedProperty().bind(uiPointEditorContainer.visibleProperty()); + + uiValidationContainer.setCenter(uiValidator); + uiValidator.getWarningList().getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + uiValidator.getWarningList().getSelectionModel().selectedItemProperty().addListener(this::selectedErrorChanged); + uiValidator.getErrorList().getSelectionModel().setSelectionMode(SelectionMode.SINGLE); + uiValidator.getErrorList().getSelectionModel().selectedItemProperty().addListener(this::selectedErrorChanged); + /* + * HACK : Error list force focus on itself after click. So we + * have to repeat the selectedErrorChanged on mouse released. + */ + final EventHandler handler = evt -> { + final ConstraintViolation violation; + if (evt.getSource() == uiValidator.getWarningList()) { + violation = uiValidator.getWarningList().getSelectionModel().getSelectedItem(); + } else if (evt.getSource() == uiValidator.getErrorList()) { + violation = uiValidator.getErrorList().getSelectionModel().getSelectedItem(); + } else { + violation = null; + } + + if (violation != null) + selectedErrorChanged(null, null, violation); + }; + uiValidator.getErrorList().addEventHandler(MouseEvent.MOUSE_RELEASED, handler); + uiValidator.getWarningList().addEventHandler(MouseEvent.MOUSE_RELEASED, handler); + + /* + * Disability + */ + BooleanBinding emptyDataset = Bindings.isEmpty(session.getDataset().getItems()); + uiDeleteDataset.disableProperty().bind(emptyDataset); + uiExportDataset.disableProperty().bind(emptyDataset); + uiProceed.disableProperty().bind(emptyDataset); + + uiDeletePoints.disableProperty().bind(uiGroupTable.getSelectionModel().selectedItemProperty().isNull()); + + // Icons + uiDeleteDataset.setGraphic(new ImageView(Rhomeo.ICON_TRASH_BLUE)); + uiDeletePoints.setGraphic(new ImageView(Rhomeo.ICON_TRASH_BLUE)); + uiDisplayDocumentation.setGraphic(new ImageView(Rhomeo.ICON_FILE_TEXT)); + + session.getDataset().getItems().addListener(this::changeDetected); + } + + @FXML + void addPoint(ActionEvent event) { + // No tracking point selected. However, a null value will allow creation + // of a fresh one. We force editor visibility. + uiGroupTable.getSelectionModel().clearSelection(); + uiStatementEditor.setItems(FXCollections.observableArrayList()); + } + + @FXML + void deleteDataset(ActionEvent event) { + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, bundle.getString("dataset.delete.confirm"), ButtonType.NO, ButtonType.YES); + alert.setResizable(true); + if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) { + // issue #79 : focused filter is not suppressed. + uiGroupTable.getSelectionModel().clearSelection(); + session.getDataset().getItems().clear(); + if (uiStatementEditor != null) + uiStatementEditor.setItems(FXCollections.observableArrayList()); + uiValidator.reset(); + } + } + + @FXML + void deletePoints(ActionEvent event) { + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, bundle.getString("point.delete.confirm"), ButtonType.NO, ButtonType.YES); + alert.setResizable(true); + if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) { + // Defensive copy + final ArrayList selected = new ArrayList<>(uiGroupTable.getSelectionModel().getSelectedItems()); + // HACK : filter table won't delete entries if they're focused. + uiGroupTable.getSelectionModel().clearSelection(); + if (!selected.isEmpty()) { + Predicate filter = selected.get(0); + for (int i = 1; i < selected.size(); i++) { + filter = filter.or(selected.get(i)); + } + session.getDataset().getItems().removeIf(filter); + } + } + } + + @FXML + void displayDocumentation(ActionEvent event) { + final Protocol protocol = session.getDataContext().getProtocol(); + Rhomeo.generateModelZip(protocol, uiDisplayDocumentation); + } + + @FXML + Task exportDataset(ActionEvent event) { + final File chosen = chooseCSV(uiExportDataset.getText(), true); + if (chosen != null) { + final TaskManager.MockTask export = new TaskManager.MockTask(bundle.getString("dataset.export.task"), () -> { + final char sep; + final String separator = csvPrefs.getPreference(CSVPreferences.SEPARATOR_KEY); + if (separator == null || separator.isEmpty()) + sep = CSVMapper.DEFAULT_SEPARATOR; + else + sep = separator.charAt(separator.length() - 1); + + // Check if we must transform data when encoding. + Class dataType = session.getDataContext().getProtocol().getDataType(); + Iterator toEncode = session.getDataset().getItems().iterator(); + Optional opt = session.getWrapper(dataType); + if (opt.isPresent()) { + dataType = opt.get().getOutputType(); + toEncode = new Transformer(opt.get(), toEncode); + } + + final CSVEncoder encoder = new CSVEncoder( + chosen.toPath(), dataType, sep, Charset.forName(csvPrefs.getPreference(CSVPreferences.CHARSET_KEY)), null); + encoder.encode(toEncode, true); + return true; + }); + + export.setOnSucceeded(evt -> changeDetected.set(false)); + + export.setOnFailed(evt -> Platform.runLater(() -> { + GeotkFX.newExceptionDialog(bundle.getString("dataset.export.error"), export.getException()); + })); + + export.runningProperty().addListener((obs, oldValue, newValue) -> { + setDisable(newValue); + // TODO : show a loading popup + }); + + return TaskManager.INSTANCE.submit(export); + } + + return null; + } + + @FXML + void importDataset(ActionEvent event) { + Task previousTask = null; + if (!session.getDataset().getItems().isEmpty()) { + final String contentText = bundle.getString("save.before.import"); + final Alert saveCurrent = new Alert(Alert.AlertType.WARNING, contentText, ButtonType.CANCEL, ButtonType.NO, ButtonType.YES); + saveCurrent.setResizable(true); + ButtonType choice = saveCurrent.showAndWait().orElse(ButtonType.CANCEL); + if (ButtonType.CANCEL.equals(choice)) { + return; + } else if (ButtonType.YES.equals(choice)) { + previousTask = exportDataset(event); + if (previousTask == null) { + return; // user cancelled saving. Stop the entire process. + } + } + } + + final File input = chooseCSV(bundle.getString("dataset.import.choice"), false); + if (input == null) + return; + + /* On dataset change, the trigger serving to notify user on possible + * unsaved entries. But, as we are just importing data, we must keep + * the same state as before the task. + */ + final boolean previousStatus = changeDetected.get(); + final TaskManager.MockTask importTask = new TaskManager.MockTask(bundle.getString("dataset.import.task"), () -> { + final HashSet ignorable = new HashSet<>(2); + ignorable.add("trackingPoint"); + ignorable.add("remarks"); + final char sep; + final String separator = csvPrefs.getPreference(CSVPreferences.SEPARATOR_KEY); + if (separator == null || separator.isEmpty()) + sep = CSVMapper.DEFAULT_SEPARATOR; + else + sep = separator.charAt(separator.length() - 1); + final CSVDecoder decoder = new CSVDecoder( + input.toPath(), + session.getDataContext().getProtocol().getDataType(), + sep, + Charset.forName(csvPrefs.getPreference(CSVPreferences.CHARSET_KEY)), + ignorable + ); + + // Check doublons. + int integrated = 0; + int doublons = 0; + final ObservableList dataset = session.getDataset().getItems(); + final HashSet doublonDetector = new HashSet(dataset); + try (final CloseableIterator it = decoder.decodeLazy()) { + Object read; + while (it.hasNext()) { + read = it.next(); + if (doublonDetector.add(read)) { + integrated++; + } else { + doublons++; + } + } + } + + // Update dataset with new elements. + doublonDetector.removeAll(dataset); + final TaskManager.MockTask t = new TaskManager.MockTask(() -> dataset.addAll(doublonDetector)); + Platform.runLater(t); + t.get(); + + if (doublons <= 0) + return String.format(bundle.getString("dataset.import.result.no-doublon"), integrated); + else + return String.format(bundle.getString("dataset.import.result"), integrated, doublons); + }); + + importTask.setOnFailed(evt -> Platform.runLater(() -> { + GeotkFX.newExceptionDialog(bundle.getString("dataset.import.error"), importTask.getException()); + })); + + importTask.setOnSucceeded(evt -> Platform.runLater(() -> { + changeDetected.set(previousStatus); + Rhomeo.showAlert(Alert.AlertType.INFORMATION, importTask.getValue()); + })); + + if (previousTask != null) { + final SimpleObjectProperty previousState = new SimpleObjectProperty<>(); + previousState.addListener((obs, oldState, newState) -> { + if (Worker.State.SUCCEEDED.equals(newState)) { + TaskManager.INSTANCE.submit(importTask); + } + }); + previousState.bind(previousTask.stateProperty()); + } else { + TaskManager.INSTANCE.submit(importTask); + } + } + + @FXML + void nextStep(ActionEvent event) { + Task previousTask = checkSaveChange().orElse(null); + + final Runnable proceed = () -> { + setProcessContext(session); + + /* + * Check if we're able to jump to the next step. If not, we try to guess + * why, and inform user of what is wrong. + */ + if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) { + if (session.getDataContext().getReferences().size() < session.getDataContext().getProtocol().getReferenceTypes().size()) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Les versions des référentiels associés au protocole ne sont pas définies !"); + } else { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données."); + } + } + }; + + if (previousTask != null) { + previousTask.setOnSucceeded(evt -> { + if (Boolean.TRUE.equals(evt.getSource().getValue())) + Platform.runLater(proceed); + }); + } else { + proceed.run(); + } + } + + /** + * If changes have been detected in currently edited dataset, ask to user if + * he wants to save them, and launch export process if he agrees. + * + * /!\ You must call this method from FX thread. + * + * @return An empty object if there's no change to save. Otherwise, a task + * is returned, which returns false if user cancelled saving, or true if + * data has been successfully written. + */ + public Optional> checkSaveChange() { + if (!changeDetected.get()) { + return Optional.empty(); + } + + final String contentText = bundle.getString("save.before.next"); + final Alert saveCurrent = new Alert(Alert.AlertType.CONFIRMATION, contentText, ButtonType.CANCEL, ButtonType.NO, ButtonType.YES); + saveCurrent.setResizable(true); + ButtonType choice = saveCurrent.showAndWait().orElse(ButtonType.CANCEL); + + if (ButtonType.YES.equals(choice)) { + final Task exportTask = exportDataset(null); + if (exportTask != null) + return Optional.of(exportTask); + } else if (ButtonType.NO.equals(choice)) { + return Optional.empty(); + } + + final TaskManager.MockTask t = new TaskManager.MockTask(() -> false); + t.run(); + return Optional.of(t); + } + + /** + * When a change is detected on input dataset, we notify user that changes + * occurred and some data may not have been saved. + * + * @param obs + */ + private void changeDetected(final Observable obs) { + /* If dataset has been emptied, we clear change state. We check it here, + * so it is valid either if user deleted it manually or via "clear dataset" + * button. + */ + if (session.getDataset().getItems().isEmpty()) + changeDetected.set(false); + else + changeDetected.set(true); + } + + /** + * When we change items of inner {@link StatementEditor}, we update + * listeners registered on it. + * + * @param obs + */ + private void itemsPropertyChanged(final Observable obs) { + if (dataList != null) + dataList.removeListener(itemListener); + + dataList = uiStatementEditor.getItems(); + if (dataList != null) { + dataList.addListener(itemListener); + } + } + + /** + * Report changes from editor list to dataset. + * @param c + */ + private void itemsChanged(final ListChangeListener.Change c) { + ObservableList dataset = session.getDataset().getItems(); + while (c.next()) { + if (c.wasRemoved()) { + // Delete strict reference, because if the user has added multiple new + // objects in the table, they could be equal. + final ArrayList removedCopy = new ArrayList(c.getRemoved()); + dataset.removeIf(item -> { + final Iterator it = removedCopy.iterator(); + while (it.hasNext()) { + if (item == it.next()) { + it.remove(); + return true; + } + } + return false; + }); + } + if (c.wasAdded()) { + dataset.addAll(c.getAddedSubList()); + } + } + } + + /** + * Utility method to set {@link Session}'s {@link ProcessContext}. + * + * @param session + */ + public static void setProcessContext(final Session session){ + final ProcessContext processContext; + if (session.getProcessContext() != null) { + processContext = session.getProcessContext(); + // We keep previous selection, but clean tracking points which does + // not exist anymore. After this operation, if we've got no more + // point selected, we reset selection. +// processContext.getTrackingPoints().retainAll(session.getDataset().getTrackingPoints()); +// if (processContext.getTrackingPoints().isEmpty()) { +// processContext.getTrackingPoints().addAll(session.getDataset().getTrackingPoints()); +// } + } else { + // Create a process context. By default, we activate all available indicators for the edited protocol. + processContext = new ProcessContext(session.getDataset(), session.getDataContext()); + session.getIndicators().stream() + .filter(i -> i.getProtocol() == session.getDataContext().getProtocol()) + .forEach(i -> processContext.getIndicators().add(i)); + //processContext.getTrackingPoints().addAll(session.getDataset().getTrackingPoints()); + } + + session.setProcessContext(processContext); + } + + /** + * On error selection, we try to retrieve the origin statement, then focus + * on it in the statement editor. + * + * @param obs + * @param oldValue + * @param newValue + */ + private void selectedErrorChanged(final ObservableValue obs, final ConstraintViolation oldValue, final ConstraintViolation newValue) { + if (newValue != null) { + if (newValue.getLeafBean() instanceof Statement) { + final Statement st = (Statement) newValue.getLeafBean(); + /* + * Find group related to the target statement. We + * need it to initialize statement editor. + */ + for (final Predicate p : uiGroupTable.getItems()) { + if (p.test(st)) { + uiGroupTable.getSelectionModel().select(p); + break; + } + } + + final Iterator it = newValue.getPropertyPath().iterator(); + String pName = null; + while (it.hasNext()) { + pName = it.next().getName(); + } + uiStatementEditor.focusOn(st, pName); + } + } + } + + /** + * Display a file chooser to allow user to choose a CSV file to open or save. + * @param title The title to display on the file chooser header. + * @param saveMode True to show file chooser in save mode (see {@link FileChooser#showSaveDialog(javafx.stage.Window) }). + * False to use opening mode (See {@link FileChooser#showOpenDialog(javafx.stage.Window) }. + * + * @return The file selected by user. Can be null if user cancelled. + */ + private File chooseCSV(final String title, final boolean saveMode) { + final FileChooser chooser = new FileChooser(); + chooser.setTitle(title); + chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Fichier CSV", "*.csv")); + final File previousPath = Rhomeo.getPreviousPath(FXDatasetPane.class); + if (previousPath != null) { + chooser.setInitialDirectory(previousPath); + } + + File result = saveMode? + chooser.showSaveDialog(getScene().getWindow()): + chooser.showOpenDialog(getScene().getWindow()); + + if (result != null) { + Rhomeo.setPreviousPath(FXDatasetPane.class, result.getParentFile()); + if (!CSV_SUFFIX.matcher(result.getName()).find()) { + result = new File(result.getParentFile(), result.getName().concat(".csv")); + } + } + + return result; + } + + private static class Transformer implements Iterator { + + final ObjectWrapper wrapper; + final Iterator source; + + public Transformer(ObjectWrapper wrapper, Iterator source) { + this.wrapper = wrapper; + this.source = source; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public O next() { + return wrapper.apply(source.next()); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXFloreEditor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXFloreEditor.java new file mode 100644 index 0000000..a46bacf --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXFloreEditor.java @@ -0,0 +1,160 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p02.list.PhysionomyFlore; +import fr.cenra.rhomeo.protocol.p02.FloreStatement; +import java.util.Arrays; +import java.util.List; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.util.StringConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class FXFloreEditor extends AbstractCustomStatementEditor { + + @FXML + private ComboBox uiPhysio; + + public FXFloreEditor() { + super(FloreStatement.class); + Rhomeo.loadFXML(this, FXFloreEditor.class); + + Rhomeo.initComboBox(uiPhysio, FXCollections.observableList(Arrays.asList(PhysionomyFlore.values()))); + uiPhysio.valueProperty().addListener(this::physionomyChanged); + uiPhysio.setConverter(new StringConverter() { + @Override + public String toString(PhysionomyFlore object) { + if (object == null) + return null; + return new StringBuilder(object.getCode()).append(" - ").append(object.getValue()).toString(); + } + + @Override + public PhysionomyFlore fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + return RhomeoCore.getByCode(PhysionomyFlore.class, string.split("\\s")[0]).orElse(null); + } + }); + } + + @Override + protected void init() { + super.init(); + if (uiStatementTable != null) + uiStatementTable.sortColumns(Arrays.asList("cd_nom", "abundance")); + } + + @Override + protected String[] getFieldsToIgnore() { + final String[] toIgnore = super.getFieldsToIgnore(); + final String[] overrided = new String[toIgnore.length + 2]; + overrided[0] = "physionomy"; + overrided[1] = "strate"; + System.arraycopy(toIgnore, 0, overrided, 2, toIgnore.length); + return overrided; + } + + @Override + protected FloreStatement initStatement() throws ReflectiveOperationException { + final FloreStatement data = super.initStatement(); + if (uiPhysio.getValue() instanceof PhysionomyFlore) { + ((FloreStatement) data).setPhysionomy(uiPhysio.getValue().getCode()); + } + return data; + } + + /* + * UI LISTENERS + */ + private void physionomyChanged(final ObservableValue obs, final PhysionomyFlore oldPhysio, final PhysionomyFlore newPhysio) { + final String newCode = newPhysio == null ? null : newPhysio.getCode(); + if (uiStatementTable == null || uiStatementTable.getItems() == null) + return; + + for (int i = 0; i < uiStatementTable.getItems().size(); i++) { + uiStatementTable.getItems().get(i).physionomyProperty().set(newCode); + } + } + + @Override + protected void itemsChanged(final ObservableValue obs, final List oldItems, final List newItems) { + super.itemsChanged(obs, oldItems, newItems); + if (newItems != null && !newItems.isEmpty()) { + uiPhysio.setValue(RhomeoCore.getByCode(PhysionomyFlore.class, newItems.get(0).getPhysionomy()).orElse(null)); + } else { + uiPhysio.setValue(null); + } + } + + @Component + public static class Spi implements StatementEditorSpi { + + @Autowired + Session session; + + @Override + public StatementEditor createEditor() { + return session.createPrototype(FXFloreEditor.class); + } + + @Override + public Class getDataType() { + return FloreStatement.class; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXOrthopteraEditor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXOrthopteraEditor.java new file mode 100644 index 0000000..b2075f3 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXOrthopteraEditor.java @@ -0,0 +1,79 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p05.OrthopteraStatement; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Samuel Andrés (Geomatys) + */ +@Component +@Scope("prototype") +public class FXOrthopteraEditor extends AbstractCustomStatementEditor { + + public FXOrthopteraEditor() { + super(OrthopteraStatement.class); + Rhomeo.loadFXML(this, FXFloreEditor.class); + } + + @Component + public static class Spi implements StatementEditorSpi { + + @Autowired + Session session; + + @Override + public StatementEditor createEditor() { + return session.createPrototype(FXOrthopteraEditor.class); + } + + @Override + public Class getDataType() { + return OrthopteraStatement.class; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXP07Editor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXP07Editor.java new file mode 100644 index 0000000..0c25113 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXP07Editor.java @@ -0,0 +1,87 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p07.P07Statement; +import java.util.Arrays; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class FXP07Editor extends AbstractCustomStatementEditor { + + public FXP07Editor() { + super(P07Statement.class); + Rhomeo.loadFXML(this, FXFloreEditor.class); + } + + @Override + protected void init() { + super.init(); + if (uiStatementTable != null) + uiStatementTable.sortColumns(Arrays.asList("cd_nom", "number")); + } + + @Component + public static class Spi implements StatementEditorSpi { + + @Autowired + Session session; + + @Override + public StatementEditor createEditor() { + return session.createPrototype(FXP07Editor.class); + } + + @Override + public Class getDataType() { + return P07Statement.class; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXTrackingPointTable.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXTrackingPointTable.java new file mode 100644 index 0000000..a6c9e03 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/FXTrackingPointTable.java @@ -0,0 +1,207 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.ui.FilterTable; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p01.P01; +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.transformation.FilteredList; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.util.Callback; + +/** + * A simple table whose aim is to display tracking point information. + * @author Alexis Manin (Geomatys) + */ +public class FXTrackingPointTable extends FilterTable { + + private static final Callback, TableCell> DATE_CELL_FACTORY = FXTrackingPointTable::createDateCell; + + private final Session session; + private final TableColumn nbColumn; + + private final FilteredList dataset; + + public FXTrackingPointTable(final Session session) { + this.session = session; + final TableColumn nameColumn = new TableColumn("Nom"); + nameColumn.setCellValueFactory(input -> new SimpleStringProperty(input.getValue().getName())); + + final TableColumn dateColumn = new TableColumn("Date"); + dateColumn.setCellValueFactory(input -> (ObservableValue) new SimpleObjectProperty<>(input.getValue().getDate())); + dateColumn.setCellFactory(DATE_CELL_FACTORY); + + // HACK for column name (we should not do it that way, but time...) + String nbTitle = "Nombre"; + if (session.getDataContext() != null && session.getDataContext().getProtocol() != null) { + final Protocol p = session.getDataContext().getProtocol(); + if (p instanceof P01) + nbTitle = "Nombre d'horizons"; + else + nbTitle = "Nombre d'observations"; + } + nbColumn = new TableColumn(nbTitle); + nbColumn.setCellValueFactory(this::getSubsetSize); + + getColumns().addAll(nameColumn, dateColumn, nbColumn); + setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + setItems(FXCollections.observableArrayList()); + + dataset = session.getDataset().getItems().filtered(null); // Prepare a filtered list to allow user setting filters. + dataset.addListener(this::datasetChanged); + addFilters(dataset); + } + + @Override + public Predicate getPreFilter() { + return dataset.getPredicate(); + } + + @Override + public void setPreFilter(final Predicate filter) { + dataset.setPredicate(filter); + } + + @Override + public ObjectProperty> preFilterProperty() { + return dataset.predicateProperty(); + } + + private ObservableValue getSubsetSize(final TableColumn.CellDataFeatures cellFeature) { + // If given value and current dataset are not null, we set column value as tracking point subset size. + if (cellFeature.getValue() != null && session != null && session.getDataset() != null) { + return (ObservableValue) Bindings.size(dataset.filtered(cellFeature.getValue())); + } else { + return new SimpleObjectProperty<>(null); + } + } + + /** + * Every time session dataset changes, we update list of available groups. + * @param c Changes which occurred on surveyed data. + */ + private void datasetChanged(final ListChangeListener.Change c) { + while (c.next()) { + if (c.wasUpdated()) { + removeObsolete(); + addFilters(c.getList().subList(c.getFrom(), c.getTo())); + } else { + if (c.wasRemoved()) { + removeObsolete(); + } + + addFilters(c.getAddedSubList()); + } + } + } + + /** + * Delete from table all useless entries. I.e all entries which don't have + * any statement associated anymore, and not being currently edited. + */ + private void removeObsolete() { + /* HACK : To find obsolete groups, we just browse count column, and + * delete matching items for each count equal to 0. + */ + final Iterator it = getItems().iterator(); + Integer nb; + ByNameAndDateFilter current; + while (it.hasNext()) { + current = it.next(); + // Even if the current group is empty, we don't delete + // it if user is working with it. + if (current.equals(getSelectionModel().getSelectedItem())) + continue; + nb = nbColumn.getCellData(current); + if (nb == null || nb == 0) + it.remove(); + } + } + + /** + * Add filters matching given data name/date pairs. + * @param dataset Data which will serve to build list of filters to add. + */ + private void addFilters(final List dataset) { + if (dataset == null || dataset.isEmpty()) + return; + + final HashSet set = new HashSet<>(); + for (final Statement st : dataset) { + set.add(new ByNameAndDateFilter(st.getTrackingPoint(), st.getDate())); + } + + synchronized (this) { + set.removeAll(getItems()); // Ensure no doublon is added. + getItems().addAll(set); + } + } + + private static TableCell createDateCell(final TableColumn column) { + return new TableCell() { + @Override + protected void updateItem(LocalDate item, boolean empty) { + super.updateItem(item, empty); + if (empty) + setText(null); + else if (item == null) + setText(" - "); + else + setText(item.toString()); + } + }; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ObjectTable.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ObjectTable.java new file mode 100644 index 0000000..c1bdeb7 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/ObjectTable.java @@ -0,0 +1,192 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.MethodDescriptor; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import org.apache.sis.util.ArgumentChecks; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class ObjectTable extends TableView { + + private final Class dataType; + private final BeanInfo beanInfo; + + /** + * Create a new table to expose data type properties. + * + * @param objectType Type of row item objects. + * @param blackList A set of properties to ignore when building table columns. + * + * @throws IntrospectionException + */ + public ObjectTable(final Class objectType, String... blackList) throws IntrospectionException { + ArgumentChecks.ensureNonNull("Data type", objectType); + dataType = objectType; + beanInfo = Introspector.getBeanInfo(objectType, Object.class); + + final Set toIgnore; + if (blackList == null || blackList.length <= 0) { + toIgnore = Collections.EMPTY_SET; + } else { + toIgnore = new HashSet(Arrays.asList(blackList)); + } + + // Index methods by name to find them easily + final MethodDescriptor[] mds = beanInfo.getMethodDescriptors(); + final Map methods = new HashMap<>(mds.length); + for (final MethodDescriptor md : mds) { + methods.put(md.getName(), md.getMethod()); + } + + final boolean bundleAvailable = InternationalResource.class.isAssignableFrom(dataType); + boolean editable = false; + for (final PropertyDescriptor desc : beanInfo.getPropertyDescriptors()) { + if (toIgnore.contains(desc.getName())) + continue; + + try { + if (bundleAvailable) { + try { + desc.setDisplayName(InternationalResource.getResourceString((Class) dataType, desc.getName(), "label")); + desc.setShortDescription(InternationalResource.getResourceString((Class) dataType, desc.getName(), "tooltip")); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.FINE, "No bundle available for property ".concat(desc.getName()), e); + } + } + final PropertyColumn col = new PropertyColumn<>(desc, methods.get(desc.getName().concat("Property"))); + if (col.isEditable()) + editable = true; + getColumns().add(col); + } catch (IllegalArgumentException e) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot create a table column for property ".concat(desc.getName()), e); + } + } + + setEditable(editable); + setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY); + } + + /** + * Try to sort table columns according to the names given in input list. Names + * in input list must be a property name, or (only for columns not working with object properties) + * the text of the column. Order in the list will define the order of the columns. + * + * @param orderedNames Order to apply on this table columns. + */ + public void sortColumns(final String... orderedNames) { + if (orderedNames == null || orderedNames.length < 1) + return; + sortColumns(Arrays.asList(orderedNames)); + } + + public void sortColumns(final List orderedNames) { + if (orderedNames == null || orderedNames.isEmpty()) + return; + + getColumns().sort((TableColumn o1, TableColumn o2) -> { + final String firstName, secondName; + if (o1 instanceof PropertyColumn) { + firstName = ((PropertyColumn)o1).descriptor.getName(); + } else { + firstName = o1.getText(); + } + if (o2 instanceof PropertyColumn) { + secondName = ((PropertyColumn)o2).descriptor.getName(); + } else { + secondName = o2.getText(); + } + + final int firstOccur = orderedNames.indexOf(firstName); + final int secondOccur = orderedNames.indexOf(secondName); + // Second property not in the given list. No change in ordering. + if (secondOccur < 0) + return -1; + // First property not found. Moved to the back. + else if (firstOccur < 0) + return 1; + else + return orderedNames.indexOf(firstName) - orderedNames.indexOf(secondName); + }); + } + + public Class getDataType() { + return dataType; + } + + public void focusOn(T item, String propertyName) { + final int rowIndex = getItems().indexOf(item); + if (rowIndex >= 0) { + if (propertyName == null || (propertyName = propertyName.trim()).isEmpty()) { + getSelectionModel().select(item);// No property given. Select row. + } else { + // Try to find the column of the property, to focus / edit queried cell. + for (final TableColumn col : getColumns()) { + if (col instanceof PropertyColumn && ((PropertyColumn) col).descriptor.getName().equals(propertyName)) { + if (col.isEditable()) { + edit(rowIndex, col); + } else { + getFocusModel().focus(rowIndex, col); + } + return; + } + } + } + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P01Editor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P01Editor.java new file mode 100644 index 0000000..e086e1a --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P01Editor.java @@ -0,0 +1,104 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p01.PedologyStatement; +import java.util.ResourceBundle; +import javafx.event.ActionEvent; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.TableView; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class P01Editor extends AbstractCustomStatementEditor { + + public P01Editor() { + super(PedologyStatement.class); + Rhomeo.loadFXML(this, P01Editor.class); + } + + @Override + protected void init() { + super.init(); + if (uiStatementTable != null) { + uiStatementTable.setMinHeight(30); + uiStatementTable.setMaxSize(Double.MAX_VALUE, USE_COMPUTED_SIZE); + uiStatementTable.setColumnResizePolicy(TableView.UNCONSTRAINED_RESIZE_POLICY); + uiStatementTable.sortColumns("numberH", "depth", "limits", "color", "value", "chroma", "texture", "structure", "rawElements", "roots", "stains", "abundance", "size", "shape", "humidity", "compactness", "plasticity", "adhesiveness", "friability", "alterationMo", "vonPost"); + } + } + + @Override + protected void deleteStatements(ActionEvent evt) { + final Alert alert = new Alert(Alert.AlertType.CONFIRMATION, ResourceBundle.getBundle(P01Editor.class.getCanonicalName()).getString("statement.delete.confirm"), ButtonType.NO, ButtonType.YES); + alert.setResizable(true); + if (ButtonType.YES.equals(alert.showAndWait().orElse(ButtonType.NO))) { + super.deleteStatements(evt); + } + } + + @Component + public static class Spi implements StatementEditorSpi { + + @Autowired + Session session; + + @Override + public StatementEditor createEditor() { + return session.createPrototype(P01Editor.class); + } + + @Override + public Class getDataType() { + return PedologyStatement.class; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04Editor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04Editor.java new file mode 100644 index 0000000..a13b3d3 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04Editor.java @@ -0,0 +1,222 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Statement; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import static fr.cenra.rhomeo.fx.edition.FXDatasetPane.setProcessContext; +import fr.cenra.rhomeo.protocol.p04.I04; +import fr.cenra.rhomeo.protocol.p04.I07; +import fr.cenra.rhomeo.protocol.p04.P04Statement; +import java.beans.IntrospectionException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.DoubleBinding; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.TableView; +import javafx.scene.layout.BorderPane; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.gui.javafx.util.TaskManager; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class P04Editor extends BorderPane { + + @FXML + private Button uiFinalize; + + @FXML + private BorderPane uiTableContainer; + + @FXML + private Button uiDelete; + + private final Session session; + + private final StatementTable table; + DoubleBinding sizeBinding = null; + + public P04Editor(final Session session) throws IntrospectionException { + ArgumentChecks.ensureNonNull("Session", session); + this.session = session; + Rhomeo.loadFXML(this); + table = new StatementTable<>(P04Statement.class, "trackingPoint", "date", "remarks"); + table.sortColumns("year", "habitat", "humine", "acids", "pt", "cot"); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + uiTableContainer.setCenter(table); + if (session.getDataset() != null) { + table.setItems(session.getDataset().getItems()); + table.setFixedCellSize(32); + sizeBinding = Bindings.createDoubleBinding(() -> { + final int elementNb = table.getItems().size() < 1? 1 : table.getItems().size(); + return table.getFixedCellSize() * elementNb + 30; // Header size is not counted in element numbers, so we add a hard-coded size for it. + }, table.getItems(), table.fixedCellSizeProperty()); + table.prefHeightProperty().bind(sizeBinding); + } + + uiDelete.disableProperty().bind(table.getSelectionModel().selectedItemProperty().isNull()); + uiFinalize.disableProperty().bind(Bindings.isEmpty(table.getItems())); + } + + @FXML + void addStatement(ActionEvent event) throws ReflectiveOperationException { + session.getDataset().getItems().add(session.getDataContext().getProtocol().getDataType().newInstance()); + } + + @FXML + void deleteSelection(ActionEvent event) { + // Defensive copy + final Collection selectedItems = new ArrayList<>(table.getSelectionModel().getSelectedItems()); + // Delete strict reference, because if the user has added multiple new + // objects in the table, they could be equal. + if (!selectedItems.isEmpty()) { + session.getDataset().getItems().removeIf(item -> { + for (final Statement selected : selectedItems) { + if (selected == item) + return true; + } + return false; + }); + } + } + + @FXML + void validate(ActionEvent event) { + final Validator validator = session.getBean(Validator.class); + final Set> result = validator.validate(session.getDataset()); + if (result != null && !result.isEmpty()) { + final StringBuilder builder = new StringBuilder("La saisie n'est pas valide : "); + int index; + for (final ConstraintViolation cv : result) { + builder.append(System.lineSeparator()).append(" - "); + index = -1; + final Object leafBean = cv.getLeafBean(); + for (int i = 0 ; i < table.getItems().size() ; i++) { + if (leafBean == table.getItems().get(i)) { + index = i; + break; + } + } + + if (index >= 0) { + builder.append("Ligne ").append(index + 1).append(" : "); + } + builder.append(cv.getMessage()); + } + Rhomeo.showAlert(Alert.AlertType.WARNING, builder.toString()); + return; + } + + // Get P04 indicators + Indicator i04 = null; + Indicator i07 = null; + for (final Indicator i : session.getIndicators()) { + if (I04.NAME.equals(i.getName())) { + i04 = i; + if (i07 != null) + break; + } else if (I07.NAME.equals(i.getName())) { + i07 = i; + if (i04 != null) + break; + } + } + + // Ask indicators to convert seized data into indices. + final List processes = new ArrayList<>(); + if (i04 != null) { + i04.createProcesses(new ProcessContext(session.getDataset(), session.getDataContext())).forEach(p -> { + TaskManager.INSTANCE.submit(p); + processes.add(p); + }); + } + + if (i07 != null) { + i07.createProcesses(new ProcessContext(session.getDataset(), session.getDataContext())).forEach(p -> { + TaskManager.INSTANCE.submit(p); + processes.add(p); + }); + } + + // Aggregate / sort results. + final Task> t = new TaskManager.MockTask("Traitement des résultats...", () -> { + Set indices = new HashSet<>(); + for (final fr.cenra.rhomeo.api.process.Process p : processes) { + try { + indices.addAll(p.get()); + } catch (Exception ex) { + Rhomeo.LOGGER.log(Level.WARNING, "A process failed", ex); + } + } + + return indices; + }); + + // Proceed to finalization. + t.setOnSucceeded((evt) -> { + session.setResults(t.getValue()); + session.requestWorkflowStep(WorkflowStep.FINALIZATION); + }); + + t.setOnFailed((evt) -> Rhomeo.showAlert(Alert.AlertType.WARNING, "Une erreur s'est produite. Impossible de procéder à la finalisation.")); + + TaskManager.INSTANCE.submit(t); + + setProcessContext(session); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04EditorSpi.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04EditorSpi.java new file mode 100644 index 0000000..25d7cca --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P04EditorSpi.java @@ -0,0 +1,109 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.protocol.p04.P04; +import java.beans.IntrospectionException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import javafx.scene.Node; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class P04EditorSpi implements ProtocolEditorSpi { + + @Autowired + @Qualifier(P04.NAME) + Protocol p04; + + @Autowired + Session session; + + final Set toIgnore; + + private P04EditorSpi() { + final HashSet steps = new HashSet<>(2); + steps.add(WorkflowStep.PROCESS); + steps.add(WorkflowStep.RESULT); + toIgnore = Collections.unmodifiableSet(steps); + } + + @Override + public Protocol getProtocol() { + return p04; + } + + @Override + public Node getEditor(WorkflowStep step) { + Node result = null; + switch (step) { + case DATASET: { + try { + result = new P04Editor(session); + } catch (IntrospectionException ex) { + Rhomeo.LOGGER.log(Level.WARNING, "Cannot initialize edition panel for P04", ex); + } + } + break; + case PROCESS: + case RESULT: + throw new IllegalStateException("Queried step is invalid for this protocol wizard !"); + } + + return result; + } + + @Override + public Set getStepsToIgnore() { + return toIgnore; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P06Editor.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P06Editor.java new file mode 100644 index 0000000..a4df888 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/P06Editor.java @@ -0,0 +1,161 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.ui.StatementEditor; +import fr.cenra.rhomeo.api.ui.StatementEditorSpi; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.protocol.p06.Habitat; +import fr.cenra.rhomeo.protocol.p06.OdonataStatement; +import java.util.Arrays; +import java.util.List; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.util.StringConverter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class P06Editor extends AbstractCustomStatementEditor { + + @FXML + private ComboBox uiHabitat; + + public P06Editor() { + super(OdonataStatement.class); + Rhomeo.loadFXML(this, P06Editor.class); + + Rhomeo.initComboBox(uiHabitat, FXCollections.observableList(Arrays.asList(Habitat.values()))); + uiHabitat.valueProperty().addListener(this::habitatChanged); + uiHabitat.setConverter(new StringConverter() { + @Override + public String toString(Habitat object) { + if (object == null) + return null; + return new StringBuilder(object.getCode()).append(" - ").append(object.getValue()).toString(); + } + + @Override + public Habitat fromString(String string) { + if (string == null || (string = string.trim()).isEmpty()) { + return null; + } + + return RhomeoCore.getByCode(Habitat.class, string.split("\\s")[0]).orElse(null); + } + }); + } + + @Override + protected void init(){ + super.init(); + if (uiStatementTable != null) + uiStatementTable.sortColumns(Arrays.asList("cd_nom", "behavior")); + } + + @Override + protected String[] getFieldsToIgnore() { + String[] fieldsToIgnore = super.getFieldsToIgnore(); + fieldsToIgnore = Arrays.copyOf(fieldsToIgnore, fieldsToIgnore.length +1); + fieldsToIgnore[fieldsToIgnore.length -1] = "habitat"; + return fieldsToIgnore; + } + + + @Override + protected OdonataStatement initStatement() throws ReflectiveOperationException { + final OdonataStatement data = super.initStatement(); + if (uiHabitat.getValue() instanceof Habitat) { + data.setHabitat(uiHabitat.getValue().getCode()); + } + + return data; + } + + /* + * UI LISTENERS + */ + + private void habitatChanged(final ObservableValue obs, final Habitat oldPhysio, final Habitat newPhysio) { + final String newCode = newPhysio == null? null : newPhysio.getCode(); + if (uiStatementTable == null || uiStatementTable.getItems() == null) + return; + for (int i = 0 ; i < uiStatementTable.getItems().size() ; i++) { + uiStatementTable.getItems().get(i).habitatProperty().set(newCode); + } + } + + @Override + protected void itemsChanged(final ObservableValue obs, final List oldItems, final List newItems) { + super.itemsChanged(obs, oldItems, newItems); + + if (newItems == null || newItems.isEmpty()) { + uiHabitat.setValue(null); + } else { + uiHabitat.setValue(RhomeoCore.getByCode(Habitat.class, newItems.get(0).getHabitat()).orElse(null)); + } + } + + @Component + public static class Spi implements StatementEditorSpi { + + @Autowired + Session session; + + @Override + public StatementEditor createEditor() { + return session.createPrototype(P06Editor.class); + } + + @Override + public Class getDataType() { + return OdonataStatement.class; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/PropertyColumn.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/PropertyColumn.java new file mode 100644 index 0000000..1bf19cf --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/PropertyColumn.java @@ -0,0 +1,728 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.Version; +import fr.cenra.rhomeo.api.annotations.RefersTo; +import fr.cenra.rhomeo.api.data.Reference; +import fr.cenra.rhomeo.api.data.Warning; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.data.reference.Taxref; +import fr.cenra.rhomeo.core.util.RelationResolver; +import java.beans.PropertyDescriptor; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.logging.Level; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.beans.value.WritableValue; +import javafx.collections.MapChangeListener; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TablePosition; +import javafx.scene.control.TableView; +import javafx.scene.control.Tooltip; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.Region; +import javafx.util.StringConverter; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.util.ArgumentChecks; +import org.apache.sis.util.Numbers; +import org.geotoolkit.font.FontAwesomeIcons; +import org.geotoolkit.gui.javafx.parameter.FXValueEditor; +import org.geotoolkit.gui.javafx.parameter.FXValueEditorSpi; +import org.geotoolkit.gui.javafx.util.ComboBoxCompletion; +import org.opengis.parameter.ParameterDescriptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * A column whose aim is to display and offer edition capabilities on a specific + * POJO property. The display / edition capabilities are computed using reflection. + * + * @author Alexis Manin (Geomatys) + * @param Type of object found in a row of the parent table. + */ +@Component +@Scope("prototype") +public class PropertyColumn extends TableColumn { + + static final KeyHandler KEY_HANDLER = new KeyHandler(); + + static final StringConverter BASIC_CONVERTER = new BasicConverter(); + + @Autowired + private Validator validator; + + @Autowired + RelationResolver resolver; + + /** + * Descriptor of the property to manage in the column (property extracted + * from a row item). + */ + final PropertyDescriptor descriptor; + /** + * A boolean indicating if {@link #observableGetter} implements {@link WritableValue} + * interface. + */ + final boolean isWritableValue; + /** + * A method extracted from input {@link PropertyDescriptor} to write target + * property. + */ + final Method writeMethod; + /** + * A method extracted from input {@link PropertyDescriptor} to read target + * property. + */ + final Method readMethod; + /** + * A method with no parameter, whose aim is to send back an observable + * object on target property. + */ + final Method observableGetter; + /** + * An annotation extracted from getters to know if the property to manage + * is referring to another object. + */ + final RefersTo referent; + + public final SimpleObjectProperty editorProperty = new SimpleObjectProperty<>(); + + // Hack for issue #75 + final boolean isSpecies; + + /** + * A weak bound to the converter used for transformation of column values + * in text. We use weak reference here mainly because of application + * {@link Reference} which are loaded asynchronously (user can change used + * version, etc.). Also, it avoid to keep a (possibly) consequent memory + * chunk when not needed. + */ + private WeakReference textConverterRef; + + public PropertyColumn(final PropertyDescriptor desc, final Method observableGetter) { + super(); + ArgumentChecks.ensureNonNull("Property descriptor", desc); + Session.getInstance().injectDependencies(this); + descriptor = desc; + writeMethod = desc.getWriteMethod(); + readMethod = desc.getReadMethod(); + this.observableGetter = observableGetter; + /* + * Analyze given parameters to determine how to handle cell content. + */ + boolean tmpIsProperty = false; + boolean isObservableValue = false; + if (observableGetter != null) { + Class observableType = observableGetter.getReturnType(); + if (WritableValue.class.isAssignableFrom(observableType)) { + tmpIsProperty = true; + } else if (ObservableValue.class.isAssignableFrom(observableType)) { + isObservableValue = true; + } + } + + isWritableValue = tmpIsProperty; + + if (isWritableValue || isObservableValue) { + setCellValueFactory((param) -> { + try { + return (ObservableValue) observableGetter.invoke(param.getValue()); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot acces observable property ".concat(desc.getName()), ex); + return null; + } + }); + + } else if (readMethod != null) { + readMethod.setAccessible(true); + setCellValueFactory((param) -> { + try { + return new SimpleObjectProperty<>(readMethod.invoke(param.getValue())); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot acces property ".concat(desc.getName()), ex); + return null; + } + }); + + } else { + throw new IllegalArgumentException("Cannot extract values from given property descriptor !"); + } + + RefersTo tmpReferent = null; + if (readMethod != null) { + tmpReferent = readMethod.getAnnotation(RefersTo.class); + } + + if (tmpReferent == null && observableGetter != null) { + tmpReferent = observableGetter.getAnnotation(RefersTo.class); + } + referent = tmpReferent; + + // Hack #75 + isSpecies = referent == null? false : referent.property().equals(Taxref.ATT_CD_NOM); + + /* When editing a property pointing on a reference, we listen on data + * context changes to update choice list when user change the version + * used. + */ + if (referent != null && Reference.class.isAssignableFrom(referent.type())) { + try { + Session.getInstance().getDataContext().getReferences().addListener((MapChangeListener.Change, ? extends Version> change) -> { + if (referent.type().equals(change.getKey())) { + initEditor(); + } + }); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.FINE, "Cannot listen on data context changes.", e); + } + } + + initEditor(); + + setCellFactory(this::createPropertyCell); + + editableProperty().bind(editorProperty.isNotNull()); + setText(desc.getDisplayName()); + + String remarks = desc.getShortDescription(); + if (remarks != null && !remarks.equals(desc.getDisplayName()) && !(remarks = remarks.trim()).isEmpty()) { + final Label label = new Label(FontAwesomeIcons.ICON_INFO_CIRCLE); + label.setTooltip(new Tooltip(remarks)); + label.setStyle("-fx-font-family: FontAwesome; -fx-font-size:16;"); + setGraphic(label); + } + + // # 86 : Compare species using their name + if (isSpecies) { + setComparator((o1, o2) -> { + if (o1 == o2) // both null or same reference + return 0; + else if (o1 == null) + return 1; + else if (o2 == null) + return -1; + else if (o1.equals(o2)) + return 0; + + final StringConverter converter = getOrCreateTextConverter(); + final String s1 = converter.toString(o1); + final String s2 = converter.toString(o2); + if (s1 == s2) // both null or same reference + return 0; + else if (s1 == null) + return 1; + else if (s2 == null) + return -1; + + return s1.compareTo(s2); + }); + } + } + + private void initEditor() { + /* If there is writing possibility, we try to setup an editor for the + * column cells. + */ + if (isWritableValue || writeMethod != null) { + Set possibleValues = null; + if (referent != null) { + try { + possibleValues = resolver.getPossibleValues(referent); + } catch (Exception ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot load choice list for property ".concat(descriptor.getName()), ex); + } + } + + final ParameterDescriptor parameterDescriptor; + final ParameterBuilder builder = new ParameterBuilder().addName(descriptor.getName()).setRequired(false); + if (possibleValues != null) { + parameterDescriptor = builder.createEnumerated((Class) Numbers.primitiveToWrapper(descriptor.getPropertyType()), possibleValues.toArray(), null); + } else { + parameterDescriptor = builder.create(descriptor.getPropertyType(), null); + } + + Optional editor = FXValueEditorSpi.findEditor(parameterDescriptor); + if (editor.isPresent()) { + // If given editor is a combo box, we activate auto-completion on it. + final FXValueEditor tmpEditor = editor.get(); + Optional optCombo = Rhomeo.findComboBox(tmpEditor.getComponent()); + if (optCombo.isPresent()) { + ComboBox cBox = optCombo.get(); + // See ComboBoxListViewSkin + cBox.getProperties().put("comboBoxRowsToMeasureWidth", 7); + setConverter(cBox); + try { + cBox.setEditable(true); + } catch (RuntimeException e) { + // A runtime exception is launched if the editable property is already bound + // Just log and let the property bound and handled by something else. + Rhomeo.LOGGER.log(Level.FINE, e.getLocalizedMessage(), e); + } + ComboBoxCompletion.autocomplete(cBox); + // HACK : Force auto-completion to reset when starting edition. + cBox.focusedProperty().addListener((obs, oldVal, newVal) -> { + if (newVal) + cBox.fireEvent(new KeyEvent(KeyEvent.KEY_RELEASED, "", "", KeyCode.UNDEFINED, false, false, false, false)); + }); + } else { + Rhomeo.prepareSpinners(tmpEditor.getComponent()); + } + editorProperty.set(tmpEditor); + } + } + } + + private PropertyCell createPropertyCell(TableColumn column) { + return new PropertyCell(); + } + + public PropertyDescriptor getTarget() { + return descriptor; + } + + private void setConverter(ComboBox cBox) { + final StringConverter baseConverter = getOrCreateTextConverter(); + final StringConverter converter = new StringConverter() { + @Override + public String toString(Object object) { + return baseConverter.toString(object); + } + + @Override + public Object fromString(String string) { + if (string == null || string.isEmpty()) + return null; + final Object firstGuess = baseConverter.fromString(string); + if (firstGuess == null) { + for (final Object o : cBox.getItems()) { + if (Objects.equals(string, o.toString())) + return o; + } + } + + return null; + } + }; + + cBox.setConverter(converter); + } + + /** + * Create a converter for the elements of this column. + * @return A converter to display this column elements as text. Never null. + */ + private StringConverter getOrCreateTextConverter() { + StringConverter converter = textConverterRef == null? null : textConverterRef.get(); + if (converter != null) + return converter; + + if (referent != null) { + try { + final Set targets = resolver.getTargetObjects(referent); + final Function extractor = RelationResolver.acquireExtractor(referent); + final HashMap refMap = new HashMap<>(targets.size()); + for (final Object o : targets) { + refMap.put(extractor.apply(o), o.toString()); + } + + converter = new StringConverter() { + @Override + public String toString(Object object) { + if (object == null || (isSpecies && object.equals(0))) + return null; + final String str = refMap.get(object); + if (str != null) + return str; + return BASIC_CONVERTER.toString(object); + } + + @Override + public Object fromString(String string) { + if (string == null || string.isEmpty()) + return null; + + for (final Map.Entry entry : refMap.entrySet()) { + if (string.equals(entry.getValue())) { + return entry.getKey(); + } + } + + return BASIC_CONVERTER.fromString(string); + } + }; + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.FINE, "Cannot tune combobox cells.", e); + } + } + + if (converter == null) { + return BASIC_CONVERTER; + } else { + textConverterRef = new WeakReference<>(converter); + return converter; + } + } + + /** + * A cell for rendering and edition of a property. + */ + private class PropertyCell extends AutoCommitableTableCell { + + /** + * A boolean to know if we're in edition state or not. We store a local + * variable here, because when calling {@link TableCell#startEdit() }, + * the edition flag is updated AFTER item update. If we do not want to + * update the view twice on state change, we have to locally store edition + * state, then calling super.startEdit(). + */ + boolean edition = false; + + EventHandler enteredOnError; + + final ChangeListener focusListener = this::focusChanged; + + @Override + public void cancelEdit() { + edition = false; + super.cancelEdit(); + updateItem(getTableColumn().getCellData(getTableRow().getIndex()), false); + removeKeyHandling(); + } + + @Override + public void commitEdit() { + commitEdit(editorProperty.get().valueProperty().getValue()); + } + + @Override + public void commitEdit(Object newValue) { + if (isWritableValue) { + WritableValue property = (WritableValue) getTableColumn().getCellObservableValue(getTableRow().getIndex()); + property.setValue(newValue); + } else { + try { + writeMethod.invoke(getTableRow().getItem(), newValue); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot commit value !", ex); + } + } + + edition = false; + super.commitEdit(newValue); + removeKeyHandling(); + editorProperty.get().getComponent().removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER); + } + + @Override + public void startEdit() { + edition = true; // Set to true for view update. + super.startEdit(); + if (!(edition = isEditing())) + return; + + final FXValueEditor editor = editorProperty.get(); + if (editor == null || + !(isWritableValue || writeMethod != null)) { + throw new IllegalStateException("Cannot start edition on not editable cell."); + } + + // Init editor value from cell content. + editor.valueProperty().setValue(getItem()); + + addKeyHandling(); + } + + @Override + protected void updateItem(Object item, boolean empty) { + super.updateItem(item, empty); + + if (edition && !empty) { + setText(null); + Node node = editorProperty.get().getComponent(); + if (node instanceof Region) { + final Region r = (Region) node; + r.setMinSize(0, 0); + r.prefWidthProperty().bind(prefWidthProperty()); + r.prefHeightProperty().bind(prefHeightProperty()); + } + setGraphic(node); + Optional opt = Rhomeo.findComboBox(node); + if (opt.isPresent()) { + node = opt.get(); + } + + node.requestFocus(); + node.focusedProperty().addListener(focusListener); + + } else { + removeGraphic(); + if (empty) { + setText(null); + } else { + setText(getOrCreateTextConverter().toString(item)); + } + } + + checkValue(item, empty); + } + + private void focusChanged(final ObservableValue obs, final Boolean oldValue, final boolean newValue) { + if (edition && !newValue) { + commitEdit(); + } + } + + /** + * Remove the graphic attached to the cell, freeing its previously bound + * properties in the same time. + */ + private void removeGraphic() { + Node graphic = getGraphic(); + if (graphic != null) { + if (graphic instanceof Region) { + final Region r = (Region) graphic; + r.prefWidthProperty().unbind(); + r.prefHeightProperty().unbind(); + } + + Optional opt = Rhomeo.findComboBox(graphic); + if (opt.isPresent()) { + graphic = opt.get(); + } + graphic.focusedProperty().removeListener(focusListener); + } + setGraphic(null); + } + + public void checkValue(final Object value, final boolean isEmpty) { + final Object rowItem; + if (isEmpty || getTableRow() == null) { + rowItem = null; + } else { + rowItem = getTableRow().getItem(); + } + + final Set result; + if (rowItem == null) { // Empty cell + result = Collections.EMPTY_SET; + } else { + result = validator.validateValue((Class) rowItem.getClass(), + PropertyColumn.this.descriptor.getName(), + value + ); + } + + if (!result.isEmpty()) { + int warningNumber = 0; + for (final ConstraintViolation cv : result) { + if (cv.getConstraintDescriptor().getPayload().contains(Warning.class)) + warningNumber++; + } + + if (warningNumber >= result.size()) { + if (!getStyleClass().contains(Rhomeo.CSS_WARNING_FIELD)) { + getStyleClass().add(Rhomeo.CSS_WARNING_FIELD); + } + } else if (!getStyleClass().contains(Rhomeo.CSS_ERROR_FIELD)) { + getStyleClass().add(Rhomeo.CSS_ERROR_FIELD); + } + + // Activate a tootltip to give user information about what happens. + createErrorTooltip(result).ifPresent(tip -> setTooltip(tip)); + } else { + setTooltip(null); + getStyleClass().removeAll(Rhomeo.CSS_ERROR_FIELD, Rhomeo.CSS_WARNING_FIELD); + } + } + + /** + * Create a tooltip listing error message of input violations. + * @param errors Errors to display. + * @return An empty optional if input list was null or empty. A tooltip + * with input error messages otherwise. + */ + private Optional createErrorTooltip(final Collection errors) { + if (errors == null || errors.isEmpty()) + return Optional.empty(); + final String text; + if (errors.size() == 1) { + text = errors.iterator().next().getMessage(); + } else { + final Iterator it = errors.iterator(); + final StringBuilder builder = new StringBuilder("∙ "); + if (it.hasNext()) + builder.append(it.next().getMessage()); + while (it.hasNext()) { + builder.append(System.lineSeparator()).append("∙ ").append(it.next().getMessage()); + } + text = builder.toString(); + } + + final Tooltip tip = new Tooltip(text); + tip.getStyleClass().add("info-popup"); + + return Optional.of(tip); + } + + /** + * Set the {@link #KEY_HANDLER} to listen on key events on the given + * cell. The event listener is meant to listen for actions on a cell + * being in edition mode. + * + * Note : If the handler was already listening on another cell, its + * unregistered before setting input cell. + * + * @param target The table cell to activate Key event handling on. + */ + private void addKeyHandling() { + if (KEY_HANDLER.target != null) { + removeKeyHandling(); + } + + KEY_HANDLER.target = this; + addEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER); + addEventFilter(KeyEvent.ANY, KEY_HANDLER); + editorProperty.get().getComponent().addEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER); + } + + /** + * Set the {@link #KEY_HANDLER} to stop listening on given cell. + * + * @param target The cell to deactivate key event handling for. + */ + private void removeKeyHandling() { + if (KEY_HANDLER != null && KEY_HANDLER.target != null) { + KEY_HANDLER.target.removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER); + KEY_HANDLER.target.removeEventFilter(KeyEvent.ANY, KEY_HANDLER); + editorProperty.get().getComponent().removeEventHandler(KeyEvent.KEY_RELEASED, KEY_HANDLER); + KEY_HANDLER.target = null; + } + } + } + + /** + * Listens on keyboard key release on a given table cell. Default listeners are : + * - ENTER : commit edit + * - TAB : commit edit then edit next column. If no more column is available, + * start editing first column of the next row. + * - ESC : cancel edit. + */ + static class KeyHandler implements EventHandler { + + AutoCommitableTableCell target; + + @Override + public void handle(KeyEvent evt) { + if (target == null) + return; + + if (KeyEvent.KEY_RELEASED.equals(evt.getEventType())) { + switch (evt.getCode()) { + case ENTER: + target.commitEdit(); + evt.consume(); + break; + case TAB: + final TableView tableView = target.getTableView(); + TablePosition currentCell = tableView.getEditingCell(); + if (currentCell == null) + currentCell = new TablePosition(tableView, 0, null); + target.commitEdit(); + Rhomeo.editNextCell(currentCell); + evt.consume(); + break; + case ESCAPE: + target.cancelEdit(); + evt.consume(); + } + } else { + // Filter events + switch (evt.getCode()) { + case TAB: + evt.consume(); + } + } + } + } + + private static class BasicConverter extends StringConverter { + + @Override + public String toString(Object object) { + if (object instanceof Geometry) { + return "Objet trop complexe pour affichage"; + } else if (object != null && Numbers.isFloat(object.getClass())) { + return RhomeoCore.SECOND_DECIMAL_FORMAT.format(object); + } else { + return String.valueOf(object); + } + } + + @Override + public Object fromString(String string) { + return null; + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/StatementTable.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/StatementTable.java new file mode 100644 index 0000000..f82b719 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/edition/StatementTable.java @@ -0,0 +1,60 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.edition; + +import fr.cenra.rhomeo.api.data.Statement; +import java.beans.IntrospectionException; +import javafx.collections.transformation.SortedList; + +/** + * + * @author Alexis Manin (Geomatys) + * @param Type of row items. + */ +public class StatementTable extends ObjectTable { + + public StatementTable(Class objectType, String... blackList) throws IntrospectionException { + super(objectType, blackList); + itemsProperty().addListener((obs, oldValue, newValue) -> { + if (newValue instanceof SortedList) { + ((SortedList)newValue).comparatorProperty().bind(comparatorProperty()); + } + }); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXGeoReferentialTable.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXGeoReferentialTable.java new file mode 100644 index 0000000..a5852bf --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXGeoReferentialTable.java @@ -0,0 +1,282 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p08; + +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.data.reference.GeoReferential; +import fr.cenra.rhomeo.core.data.reference.GeoReferentials; +import java.util.ArrayList; +import java.util.StringJoiner; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.geotoolkit.gui.javafx.util.FXTableView; +import org.geotoolkit.gui.javafx.util.TaskManager; + +/** + * Table to download referential data for a site. + * + * @author Johann Sorel (Geomatys) + */ +public class FXGeoReferentialTable extends VBox { + + private static final Image CHECKED = new Image("/fr/cenra/rhomeo/fx/p08/u74.png"); + + private final GeoReferentials geoRef; + private final Site site; + private final TableView table = new FXTableView<>(); + private final Button downloadButton = new Button("Télécharger"); + private final ProgressIndicator progress = new ProgressIndicator(); + private final String[] requiredTypes; + + final TableColumn colAvailable; + + public FXGeoReferentialTable(GeoReferentials geoRef,Site site, String ... requieredTypes){ + this.geoRef = geoRef; + this.site = site; + this.requiredTypes = requieredTypes; + + progress.setProgress(-1); + progress.setVisible(false); + progress.setPrefSize(32, 32); + progress.setMinSize(32, 32); + + downloadButton.getStyleClass().add("create-button"); + downloadButton.setStyle("-fx-text-fill: #FFFFFF;"); + downloadButton.managedProperty().bind(downloadButton.visibleProperty()); + downloadButton.setPadding(new Insets(5, 20, 5, 20)); + final HBox box = new HBox(progress,downloadButton); + box.setPadding(new Insets(5, 5, 5, 5)); + box.setAlignment(Pos.CENTER_RIGHT); + getChildren().addAll(table, box); + + //table structure + final TableColumn colAnnees = new TableColumn<>("Années disponibles"); + colAvailable = new TableColumn<>("Années téléchargées sur le poste"); + final TableColumn colDownload = new TableColumn<>("Années à télécharger"); + colAnnees.setCellValueFactory(param -> new SimpleIntegerProperty(param.getValue().getYear()).asObject()); + colAvailable.setCellValueFactory((TableColumn.CellDataFeatures param) -> param.getValue().completed(requiredTypes)); + colAvailable.setCellFactory((TableColumn param) -> new AvailableCell()); + colDownload.setCellValueFactory((TableColumn.CellDataFeatures param) -> param.getValue().toDownloadProperty()); + colDownload.setCellFactory((TableColumn param) -> new DownloadCell()); + + table.getColumns().addAll(colAnnees, colAvailable, colDownload); + table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + downloadButton.setOnAction(this::downloadRef); + + updateRefTable(); + } + + private void updateRefTable(){ + final Task> update = new Task>() { + @Override + protected ObservableList call() throws Exception { + updateTitle("Mise à jour des références"); + final ObservableList referentials = geoRef.getReferentials(site); + //remove referentials which don't have all required data + for (int i = referentials.size() - 1; i >= 0; i--) { + if (!referentials.get(i).areAvailable(requiredTypes)) { + referentials.remove(i); + } + } + return referentials; + } + }; + + update.setOnSucceeded(evt -> Platform.runLater(() -> { + table.setItems(update.getValue()); + })); + + update.setOnFailed(evt -> Platform.runLater(() -> { + final Throwable ex = evt.getSource().getException(); + table.setPlaceholder(new Label(ex.getLocalizedMessage())); + table.setItems(FXCollections.emptyObservableList()); + RhomeoCore.LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex); + })); + + progress.visibleProperty().bind(update.runningProperty()); + TaskManager.INSTANCE.submit(update); + } + + private void downloadRef(ActionEvent event) { + + final Task task = new Task() { + @Override + protected Object call() throws Exception { + int selectedYears = 0; + final ArrayList failedYears = new ArrayList(); + for (GeoReferential ref : table.getItems()) { + if (ref.toDownloadProperty().get()) { + selectedYears++; + updateTitle("Téléchargement ".concat(String.valueOf(ref.getYear()))); + try { + geoRef.downloadReferential(site, ref.getYear()); + } catch (IllegalArgumentException e) { + failedYears.add(ref.getYear()); + } + } + } + + if (selectedYears < 1) { + Platform.runLater(() -> { + final Alert alert = new Alert(Alert.AlertType.WARNING, "Aucune année séléctionnée pour le téléchargement.", ButtonType.CLOSE); + alert.setResizable(true); + alert.show(); + }); + + } else if (!failedYears.isEmpty()) { + final String msg; + if (failedYears.size() < selectedYears) { + final StringJoiner builder = new StringJoiner(", ", "Aucun référentiel n'est disponible pour la zone demandée en ", "."); + failedYears.stream() + .map(year -> String.valueOf(year)) + .forEach(year -> builder.add(year)); + msg = builder.toString(); + } else { + msg = "Aucun référentiel n'est disponible pour les années demandées"; + } + Platform.runLater(() -> { + final Alert alert = new Alert(Alert.AlertType.WARNING, msg, ButtonType.CLOSE); + alert.setResizable(true); + alert.show(); + }); + } + return true; + } + }; + + progress.visibleProperty().bind(task.runningProperty()); + downloadButton.visibleProperty().bind(task.runningProperty().not()); + // TODO : display message on illegal argument exception : no data available. + final EventHandler e = (EventHandler) (Event event1) -> updateRefTable(); + + task.setOnSucceeded(e); + task.setOnFailed(evt -> { + if (evt.getSource().getException() instanceof IllegalArgumentException) { + final Alert alert = new Alert(Alert.AlertType.WARNING, "Aucun référentiel n'est disponible pour la zone demandée en ", ButtonType.CLOSE); + alert.setResizable(true); + alert.show(); + } + e.handle(evt); + }); + task.setOnCancelled(e); + TaskManager.INSTANCE.submit(task); + } + + public ObservableList getItems() { + return table.getItems(); + } + + private static class AvailableCell extends TableCell{ + + public AvailableCell() { + } + + @Override + protected void updateItem(Boolean item, boolean empty) { + super.updateItem(item, empty); + if(Boolean.TRUE.equals(item)){ + setGraphic(new ImageView(CHECKED)); + }else{ + setText(null); + setGraphic(null); + } + } + } + + private class DownloadCell extends TableCell { + + private final CheckBox checkBox = new CheckBox(); + + private DownloadCell() { + setGraphic(checkBox); + + checkBox.setOnAction((ActionEvent event) -> { + TableRow row = tableRowProperty().getValue(); + if (row == null) + return; + final GeoReferential r = (GeoReferential) row.getItem(); + if (r != null) { + r.toDownloadProperty().set(checkBox.isSelected()); + } + }); + } + + @Override + protected void updateItem(Boolean item, boolean empty) { + super.updateItem(item, empty); + + checkBox.setVisible(!empty); + checkBox.setSelected(Boolean.TRUE.equals(item)); + + TableRow row = tableRowProperty().getValue(); + if (row == null) + return; + final GeoReferential r = (GeoReferential) row.getItem(); + if (r != null) { + checkBox.setVisible(!colAvailable.getCellData(r)); + } + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08DatasetPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08DatasetPane.java new file mode 100644 index 0000000..71fe302 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08DatasetPane.java @@ -0,0 +1,220 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p08; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.core.BeanUtils; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.data.county.CountyRepository; +import fr.cenra.rhomeo.core.data.reference.GeoReferential; +import fr.cenra.rhomeo.core.data.reference.GeoReferentials; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import fr.cenra.rhomeo.protocol.p08.I12; +import java.awt.Color; +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; +import java.util.stream.Collectors; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javax.annotation.PostConstruct; +import org.geotoolkit.data.FeatureStoreUtilities; +import org.geotoolkit.data.bean.BeanFeature; +import org.geotoolkit.display2d.GO2Utilities; +import org.geotoolkit.feature.Feature; +import org.geotoolkit.feature.FeatureBuilder; +import org.geotoolkit.gui.javafx.render2d.FXMapFrame; +import org.geotoolkit.map.FeatureMapLayer; +import org.geotoolkit.map.MapBuilder; +import org.geotoolkit.map.MapContext; +import org.geotoolkit.map.MapItem; +import org.geotoolkit.map.MapLayer; +import org.geotoolkit.style.MutableStyleFactory; +import org.opengis.feature.Property; +import org.opengis.filter.FilterFactory2; +import org.opengis.filter.expression.PropertyName; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Johann Sorel (Geomatys) + */ +@Component +@Scope("prototype") +public class FXP08DatasetPane extends VBox { + + @Autowired + private Session session; + + @Autowired + private GeoReferentials geoRef; + + @Autowired + @Qualifier(I12.NAME) + private Indicator indicator; + + @FXML + private Button next; + + @FXML + private BorderPane tableSpace; + + @Autowired(required = false) + private CountyRepository countyRepository; + + private FXGeoReferentialTable table; + + public FXP08DatasetPane() { + Rhomeo.loadFXML(this); + } + + @PostConstruct + private void postConstruct() { + table = new FXGeoReferentialTable(geoRef, session.getDataContext().getSite(), + GeoReferentials.TYPE_ZONE_HYDRO, + GeoReferentials.TYPE_TACHE_ARTIF, + GeoReferentials.TYPE_TACHE_URBAINE); + tableSpace.setCenter(table); + } + + @FXML + void nextStep(ActionEvent event) { + + //check there is at least one year of data available + int nbAvailable = 0; + final ObservableList items = table.getItems(); + for(GeoReferential ref : items){ + //we need zone hydro,tache artif and tache_urbaine + if(ref.zoneHydroLocal().get() + && ref.tacheArtifLocal().get() + && ref.tacheUrbaineLocal().get()) nbAvailable++; + } + + if (nbAvailable <= 0) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : Aucun réferentiel téléchargé."); + return; + } + + final ProcessContext ctx = new ProcessContext(session.getDataset(), session.getDataContext()); + ctx.getIndicators().add(indicator); + session.setProcessContext(ctx); + + if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données."); + } + } + + @FXML + private void showMap() throws Exception { + final Site currentSite = session.getDataContext().getSite(); + // Should not be null when called here + if (currentSite == null) { + throw new IllegalArgumentException("Currently selected site was null here, but shouldn't be"); + } + + // If counties are available, we display them on the map. + final MapContext context = MapBuilder.createContext(); + final MutableStyleFactory sf = GO2Utilities.STYLE_FACTORY; + + if (countyRepository != null) { + try { + context.layers().add(MapBuilder.createFeatureLayer(countyRepository.getFeatures(), sf.style(sf.lineSymbolizer()) + )); + } catch (Exception e) { + Rhomeo.LOGGER.log(Level.WARNING, "Cannot show county layer", e); + } + } + + final BeanFeature feature = new BeanFeature(currentSite, BeanUtils.createMapping(Site.class, null)); + final MapLayer layer = MapBuilder.createFeatureLayer(FeatureStoreUtilities.collection(feature), sf.style(sf.polygonSymbolizer(null, sf.fill(Color.red), null))); + layer.setName("Site"); + context.layers().add(layer); + context.setAreaOfInterest(layer.getBounds()); + + Geometry buffer = GeometryUtils.computeBuffer(currentSite.getGeometry()); + final FeatureBuilder fb = new FeatureBuilder(feature.getType()); + for (final Property p : feature.getProperties()) { + if (p.getValue() instanceof Geometry) + fb.setPropertyValue(p.getName(), buffer); + else + fb.setPropertyValue(p.getName(), p.getValue()); + } + final Feature bufferFeature = fb.buildFeature("buffer"); + final FeatureMapLayer bufferLayer = MapBuilder.createFeatureLayer(FeatureStoreUtilities.collection(bufferFeature)); + bufferLayer.setName("Buffer"); + context.layers().add(1, bufferLayer); + + final FilterFactory2 ff = GO2Utilities.FILTER_FACTORY; + final PropertyName geomProp = ff.property("geom"); + + MapItem zoneHydro = geoRef.getWFSLayers(Optional.of(ff.intersects(geomProp, ff.literal(buffer))), GeoReferentials.TYPE_ZONE_HYDRO); + zoneHydro = zoneHydro.items().get(0); + MapItem wfsLayers = geoRef.getWFSLayers(Optional.of(ff.bbox(null, ((MapLayer)zoneHydro).getBounds())), GeoReferentials.TYPE_TACHE_ARTIF, GeoReferentials.TYPE_TACHE_URBAINE, GeoReferentials.TYPE_RPG); + wfsLayers.setName("BBox filtered"); + context.items().add(0, wfsLayers); + + final List geomCollection = ((FeatureMapLayer)zoneHydro).getCollection().stream() + .map(feat -> feat.getDefaultGeometryProperty().getValue()) + .filter(geom -> geom instanceof Geometry) + .map(geom -> (Geometry) geom) + .collect(Collectors.toList()); + Geometry filterGeom = GO2Utilities.JTS_FACTORY.createGeometryCollection(geomCollection.toArray(new Geometry[geomCollection.size()])).union(); + wfsLayers = geoRef.getWFSLayers(Optional.of(ff.intersects(geomProp, ff.literal(filterGeom))), GeoReferentials.TYPE_TACHE_ARTIF, GeoReferentials.TYPE_TACHE_URBAINE, GeoReferentials.TYPE_RPG); + wfsLayers.setName("Polygon filtered"); + context.items().add(1, wfsLayers); + + context.items().add(0, zoneHydro); + + FXMapFrame.show(context); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08ProcessPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08ProcessPane.java new file mode 100644 index 0000000..b7111e9 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/FXP08ProcessPane.java @@ -0,0 +1,310 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p08; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.util.ExportUtils; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import fr.cenra.rhomeo.protocol.p08.I12; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import fr.cenra.rhomeo.api.process.Process; +import fr.cenra.rhomeo.fx.p09.FXP09ProcessPane; +import fr.cenra.rhomeo.core.data.reference.GeoReferential; +import fr.cenra.rhomeo.core.data.reference.GeoReferentials; +import fr.cenra.rhomeo.protocol.p08.I12Process; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.BitSet; +import java.util.Optional; +import java.util.prefs.Preferences; +import javafx.application.Platform; +import javafx.beans.property.SimpleObjectProperty; +import javafx.scene.control.ChoiceDialog; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.internal.GeotkFX; + +/** + * + * @author Johann Sorel (Geomatys) + */ +@Component +@Scope("prototype") +public class FXP08ProcessPane extends VBox { + + @Autowired + private Session session; + + @Autowired + private GeoReferentials geoRef; + + @Autowired + @Qualifier(I12.NAME) + private Indicator indicator; + + @FXML + private Button next; + + @FXML + private Button uiExport; + + @FXML + private HBox yearBoxes; + + private final List checkBoxes = new ArrayList<>(); + + private final SimpleObjectProperty>> globalTask = new SimpleObjectProperty<>(); + + private BitSet activatedYears; + + private final Preferences prefs; + + public FXP08ProcessPane(){ + Rhomeo.loadFXML(this); + prefs = Preferences.userNodeForPackage(FXP08ProcessPane.class); + } + + @PostConstruct + private void postConstruct() { + //liste available referential state for this site + final ObservableList refs; + try { + refs = geoRef.getLocalReferentials(session.getDataContext().getSite()) + .filtered(ref -> ref.hasLocalTypes( + GeoReferentials.TYPE_ZONE_HYDRO, + GeoReferentials.TYPE_TACHE_ARTIF, + GeoReferentials.TYPE_TACHE_URBAINE) + ) + .sorted(); + } catch (IllegalArgumentException | IOException ex) { + // If we cannot load georeferentials, it's no use to go further. + GeotkFX.newExceptionDialog("Impossible de déterminer la liste des réferentiels disponibles. La lecture sur le système a échouée.", ex).show(); + session.requestWorkflowStep(WorkflowStep.DATASET); + return; + } + + final ByteBuffer yearsAsBytes = ByteBuffer.wrap(new byte[refs.size() * (Integer.SIZE / Byte.SIZE)]); + final IntBuffer years = yearsAsBytes.asIntBuffer(); + for (final GeoReferential ref : refs) { + years.put(ref.getYear()); + } + years.position(0); + + /* Restore selection state from serialized bitset if possible. To + * determine if restoration is possible, we ensure there's data stored + * in preferences, and also that they match current edited site / years. + * Otherwise, we set a new bitset, with default selection on types 17 + * and 18. + * We do the same with years, except that by default, they're all selected. + */ + final String siteName = session.getDataContext().getSite().getName(); + final boolean restore = prefs.get(FXP09ProcessPane.SITE_NAME, "").equals(siteName) + && ByteBuffer.wrap(prefs.getByteArray(FXP09ProcessPane.AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years); + + final byte[] yearSelection; + if (restore && (yearSelection = prefs.getByteArray(FXP09ProcessPane.YEAR_SELECTION, null)) != null) { + activatedYears = BitSet.valueOf(yearSelection); + } else { + activatedYears = new BitSet(refs.size()); + activatedYears.set(0, activatedYears.length()); + } + + // A new session is starting. We initialize preferences for it. + if (!restore) { + prefs.put(FXP09ProcessPane.SITE_NAME, siteName); + prefs.putByteArray(FXP09ProcessPane.AVAILABLE_YEARS, yearsAsBytes.array()); + } + + for (GeoReferential ref : refs) { + final CheckBox check = new CheckBox(String.valueOf(ref.getYear())); + final int index = checkBoxes.size(); + check.setSelected(activatedYears.get(index)); + check.selectedProperty().addListener((obs, oldValue, newValue) -> { + yearSelectionChanged(index, newValue == null? false : newValue); + }); + yearBoxes.getChildren().add(check); + checkBoxes.add(check); + } + } + + private void yearSelectionChanged(final int index, final boolean value) { + activatedYears.set(index, value); + prefs.putByteArray(FXP09ProcessPane.YEAR_SELECTION, activatedYears.toByteArray()); + } + + @FXML + void exportGeometries(ActionEvent event) { + final Site site = session.getDataContext().getSite(); + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, zipFilter); + + if (outputFile == null) { + return; + } + + int tmpSelectedYear = Integer.MIN_VALUE; + for(final CheckBox c : checkBoxes) { + if (c.isSelected()) { + tmpSelectedYear = Math.max(tmpSelectedYear, Integer.parseInt(c.getText())); + } + } + + final int selectedYear = tmpSelectedYear; + + final ChoiceDialog dialog = new ChoiceDialog<>(ExportUtils.ExportType.GJSON, ExportUtils.ExportType.values()); + dialog.setTitle("Format d'export"); + dialog.setHeaderText("Choisissez un format de sortie"); + dialog.setResizable(true); + dialog.showAndWait().ifPresent(type -> { + Task t = TaskManager.INSTANCE.submit("Export", () -> { + final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry()); + final Optional territory; + if (selectedYear > Integer.MIN_VALUE) { + territory = geoRef.getLocalReferential(site, selectedYear).unionIntersecting(GeoReferentials.TYPE_ZONE_HYDRO, site.getGeometry()); + } else { + territory = Optional.empty(); + } + ExportUtils.exportSiteAndBuffer(outputFile, site, buffer, territory, type); + return true; + }); + uiExport.disableProperty().unbind(); + uiExport.disableProperty().bind(t.runningProperty()); + }); + } + + @FXML + void nextStep(ActionEvent event) { + + //check there is at least one year of data available + int nbAvailable = 0; + for(CheckBox c : checkBoxes){ + if(c.isSelected()) nbAvailable++; + } + + if (nbAvailable==0) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : veuillez sélectionner une année."); + return; + } + + final Computer computer = new Computer(); + globalTask.set(computer); + + computer.stateProperty().addListener(obs -> { + if (computer.isDone()) { + setDisable(false); + switch (computer.getState()) { + case FAILED: + Rhomeo.showAlert(Alert.AlertType.ERROR, "process-failed"); + break; + case SUCCEEDED: + session.setResults(computer.getValue()); + globalTask.set(null); + session.requestWorkflowStep(WorkflowStep.RESULT); + break; + } + } + }); + + TaskManager.INSTANCE.submit(computer); + + } + + + private class Computer extends Task> { + + private final List processes = new ArrayList<>(); + + @Override + protected Set call() throws Exception { + processes.addAll(indicator.createProcesses(session.getProcessContext())); + final I12Process process = (I12Process) processes.get(0); + + //configure selected years + for(CheckBox c : checkBoxes){ + if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText())); + } + + updateProgress(-1, -1); + updateTitle("I12"); + + Platform.runLater(() -> { + process.titleProperty().addListener((obs) -> updateTitle(process.getTitle())); + process.messageProperty().addListener((obs) -> updateTitle(process.getMessage())); + process.workDoneProperty().addListener((obs) -> updateProgress(process.getWorkDone(), process.getTotalWork())); + }); + + process.run(); + return (Set) process.get(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (final Process p : processes) { + p.cancel(true); + } + return super.cancel(mayInterruptIfRunning); + } + } + +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/P08ProtocolEditorSpi.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/P08ProtocolEditorSpi.java new file mode 100644 index 0000000..897eb6c --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p08/P08ProtocolEditorSpi.java @@ -0,0 +1,88 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p08; + +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.protocol.p08.P08; +import java.util.Collections; +import java.util.Set; +import javafx.scene.Node; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * Protocol 08 editor spi. + * + * @author Johann Sorel (Geomatys) + */ +@Component +public class P08ProtocolEditorSpi implements ProtocolEditorSpi{ + + @Autowired + private Session session; + + @Autowired + @Qualifier(P08.NAME) + private Protocol protocol; + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public Node getEditor(WorkflowStep step) { + switch(step){ + case DATASET : return session.getBean(FXP08DatasetPane.class); + case PROCESS : return session.getBean(FXP08ProcessPane.class); + default : return null; + } + } + + @Override + public Set getStepsToIgnore() { + return Collections.EMPTY_SET; + } + +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09DatasetPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09DatasetPane.java new file mode 100644 index 0000000..961f722 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09DatasetPane.java @@ -0,0 +1,125 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p09; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.process.ProcessContext; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.fx.p08.FXGeoReferentialTable; +import fr.cenra.rhomeo.core.data.reference.GeoReferential; +import fr.cenra.rhomeo.core.data.reference.GeoReferentials; +import fr.cenra.rhomeo.protocol.p09.I13; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javax.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Johann Sorel (Geomatys) + */ +@Component +@Scope("prototype") +public class FXP09DatasetPane extends VBox { + + @Autowired + private Session session; + @Autowired + private GeoReferentials geoRef; + + @Autowired + @Qualifier(I13.NAME) + private Indicator indicator; + + @FXML + private Button next; + @FXML + private BorderPane tableSpace; + + private FXGeoReferentialTable table; + + public FXP09DatasetPane() { + Rhomeo.loadFXML(this); + } + + @PostConstruct + private void postConstruct(){ + table = new FXGeoReferentialTable(geoRef, session.getDataContext().getSite(), + GeoReferentials.TYPE_ZONE_HYDRO, + GeoReferentials.TYPE_RPG); + tableSpace.setCenter(table); + } + + @FXML + void nextStep(ActionEvent event) { + + //check there is at least one year of data available + int nbAvailable = 0; + final ObservableList items = table.getItems(); + for(GeoReferential ref : items){ + //we need zone hydro,tache artif and tache_urbaine + if(ref.zoneHydroLocal().get() + && ref.rpgLocal().get()) nbAvailable++; + } + + if (nbAvailable==0) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : la saisie n'est pas valide."); + return; + } + + final ProcessContext ctx = new ProcessContext(session.getDataset(), session.getDataContext()); + ctx.getIndicators().add(indicator); + session.setProcessContext(ctx); + + if (!session.requestWorkflowStep(WorkflowStep.PROCESS)) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante. Veuillez vérifier votre lot de données."); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09ProcessPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09ProcessPane.java new file mode 100644 index 0000000..9390672 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/FXP09ProcessPane.java @@ -0,0 +1,383 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p09; + +import com.vividsolutions.jts.geom.Geometry; +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Site; +import fr.cenra.rhomeo.api.process.Indicator; +import fr.cenra.rhomeo.api.result.Index; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.core.data.reference.GeoReferential; +import fr.cenra.rhomeo.core.data.reference.GeoReferentials; +import fr.cenra.rhomeo.core.util.GeometryUtils; +import fr.cenra.rhomeo.protocol.p09.I13; +import fr.cenra.rhomeo.protocol.p09.I13Process; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javax.annotation.PostConstruct; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import fr.cenra.rhomeo.api.process.Process; +import fr.cenra.rhomeo.core.util.ExportUtils; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.BitSet; +import java.util.Optional; +import java.util.prefs.Preferences; +import javafx.application.Platform; +import javafx.scene.control.ChoiceDialog; +import javafx.scene.layout.GridPane; +import org.geotoolkit.internal.GeotkFX; + +/** + * + * @author Johann Sorel (Geomatys) + */ +@Component +@Scope("prototype") +public class FXP09ProcessPane extends VBox { + + public static final String SITE_NAME = "site_name"; + public static final String AVAILABLE_YEARS = "available_years"; + private static final String TYPE_SELECTION = "type_selection"; + public static final String YEAR_SELECTION = "year_selection"; + + private static final String[] TYPES = new String[]{ + null, + "01 - Blé tendre", + "02 - Maïs grain et ensilage", + "03 - Orge", + "04 - Autres céréales", + "05 - Colza", + "06 - Tournesol", + "07 - Autres oleagineux", + "08 - Protéagineux", + "09 - Plantes à fibres", + "10 - Semences", + "11 - Gel (surfaces gelées sans production)", + "12 - Gel industriel", + "13 - Autres gels", + "14 - Riz", + "15 - Légumineuses à grains", + "16 - Fourrage", + "17 - Estives landes", + "18 - Prairies permanentes", + "19 - Prairies temporaires", + "20 - Vergers", + "21 - Vignes", + "22 - Fruits à coque", + "23 - Oliviers", + "24 - Autres cultures industrielles", + "25 - Légumes-fleurs", + "26 - Canne à sucre", + "27 - Arboriculture", + "28 - Divers" + }; + + @Autowired + private Session session; + @Autowired + private GeoReferentials geoRef; + @Autowired + @Qualifier(I13.NAME) + private Indicator indicator; + + @FXML + private Button next; + + @FXML + private Button uiExport; + + @FXML + private HBox yearBoxes; + @FXML + private GridPane typeBoxes; + + private final List yearCheckBoxes = new ArrayList<>(); + private final List typeCheckBoxes = new ArrayList<>(); + + private final SimpleObjectProperty>> globalTask = new SimpleObjectProperty<>(); + + private BitSet activatedYears; + private BitSet activatedTypes; + + private final Preferences prefs; + + public FXP09ProcessPane() { + Rhomeo.loadFXML(this); + prefs = Preferences.userNodeForPackage(FXP09ProcessPane.class); + } + + @PostConstruct + private void postConstruct() { + + //liste available referential state for this site + final ObservableList refs; + try { + refs = geoRef.getLocalReferentials(session.getDataContext().getSite()) + .filtered(ref -> ref.hasLocalTypes(GeoReferentials.TYPE_ZONE_HYDRO, GeoReferentials.TYPE_RPG)) + .sorted(); + } catch (IllegalArgumentException | IOException ex) { + // If we cannot load georeferentials, it's no use to go further. + GeotkFX.newExceptionDialog("Impossible de déterminer la liste des réferentiels disponibles. La lecture sur le système a échouée.", ex).show(); + session.requestWorkflowStep(WorkflowStep.DATASET); + return; + } + + final ByteBuffer yearsAsBytes = ByteBuffer.wrap(new byte[refs.size() * (Integer.SIZE / Byte.SIZE)]); + final IntBuffer years = yearsAsBytes.asIntBuffer(); + for (final GeoReferential ref : refs) { + years.put(ref.getYear()); + } + years.position(0); + + /* Restore selection state from serialized bitset if possible. To + * determine if restoration is possible, we ensure there's data stored + * in preferences, and also that they match current edited site / years. + * Otherwise, we set a new bitset, with default selection on types 17 + * and 18. + * We do the same with years, except that by default, they're all selected. + */ + final String siteName = session.getDataContext().getSite().getName(); + final boolean restore = prefs.get(SITE_NAME, "").equals(siteName) + && ByteBuffer.wrap(prefs.getByteArray(AVAILABLE_YEARS, new byte[0])).asIntBuffer().equals(years); + + final byte[] yearSelection; + if (restore && (yearSelection = prefs.getByteArray(YEAR_SELECTION, null)) != null) { + activatedYears = BitSet.valueOf(yearSelection); + } else { + activatedYears = new BitSet(refs.size()); + activatedYears.set(0, activatedYears.length()); + } + + final byte[] typeSelection; + if (restore && (typeSelection = prefs.getByteArray(TYPE_SELECTION, null)) != null) { + activatedTypes = BitSet.valueOf(typeSelection); + } else { + activatedTypes = new BitSet(TYPES.length); + activatedTypes.set(17, true); + activatedTypes.set(18, true); + activatedTypes.set(19, true); + } + + // A new session is starting. We initialize preferences for it. + if (!restore) { + prefs.put(SITE_NAME, siteName); + prefs.putByteArray(AVAILABLE_YEARS, yearsAsBytes.array()); + } + + // Init UI. For checkboxes, each time selection change, we update preferences. + for (GeoReferential ref : refs) { + final CheckBox check = new CheckBox(String.valueOf(ref.getYear())); + final int index = yearCheckBoxes.size(); + check.setSelected(activatedYears.get(index)); + check.selectedProperty().addListener((obs, oldValue, newValue) -> { + yearSelectionChanged(index, newValue == null? false : newValue); + }); + yearBoxes.getChildren().add(check); + yearCheckBoxes.add(check); + } + + double best = 0; + typeCheckBoxes.add(null); + for(int i=1;i<29;i++){ + final CheckBox box = new CheckBox(TYPES[i]); + box.setSelected(activatedTypes.get(i)); + typeCheckBoxes.add(box); + GridPane.setColumnIndex(box, (i-1)%4); + GridPane.setRowIndex(box, ((i-1)-((i-1)%4))); + typeBoxes.getChildren().add(box); + best = Math.max(best, box.getWidth()); + final int index = i; + box.selectedProperty().addListener((obs, oldValue, newValue) -> { + boxSelectionChanged(index, newValue == null? false : newValue); + }); + } + } + + private void yearSelectionChanged(final int index, final boolean value) { + activatedYears.set(index, value); + prefs.putByteArray(YEAR_SELECTION, activatedYears.toByteArray()); + } + + private void boxSelectionChanged(final int index, final boolean value) { + activatedTypes.set(index, value); + prefs.putByteArray(TYPE_SELECTION, activatedTypes.toByteArray()); + } + + @FXML + void exportGeometries(ActionEvent event) { + final Site site = session.getDataContext().getSite(); + final FileChooser.ExtensionFilter zipFilter = new FileChooser.ExtensionFilter("Archive zip", "*.zip"); + final File outputFile = Rhomeo.askOutputFilePopup(site.getName()+".zip", this, zipFilter); + + if (outputFile == null) { + return; + } + + int tmpSelectedYear = Integer.MIN_VALUE; + for(final CheckBox c : yearCheckBoxes) { + if (c.isSelected()) { + tmpSelectedYear = Math.max(tmpSelectedYear, Integer.parseInt(c.getText())); + } + } + + final int selectedYear = tmpSelectedYear; + + final ChoiceDialog dialog = new ChoiceDialog<>(ExportUtils.ExportType.GJSON, ExportUtils.ExportType.values()); + dialog.setTitle("Format d'export"); + dialog.setHeaderText("Choisissez un format de sortie"); + dialog.setResizable(true); + dialog.showAndWait().ifPresent(type -> { + Task t = TaskManager.INSTANCE.submit("Export", () -> { + final Geometry buffer = GeometryUtils.computeBuffer(site.getGeometry()); + final Optional territory; + if (selectedYear > Integer.MIN_VALUE) { + territory = geoRef.getLocalReferential(site, selectedYear).unionIntersecting(GeoReferentials.TYPE_ZONE_HYDRO, site.getGeometry()); + } else { + territory = Optional.empty(); + } + ExportUtils.exportSiteAndBuffer(outputFile, site, buffer, territory, type); + return true; + }); + uiExport.disableProperty().unbind(); + uiExport.disableProperty().bind(t.runningProperty()); + }); + } + + @FXML + void nextStep(ActionEvent event) { + + //check there is at least one year of data available + int nbAvailable = 0; + for(CheckBox c : yearCheckBoxes){ + if(c.isSelected()) nbAvailable++; + } + + if (nbAvailable==0) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de passer à l'étape suivante : veuillez sélectionner une année."); + return; + } + + final Computer computer = new Computer(); + globalTask.set(computer); + + computer.stateProperty().addListener(obs -> { + if (computer.isDone()) { + setDisable(false); + switch (computer.getState()) { + case FAILED: + Rhomeo.showAlert(Alert.AlertType.ERROR, "process-failed"); + break; + case SUCCEEDED: + session.setResults(computer.getValue()); + globalTask.set(null); + session.requestWorkflowStep(WorkflowStep.RESULT); + break; + } + } + }); + + TaskManager.INSTANCE.submit(computer); + } + + + private class Computer extends Task> { + + private final List processes = new ArrayList<>(); + + @Override + protected Set call() throws Exception { + processes.addAll(indicator.createProcesses(session.getProcessContext())); + final I13Process process = (I13Process) processes.get(0); + + //configure selected years + for(CheckBox c : yearCheckBoxes){ + if(c.isSelected())process.getYears().add(Integer.valueOf(c.getText())); + } + //configure excluded types + for(int i=1;i<29;i++){ + if(typeCheckBoxes.get(i).isSelected()){ + process.getNoCount().add(i); + } + } + + updateProgress(-1, -1); + updateTitle("I13"); + + Platform.runLater(() -> { + process.titleProperty().addListener((obs) -> updateTitle(process.getTitle())); + process.messageProperty().addListener((obs) -> updateTitle(process.getMessage())); + process.workDoneProperty().addListener((obs) -> updateProgress(process.getWorkDone(), process.getTotalWork())); + }); + + process.run(); + return (Set) process.get(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + for (final Process p : processes) { + p.cancel(true); + } + return super.cancel(mayInterruptIfRunning); + } + } + +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/P09ProtocolEditorSpi.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/P09ProtocolEditorSpi.java new file mode 100644 index 0000000..797a60d --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/p09/P09ProtocolEditorSpi.java @@ -0,0 +1,88 @@ + +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.p09; + +import fr.cenra.rhomeo.api.data.Protocol; +import fr.cenra.rhomeo.api.ui.ProtocolEditorSpi; +import fr.cenra.rhomeo.core.Session; +import fr.cenra.rhomeo.core.WorkflowStep; +import fr.cenra.rhomeo.protocol.p09.P09; +import java.util.Collections; +import java.util.Set; +import javafx.scene.Node; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +/** + * Protocol 09 editor spi. + * + * @author Johann Sorel (Geomatys) + */ +@Component +public class P09ProtocolEditorSpi implements ProtocolEditorSpi{ + + @Autowired + private Session session; + + @Autowired + @Qualifier(P09.NAME) + private Protocol protocol; + + @Override + public Protocol getProtocol() { + return protocol; + } + + @Override + public Node getEditor(WorkflowStep step) { + switch(step){ + case DATASET : return session.getBean(FXP09DatasetPane.class); + case PROCESS : return session.getBean(FXP09ProcessPane.class); + default : return null; + } + } + + @Override + public Set getStepsToIgnore() { + return Collections.EMPTY_SET; + } + +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/ConstraintViolationListCell.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/ConstraintViolationListCell.java new file mode 100644 index 0000000..1ee7077 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/ConstraintViolationListCell.java @@ -0,0 +1,83 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.validation; + + +import fr.cenra.rhomeo.api.InternationalResource; +import fr.cenra.rhomeo.core.RhomeoCore; +import java.util.Iterator; +import java.util.logging.Level; +import javafx.scene.control.ListCell; +import javax.validation.ConstraintViolation; +import javax.validation.Path; + + +/** + * A simple cell which render a constraint violation message in cell text. + * @author Alexis Manin (Geomatys) + */ +public class ConstraintViolationListCell extends ListCell { + + @Override + protected void updateItem(ConstraintViolation item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + } else { + final StringBuilder msgBuilder = new StringBuilder(); + if (item.getLeafBean() instanceof InternationalResource) { + final Iterator it = item.getPropertyPath().iterator(); + String pName = null; + while (it.hasNext()) + pName = it.next().getName(); + if (pName != null) { + try { + msgBuilder.append(InternationalResource.getResourceString((Class)item.getLeafBean().getClass(), pName, "label")) + .append(" : "); + } catch (Exception e) { + RhomeoCore.LOGGER.log(Level.FINE, "Cannot find resource ".concat(pName), e); + } + } + } + + setText(msgBuilder.append(item.getMessage()).toString()); + } + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/FXValidationPane.java b/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/FXValidationPane.java new file mode 100644 index 0000000..91f18ae --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/fx/validation/FXValidationPane.java @@ -0,0 +1,259 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.fx.validation; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.data.Dataset; +import fr.cenra.rhomeo.api.data.Warning; +import fr.cenra.rhomeo.core.Session; +import java.util.HashSet; +import java.util.ResourceBundle; +import java.util.Set; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Hyperlink; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.StackPane; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.Callback; +import javax.annotation.PostConstruct; +import javax.validation.ConstraintViolation; +import javax.validation.Validator; +import org.geotoolkit.font.FontAwesomeIcons; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class FXValidationPane extends StackPane { + + @Autowired + private Session session; + + @Autowired + private Validator validator; + + @FXML + private Label uiStateIcon; + + @FXML + private Label uiStateLabel; + + @FXML + private Button uiValidate; + + @FXML + private Hyperlink uiErrorContainer; + + @FXML + private Hyperlink uiWarningContainer; + + @FXML + private ProgressIndicator uiProgress; + + private final ListView uiErrorList; + private final ListView uiWarningList; + + private final ObservableList errors; + private final ObservableList warnings; + + private final BooleanBinding noError; + + private Stage errorStage; + private Stage warningStage; + + public FXValidationPane() { + super(); + Rhomeo.loadFXML(this, FXValidationPane.class); + errors = FXCollections.observableArrayList(); + warnings = FXCollections.observableArrayList(); + uiErrorList = new ListView<>(errors); + uiErrorList.setMaxHeight(200); + final Insets insets = new Insets(4); + uiErrorList.setPadding(insets); + uiWarningList = new ListView<>(warnings); + uiWarningList.setPadding(insets); + uiWarningList.setMaxHeight(200); + + final Callback factory = (list) -> new ConstraintViolationListCell(); + uiErrorList.setCellFactory(factory); + uiWarningList.setCellFactory(factory); + + // Update display on error detection + noError = Bindings.isEmpty(errors); + uiErrorContainer.visibleProperty().bind(noError.not()); + uiErrorContainer.managedProperty().bind(uiErrorContainer.visibleProperty()); + final BooleanBinding noWarning = Bindings.isEmpty(warnings); + uiWarningContainer.visibleProperty().bind(noWarning.not()); + uiWarningContainer.managedProperty().bind(uiWarningContainer.visibleProperty()); + noError.addListener((obs) -> { + if (session.getDataset().getItems().isEmpty()) { + uiStateIcon.setText(null); + uiStateLabel.setText(null); + } else if (noError.get()) { + uiStateIcon.setText(FontAwesomeIcons.ICON_CHECK); + uiStateIcon.setStyle("-fx-font-family: FontAwesome; -fx-font-size: 24; -fx-text-fill:#9CB35B"); + uiStateLabel.setText(ResourceBundle.getBundle(FXValidationPane.class.getCanonicalName()).getString("dataset.valid")); + } else { + uiStateIcon.setText(FontAwesomeIcons.ICON_TIMES); + uiStateIcon.setStyle("-fx-font-family: FontAwesome; -fx-font-size: 24; -fx-text-fill:#D85F70"); + uiStateLabel.setText(ResourceBundle.getBundle(FXValidationPane.class.getCanonicalName()).getString("dataset.invalid")); + } + }); + } + + @PostConstruct + private void init() { + uiValidate.disableProperty().bind(Bindings.isEmpty(session.getDataset().getItems())); + } + + @FXML + void validate(ActionEvent event) { + TaskManager.INSTANCE.submit("Validation du lot de données", () -> { + Rhomeo.fxRun(false, () -> { + setDisable(true); + uiProgress.setVisible(true); + }); + try { + Set> violations = validator.validate(session.getDataset()); + + final HashSet tmpErrors = new HashSet(); + final HashSet tmpWarnings = new HashSet(); + for (final ConstraintViolation violation : violations) { + if (findWarning(violation)) { + tmpWarnings.add(violation); + } else { + tmpErrors.add(violation); + } + } + + Rhomeo.fxRun(false, () -> { + errors.setAll(tmpErrors); + warnings.setAll(tmpWarnings); + uiStateLabel.setVisible(true); + noError.invalidate(); + }); + } finally { + Rhomeo.fxRun(false, () -> { + setDisable(false); + uiProgress.setVisible(false); + }); + } + }); + } + + private static boolean findWarning(final ConstraintViolation violation) { + for (final Object p : violation.getConstraintDescriptor().getPayload()) { + if (p == Warning.class) { + return true; + } + } + + return false; + } + + public ListView getErrorList() { + return uiErrorList; + } + + public ListView getWarningList() { + return uiWarningList; + } + + /** + * Empty this panel content. + */ + public void reset() { + warnings.clear(); + errors.clear(); + noError.invalidate(); + } + + @FXML + private void showWarnings(final ActionEvent e) { + if (warningStage == null) { + warningStage = createErrorStage("Avertissements", uiWarningList); + } + + warningStage.show(); + warningStage.requestFocus(); + } + + @FXML + private void showErrors(final ActionEvent e) { + if (errorStage == null) { + errorStage = createErrorStage("Erreurs", uiErrorList); + } + + errorStage.show(); + errorStage.requestFocus(); + } + + private Stage createErrorStage(final String title, final ListView errors) { + final Stage s = new Stage(StageStyle.UNIFIED); + if (title != null) + s.setTitle(title); + s.initModality(Modality.NONE); + if (getScene() != null) + s.initOwner(getScene().getWindow()); + + final Scene scene = new Scene(errors); + s.setScene(scene); + s.setAlwaysOnTop(false); + s.sizeToScene(); + return s; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/preferences/FXFTPConfiguration.java b/desktop/src/main/java/fr/cenra/rhomeo/preferences/FXFTPConfiguration.java new file mode 100644 index 0000000..ca0801d --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/preferences/FXFTPConfiguration.java @@ -0,0 +1,232 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.preferences; + +import fr.cenra.rhomeo.Rhomeo; +import fr.cenra.rhomeo.api.preferences.PreferenceGroup; +import fr.cenra.rhomeo.api.ui.PreferencePane; +import fr.cenra.rhomeo.core.RhomeoCore; +import fr.cenra.rhomeo.core.preferences.ftp.FTPAccess; +import fr.cenra.rhomeo.core.preferences.ftp.FTPPreferences; +import java.security.GeneralSecurityException; +import java.util.Arrays; +import java.util.logging.Level; +import javafx.application.Platform; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Alert; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.PasswordField; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.util.StringConverter; +import org.geotoolkit.gui.javafx.util.TaskManager; +import org.geotoolkit.internal.GeotkFX; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +/** + * + * @author Alexis Manin (Geomatys) + */ +@Component +@Scope("prototype") +public class FXFTPConfiguration extends BorderPane implements PreferencePane { + + @FXML + private ComboBox uiAccess; + + @Autowired + FTPPreferences prefs; + + @FXML + private TextField uiURL; + + @FXML + private Spinner uiPort; + + @FXML + private TextField uiDir; + + @FXML + private TextField uiLogin; + + @FXML + private PasswordField uiPassword; + + @FXML + private Button uiTest; + + private FTPAccess access; + + public FXFTPConfiguration() { + Rhomeo.loadFXML(this); + uiPort.setValueFactory(new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 65535, 21, 1)); + Rhomeo.initComboBox(uiAccess, FXCollections.observableArrayList(Arrays.asList(FTPPreferences.ACCESS_POINTS.values()))); + uiAccess.setEditable(false); + uiAccess.setConverter(new StringConverter() { + @Override + public String toString(FTPPreferences.ACCESS_POINTS object) { + if (object == null) + return null; + return object.getTitle(); + } + + @Override + public FTPPreferences.ACCESS_POINTS fromString(String string) { + if (string == null) + return null; + for (final FTPPreferences.ACCESS_POINTS ap : FTPPreferences.ACCESS_POINTS.values()) { + if (string.equals(ap)) + return ap; + } + + return null; + } + }); + + uiAccess.valueProperty().addListener(this::accessChanged); + } + + @FXML + void cancel(ActionEvent event) { + if (access == null) + clearForm(); + else + fillFromAcccess(); + } + + @FXML + void checkConnection(ActionEvent event) { + final String adress = uiURL.getText(); + if (adress == null || adress.isEmpty()) { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Impossible de tester la connexion si le champs d'adresse est vide !"); + } + + final int port = uiPort.getValue(); + final String workDir = uiDir.getText(); + final String login = uiLogin.getText(); + final String pw = uiPassword.getText(); + + final TaskManager.MockTask t = new TaskManager.MockTask(() -> FTPAccess.createClient(adress, port, workDir, login, pw)); + + t.setOnSucceeded((evt) -> Platform.runLater(() -> { + if (t.getValue() != null) { + Rhomeo.showAlert(Alert.AlertType.INFORMATION, "Connexion établie avec succès !"); + } else { + Rhomeo.showAlert(Alert.AlertType.WARNING, "Aucune réponse du serveur !"); + } + })); + + t.setOnFailed(evt -> Platform.runLater(() -> GeotkFX.newExceptionDialog("Impossible de se connecter au service FTP", t.getException()).show())); + + t.runningProperty().addListener((obs, oldValue, newValue) -> uiTest.setDisable(newValue)); + TaskManager.INSTANCE.submit("Test de connexion", t); + } + + private void accessChanged(final ObservableValue obs, FTPPreferences.ACCESS_POINTS oldAccess, FTPPreferences.ACCESS_POINTS newAccess) { + if (newAccess == null) { + access = null; + clearForm(); + + } else { + access = prefs.getAccess(newAccess); + fillFromAcccess(); + } + } + + private void clearForm() { + uiDir.clear(); + uiLogin.clear(); + uiPassword.clear(); + uiPort.getValueFactory().setValue(21); + uiURL.clear(); + } + + private void fillFromAcccess() { + uiDir.setText(access.getWorkingDir()); + uiLogin.setText(access.getLogin()); + try { + uiPassword.setText(access.getPassword()); + } catch (GeneralSecurityException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot read password for FTP access", ex); + Rhomeo.showAlert(Alert.AlertType.ERROR, "Impossible de lire le mot de passe enregistré"); + } + uiPort.getValueFactory().setValue(access.getPort()); + uiURL.setText(access.getAdress()); + } + + private void fillAccess() { + if (access == null) + return; + + access.setPort(uiPort.getValue()); + access.setAdress(uiURL.getText()); + access.setWorkingDir(uiDir.getText()); + access.setLogin(uiLogin.getText()); + try { + access.setPassword(uiPassword.getText()); + } catch (GeneralSecurityException ex) { + RhomeoCore.LOGGER.log(Level.WARNING, "Cannot write password for FTP access", ex); + GeotkFX.newExceptionDialog("Impossible d'enregistrer le mot de passe", ex).show(); + } + } + + @Override + public PreferenceGroup getPreferenceGroup() { + return prefs; + } + + @Override + public Node getEditor() { + return this; + } + + @Override + public void save() { + fillAccess(); + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVKey.java b/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVKey.java new file mode 100644 index 0000000..614f0de --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVKey.java @@ -0,0 +1,77 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.preferences.csv; + +import fr.cenra.rhomeo.api.preferences.InternationalPreferenceKey; +import org.apache.sis.util.ArgumentChecks; + +/** + * + * @author Alexis Manin (Geomatys) + */ +public class CSVKey implements InternationalPreferenceKey { + + private final String keyId; + private final String defaultValue; + + public CSVKey(final String netKey) { + this(netKey, null); + } + + public CSVKey(final String netKey, final String defaultValue) { + ArgumentChecks.ensureNonNull("Key", netKey); + this.keyId = netKey; + this.defaultValue = defaultValue; + } + + @Override + public String name() { + return keyId; + } + + @Override + public String getKey() { + return keyId; + } + + @Override + public String getDefaultValue() { + return defaultValue; + } +} diff --git a/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVPreferences.java b/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVPreferences.java new file mode 100644 index 0000000..0ea3185 --- /dev/null +++ b/desktop/src/main/java/fr/cenra/rhomeo/preferences/csv/CSVPreferences.java @@ -0,0 +1,74 @@ +/** + * Copyright © CENRA (2016) + * + * remi.clement@espaces-naturels.fr + * + * This software is a multi-platform and portable application based on + * the scientifical toolbox to monitor wetlands. With this toolbox it’s + * possible to calculate indicators «RhoMéO» to monitor quality, + * fonctionalities and urban and agricultural pressures of wetlands. + * Indicators are calculated using naturalist data entered and/or imported, + * and geographic data downloaded from a geographic server. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/ or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. +*/ +package fr.cenra.rhomeo.preferences.csv; + +import fr.cenra.rhomeo.api.preferences.UserPackagePreferenceGroup; +import fr.cenra.rhomeo.core.CSVMapper; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.springframework.stereotype.Component; + +/** + * Display preferences of encoding and other CSV formatting parameters. This is + * needed for dataset import/export. + * + * @author Alexis Manin (Geomatys) + */ +@Component +public class CSVPreferences extends UserPackagePreferenceGroup { + + public static final CSVKey CHARSET_KEY = new CSVKey("charset", StandardCharsets.UTF_8.name()); + public static final CSVKey SEPARATOR_KEY = new CSVKey("separator", String.valueOf(CSVMapper.DEFAULT_SEPARATOR)); + + private static final List KEYS = Collections.unmodifiableList(Arrays.asList(new CSVKey[] { + CHARSET_KEY, SEPARATOR_KEY + })); + + @Override + public List getKeys() { + return KEYS; + } + + @Override + public int getPriority() { + return 3; + } +} diff --git a/desktop/src/main/java/org/geotoolkit/gui/javafx/parameter/FXDateEditor.java b/desktop/src/main/java/org/geotoolkit/gui/javafx/parameter/FXDateEditor.java new file mode 100644 index 0000000..bd41148 --- /dev/null +++ b/desktop/src/main/java/org/geotoolkit/gui/javafx/parameter/FXDateEditor.java @@ -0,0 +1,109 @@ +/* + * Geotoolkit - An Open Source Java GIS Toolkit + * http://www.geotoolkit.org + * + * (C) 2015, 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.gui.javafx.parameter; + +import java.sql.Timestamp; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Date; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.scene.Node; +import javafx.scene.control.DatePicker; +import org.apache.sis.util.UnconvertibleObjectException; + +/** + * COPIED FROM GEOTOOLKIT 4.0.0-M0082. Aim is to simplify edition. + * + * @author Alexis Manin (Geomatys) + */ +public class FXDateEditor extends FXValueEditor { + + static final Class[] SUPPORTED_CLASSES = new Class[]{Date.class, LocalDate.class, LocalDateTime.class}; + + private final SimpleObjectProperty dateProperty = new SimpleObjectProperty(); + private final DatePicker editor = new DatePicker(); + + public FXDateEditor(final Spi spi) { + super(spi); + + dateProperty.addListener((ObservableValue observable, Object oldValue, Object newValue) -> { + if (editor.valueProperty().isBound()) + return; // Do not throw exception, because user has set editor property as a target for one of his own property. + + if (newValue == null) + editor.valueProperty().set(null); + else if (newValue instanceof LocalDateTime) + editor.valueProperty().set(((LocalDateTime)newValue).toLocalDate()); + else if (newValue instanceof LocalDate) + editor.valueProperty().set((LocalDate)newValue); + else if (newValue instanceof Date) { + editor.valueProperty().set(new Timestamp(((Date)newValue).getTime()).toLocalDateTime().toLocalDate()); + } else throw new UnconvertibleObjectException("Cannot convert from "+newValue.getClass() + " to "+LocalDate.class); + }); + + editor.valueProperty().addListener((ObservableValue observable, LocalDate oldValue, LocalDate newValue) -> { + if (dateProperty.isBound()) + return; // Do not throw exception, because user has set exposed property as a target for one of his own property. + + if (newValue == null) { + dateProperty.set(null); + } else { + Class valueClass = getValueClass(); + if (valueClass == null) + dateProperty.set(newValue); // TODO : throw an exception ? editor has not been configured, but user already started to work with it. + else if (LocalDateTime.class.isAssignableFrom(valueClass)) + dateProperty.set(newValue.atStartOfDay()); + else if (LocalDate.class.isAssignableFrom(valueClass)) + dateProperty.set(newValue); + else if (Date.class.isAssignableFrom(valueClass)) + dateProperty.set(Timestamp.valueOf(newValue.atStartOfDay())); + } + }); + } + + @Override + public Property valueProperty() { + return dateProperty; + } + + @Override + public Node getComponent() { + return editor; + } + + /** + * SPI + */ + public static final class Spi extends FXValueEditorSpi { + + @Override + public boolean canHandle(Class binding) { + for (final Class c : SUPPORTED_CLASSES) { + if (c.isAssignableFrom(binding)) + return true; + } + return false; + } + + @Override + public FXValueEditor createEditor() { + return new FXDateEditor(this); + } + } +} diff --git a/desktop/src/main/java/org/geotoolkit/gui/javafx/util/ProgressMonitor.java b/desktop/src/main/java/org/geotoolkit/gui/javafx/util/ProgressMonitor.java new file mode 100644 index 0000000..57dfe8f --- /dev/null +++ b/desktop/src/main/java/org/geotoolkit/gui/javafx/util/ProgressMonitor.java @@ -0,0 +1,388 @@ +package org.geotoolkit.gui.javafx.util; + +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ResourceBundle; +import java.util.Set; +import java.util.function.Predicate; +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.MenuButton; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ProgressBar; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.Background; +import javafx.scene.layout.Border; +import javafx.scene.layout.HBox; +import javafx.scene.text.Font; +import org.apache.sis.util.ArgumentChecks; +import org.geotoolkit.font.FontAwesomeIcons; +import org.geotoolkit.internal.GeotkFX; + +/** + * A JavaFX component which display progress and encountered errors for all tasks + * submitted on a specific task manager. + * + * The last submitted task is displayed in an Hbox. To see other running tasks + * and tasks in error, there's two {@link MenuButton}. Each display custom menu + * items containing information about a task. + * + * The {@link ProgressMonitor} is skinnable using a css stylesheet and the specific + * following css classes: + * {@link ProgressMonitor.CURRENT_TASK_CSS_CLASS} + * {@link ProgressMonitor.CURRENT_TASK_GRAPHIC_CSS_CLASS} + * {@link ProgressMonitor.ERROR_TASK_CSS_CLASS} + * {@link ProgressMonitor.ERROR_TASK_GRAPHIC_CSS_CLASS} + * {@link ProgressMonitor.PROGRESS_MONITOR_CSS_CLASS} + * {@link ProgressMonitor.CANCEL_BUTTON_CSS_CLASS} + * {@link ProgressMonitor.TASK_PROGRESS_CSS_CLASS} + * {@link ProgressMonitor.TASK_PROGRESS_GRAPHIC_CSS_CLASS} + * {@link ProgressMonitor.MENU_ITEM_CSS_CLASS} + * {@link ProgressMonitor.CLEAR_MENU_ITEM_CSS_CLASS} + * + * + * {@link ProgressMonitor} labels can be parametrized using {@link ResourceBundle} + * properties defined by {@link ProgressMonitor.ResourceKey}. + * + * @author Alexis Manin (Geomatys) + */ +public class ProgressMonitor extends HBox { + + private static String ICON_LABEL_FONT_FAMILY = "-fx-font-family: FontAwesome Regular;"; + + /** + * The css classes associated to the {@link ProgressMonitor} nodes. + */ + public static final String CURRENT_TASK_CSS_CLASS="geotk-progressMonitor-runningTasks"; + public static final String CURRENT_TASK_GRAPHIC_CSS_CLASS="geotk-progressMonitor-runningTasks-graphic"; + public static final String ERROR_TASK_CSS_CLASS="geotk-progressMonitor-tasksInError"; + public static final String ERROR_TASK_GRAPHIC_CSS_CLASS="geotk-progressMonitor-tasksInError-graphic"; + public static final String PROGRESS_MONITOR_CSS_CLASS="geotk-progressMonitor"; + public static final String CANCEL_BUTTON_CSS_CLASS="geotk-progressMonitor-cancelButton"; + public static final String TASK_PROGRESS_CSS_CLASS="geotk-progressMonitor-taskProgress"; + public static final String TASK_PROGRESS_GRAPHIC_CSS_CLASS="geotk-progressMonitor-taskProgress-graphic"; + public static final String MENU_ITEM_CSS_CLASS="geotk-progressMonitor-menuItem"; + public static final String CLEAR_MENU_ITEM_CSS_CLASS="geotk-progressMonitor-clearMenuItem"; + + private TaskProgress lastTask; + + private final MenuButton runningTasks; + private final MenuButton tasksInError; + + private final TaskManager taskRegistry; + + static { + // Load Font Awesome. + final Font font = FXUtilities.FONTAWESOME; + } + + /** + * The base constructor of progress monitors. + * + * @param registry The {@link TaskManager} followed by this progress monitor. + */ + public ProgressMonitor(final TaskManager registry) { + ArgumentChecks.ensureNonNull("Input task registry", registry); + + taskRegistry = registry; + + final Label runningIcon = new Label(FontAwesomeIcons.ICON_GEARS_ALIAS); + runningIcon.setStyle(ICON_LABEL_FONT_FAMILY); + runningIcon.getStyleClass().add(CURRENT_TASK_GRAPHIC_CSS_CLASS); + runningTasks = new MenuButton(GeotkFX.getString(ProgressMonitor.class, "currentTasksLabel"), runningIcon); + final Label errorIcon = new Label(FontAwesomeIcons.ICON_EXCLAMATION_CIRCLE); + errorIcon.setStyle(ICON_LABEL_FONT_FAMILY); + errorIcon.getStyleClass().add(ERROR_TASK_GRAPHIC_CSS_CLASS); + tasksInError = new MenuButton(GeotkFX.getString(ProgressMonitor.class, "errorTasksLabel"), errorIcon); + + final Tooltip runninTasksTooltip = new Tooltip(GeotkFX.getString(ProgressMonitor.class, "currentTasksTooltip")); + runningTasks.setTooltip(runninTasksTooltip); + final Tooltip tasksInErrorTooltip = new Tooltip(GeotkFX.getString(ProgressMonitor.class, "errorTasksTooltip")); + tasksInError.setTooltip(tasksInErrorTooltip); + + final SimpleListProperty runningTasksProp = new SimpleListProperty(taskRegistry.getSubmittedTasks()); + final SimpleListProperty failedTasksProp = new SimpleListProperty(taskRegistry.getTasksInError()); + + // Hide list of tasks if there's no information available. + runningTasks.visibleProperty().bind(runningTasksProp.sizeProperty().greaterThan(1)); + tasksInError.visibleProperty().bind(failedTasksProp.emptyProperty().not()); + + // Display number of tasks on menu button. + runningTasks.textProperty().bind(runningTasksProp.sizeProperty().asString()); + tasksInError.textProperty().bind(failedTasksProp.sizeProperty().asString()); + + // Set default visible task the last one submitted. + lastTask = new TaskProgress(); + lastTask.taskProperty().bind(runningTasksProp.valueAt(runningTasksProp.sizeProperty().subtract(1))); + + // Do not reserve size for hidden components. + runningTasks.managedProperty().bind(runningTasks.visibleProperty()); + tasksInError.managedProperty().bind(tasksInError.visibleProperty()); + lastTask.managedProperty().bind(lastTask.visibleProperty()); + + initTasks(); + + getChildren().addAll(lastTask, runningTasks, tasksInError); + minWidthProperty().bind(prefWidthProperty()); + prefWidthProperty().set(USE_COMPUTED_SIZE); + + getStyleClass().add(PROGRESS_MONITOR_CSS_CLASS); + runningTasks.getStyleClass().add(CURRENT_TASK_CSS_CLASS); + tasksInError.getStyleClass().add(ERROR_TASK_CSS_CLASS); + } + + /** + * Fill panel with currently submitted tasks. Add listeners on + * {@link TaskManager} to be aware of new events. + */ + private void initTasks() { + + final MenuItem clearErrorItem = new MenuItem(GeotkFX.getString(ProgressMonitor.class, "cleanErrorList")); + clearErrorItem.setOnAction(evt -> taskRegistry.getTasksInError().clear()); + + final Label icon = new Label(FontAwesomeIcons.ICON_TRASH_O); + icon.setStyle(ICON_LABEL_FONT_FAMILY); + clearErrorItem.setGraphic(icon); + + clearErrorItem.getStyleClass().add(CLEAR_MENU_ITEM_CSS_CLASS); + + tasksInError.getItems().add(clearErrorItem); + tasksInError.getItems().add(new SeparatorMenuItem()); + + // Listen on current running tasks + final ObservableList tmpSubmittedTasks = taskRegistry.getSubmittedTasks(); + tmpSubmittedTasks.addListener((ListChangeListener.Change c) -> { + + final Set toAdd = new HashSet<>(); + final Set toRemove = new HashSet<>(); + storeChanges(c, toAdd, toRemove); + + Platform.runLater(() -> { + for (final Task task : toAdd) { + final CustomMenuItem item = new CustomMenuItem(new TaskProgress(task)); + item.setHideOnClick(false); + runningTasks.getItems().add(item); + } + // remove Ended tasks + runningTasks.getItems().removeIf(new GetItemsForTask(toRemove)); + }); + }); + + // Check failed tasks. + final ObservableList tmpTasksInError = taskRegistry.getTasksInError(); + tmpTasksInError.addListener((ListChangeListener.Change c) -> { + final Set toAdd = new HashSet<>(); + final Set toRemove = new HashSet<>(); + storeChanges(c, toAdd, toRemove); + + Platform.runLater(() -> { + for (final Task task : toAdd) { + tasksInError.getItems().add(new ErrorMenuItem(task)); + } + // remove Ended tasks + tasksInError.getItems().removeIf(new GetItemsForTask(toRemove)); + }); + }); + + final Runnable initPanel = () -> { + final int nbSubmitted = tmpSubmittedTasks.size(); + // do not add last task to our menu, it will be used on main display. + for (int i = 0; i < nbSubmitted; i++) { + final CustomMenuItem item = new CustomMenuItem(new TaskProgress(tmpSubmittedTasks.get(i))); + item.setHideOnClick(false); + runningTasks.getItems().add(item); + } + + for (final Task t : tmpTasksInError) { + tasksInError.getItems().add(new ErrorMenuItem(t)); + } + }; + + if (Platform.isFxApplicationThread()) { + initPanel.run(); + } else { + Platform.runLater(initPanel); + } + } + + /** + * Store all objects depicted by a {@link ListChangeListener} into given + * collections. + * + * @param c The {@link ListChangeListener.Change} containing list content delta. + * @param added The collection to store new added objects into. + * @param removed Add removed objects in it. + */ + private static void storeChanges(final ListChangeListener.Change c, final Collection added, final Collection removed) { + while (c.next()) { + final List addedSubList = c.getAddedSubList(); + final List removeSubList = c.getRemoved(); + + if (addedSubList != null && !addedSubList.isEmpty()) { + added.addAll(addedSubList); + } + + final Iterator it = removeSubList.iterator(); + while (it.hasNext()) { + final Task current = it.next(); + if (!added.remove(current)) { + removed.add(current); + } + } + } + } + + /** + * The node giving information about a specific task. Allow to see title, + * description and current progress, as to cancel the task. + */ + private static class TaskProgress extends HBox { + + private final Label title = new Label(); + private final Tooltip description = new Tooltip(); + private final ProgressBar progress = new ProgressBar(); + private final Button cancelButton; + + private final ObjectProperty taskProperty = new SimpleObjectProperty<>(); + + TaskProgress() { + this(null); + } + + TaskProgress(final Task t) { + final Label icon = new Label(FontAwesomeIcons.ICON_BAN); + icon.setStyle(ICON_LABEL_FONT_FAMILY); + icon.getStyleClass().add(TASK_PROGRESS_GRAPHIC_CSS_CLASS); + cancelButton = new Button("", icon); + + taskProperty.addListener((ObservableValue observable, Task oldValue, Task newValue) -> { + if (Platform.isFxApplicationThread()) { + taskUpdated(); + } else { + Platform.runLater(()->taskUpdated()); + } + }); + + taskProperty.set(t); + + getChildren().addAll(title, progress, cancelButton); + + getStyleClass().add(TASK_PROGRESS_CSS_CLASS); + cancelButton.getStyleClass().add(CANCEL_BUTTON_CSS_CLASS); + } + + public ObjectProperty taskProperty() { + return taskProperty; + } + + public Task getTask() { + return taskProperty.get(); + } + + public synchronized void taskUpdated() { + title.textProperty().unbind(); + progress.progressProperty().unbind(); + description.textProperty().unbind(); + + cancelButton.setOnAction(null); + + final Task t =taskProperty.get(); + if (t != null) { + title.textProperty().bind(t.titleProperty()); + description.textProperty().bind(t.messageProperty()); + progress.progressProperty().bind(t.progressProperty()); + cancelButton.setOnAction((ActionEvent e) -> t.cancel()); + setVisible(true); + } else { + setVisible(false); + } + } + } + + /** + * A simple menu items for failed tasks. Display an exception Dialog when clicked. + */ + private class ErrorMenuItem extends MenuItem { + + final Task failedTask; + + ErrorMenuItem(final Task failedTask) { + ArgumentChecks.ensureNonNull("task in error", failedTask); + + this.failedTask = failedTask; + // No need for binding here. Task failed, its state should not change anymore. + String title = failedTask.getTitle(); + if (title == null || title.isEmpty()) + title = GeotkFX.getString(ProgressMonitor.class, "anonymOperation"); + setText(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm"))+" - "+title); + + final Label icon = new Label(FontAwesomeIcons.ICON_TRASH_O); + icon.setStyle(ICON_LABEL_FONT_FAMILY); + + final Button deleteButton = new Button("", icon); + deleteButton.setBorder(Border.EMPTY); + deleteButton.setPadding(Insets.EMPTY); + deleteButton.setBackground(Background.EMPTY); + deleteButton.setOnAction(e -> { + taskRegistry.getTasksInError().remove(failedTask); + e.consume(); + }); + setGraphic(deleteButton); + + final Dialog d = GeotkFX.newExceptionDialog(failedTask.getMessage(), failedTask.getException()); + d.setResizable(true); + + setOnAction((ActionEvent ae) -> d.show()); + + getStyleClass().add(MENU_ITEM_CSS_CLASS); + } + + public Task getTask() { + return failedTask; + } + } + + /** + * A simple {@link Predicate} which return current monitor progress bars + * which are focused on one of the given tasks. + */ + private static class GetItemsForTask implements Predicate { + + private final Collection tasks; + + GetItemsForTask(final Collection taskFilter) { + ArgumentChecks.ensureNonNull("Input filter tasks", taskFilter); + tasks = taskFilter; + } + + @Override + public boolean test(MenuItem item) { + if (item instanceof CustomMenuItem) { + final Node content = ((CustomMenuItem) item).getContent(); + return (content instanceof TaskProgress + && tasks.contains(((TaskProgress) content).getTask())); + } else if (item instanceof ErrorMenuItem) { + return tasks.contains(((ErrorMenuItem) item).getTask()); + } + return false; + } + } +} diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/ProgressMonitor.css b/desktop/src/main/resources/fr/cenra/rhomeo/ProgressMonitor.css new file mode 100644 index 0000000..0a26436 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/ProgressMonitor.css @@ -0,0 +1,58 @@ +.geotk-progressMonitor-cancelButton { + -fx-background-color: transparent; +} + +/*Override default css*/ +.label { + -fx-text-fill: #ffffff; +} + +.geotk-progressMonitor-tasksInError > .arrow-button, .geotk-progressMonitor-runningTasks > .arrow-button { + -fx-padding: 0; +} + +.geotk-progressMonitor-tasksInError > .arrow-button > .arrow, .geotk-progressMonitor-runningTasks > .arrow-button > .arrow { + -fx-padding: 0; +} + +.geotk-progressMonitor-tasksInError, .geotk-progressMonitor-runningTasks { + -fx-background-color: transparent; + -fx-border-style: none; + -fx-border-width: 2; +} + +.geotk-progressMonitor-tasksInError { + -fx-text-fill: green; +} + +.geotk-progressMonitor-clearMenuItem .label { + -fx-font-size: 14px; +} + +.geotk-progressMonitor-menuItem .label { + -fx-font-size: 12px; +} + +.geotk-progressMonitor { + -fx-spacing: 10; + -fx-alignment: center; + -fx-font-size: 15px; + -fx-background-color: transparent; +} + +.geotk-progressMonitor-taskProgress{ + -fx-spacing: 5; + -fx-alignment: center; +} + +.geotk-progressMonitor-runningTasks-graphic{ + -fx-text-fill: aquamarine; +} + +.geotk-progressMonitor-tasksInError-graphic{ + -fx-text-fill: #aa0000; +} + +.geotk-progressMonitor-taskProgress-graphic{ + -fx-text-fill: #aa0000; +} \ No newline at end of file diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.fxml b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.fxml new file mode 100644 index 0000000..95cd730 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.fxml @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+ + + + + + + + + +
+
+
diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.properties b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.properties new file mode 100644 index 0000000..a6e03ce --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardMenuPane.properties @@ -0,0 +1,32 @@ +site=SITE +new-site=Nouveau site +import-site=Importer un site +site-protocol=SITE ET PROTOCOLE +protocoles=PROTOCOLES +create-site=Cr\u00e9er le site +cancel=Annuler +save=Enregistrer +sites-info-title=INFORMATIONS SUR LE SITE +info-site=Site * +referent-site=R\u00e9f\u00e9rent +contour-site=Contour * +departement-site=D\u00e9partement * +structure-site=Structure +type-zh-site=Type ZH * +zb-odo-site=ZB odo +zb-ortho-site=ZB ortho +import-contour=Importer un contour de site +view-contour=Visualiser +cleanProgressMonitorList=Vider la liste +downloadShape=Format .shp +downloadKml=Format .kml + +download-selected-site-tooltip=T\u00e9l\u00e9charger le site s\u00e9lectionn\u00e9 +delete-selected-site-tooltip=Supprimer le site s\u00e9lectionn\u00e9 +edit-selected-site-tooltip=Edition / Lecture simple pour le site s\u00e9lectionn\u00e9 +view-info-site-tooltip=Afficher / Masquer les informations du site s\u00e9lectionn\u00e9 + +alert-site-deleted=La suppression du site \"{0}\" supprimera \u00e9galement ses donn\u00e9es associ\u00e9es. Etes-vous s\u00fbr ? +alert-site-download=L'extension du fichier de sortie doit \u00eatre .shp, .zip ou .kml. +alert-site-createOrUpdate=Un site est d\u00e9j\u00e0 pr\u00e9sent portant le nom \"{0}\". Veuillez saisir un nouveau nom. +unsaved-modification=Les modifications non sauvegard\u00e9es seront perdues. Etes-vous s\u00fbr de vouloir continuer ? diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.fxml b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.fxml new file mode 100644 index 0000000..ca4b16b --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + +
+ + + + + + + + +
+
diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.properties b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.properties new file mode 100644 index 0000000..e953f81 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDashboardPane.properties @@ -0,0 +1,5 @@ +title-table=TABLEAU DE BORD +publish=Publier les r\u00e9sultats +export=Exporter les r\u00e9sultats +year=Ann\u00e9e +publish-title=Publication des r\u00e9sultats sur le serveur diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.css b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.css new file mode 100644 index 0000000..78eca06 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultPreferenceGroupPane.css @@ -0,0 +1,5 @@ +.pane { + -fx-padding: 15; + -fx-hgap: 10; + -fx-vgap: 10; +} diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.fxml b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.fxml new file mode 100644 index 0000000..0cab345 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXDefaultProcessingPane.fxml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXRhomeoWizard.properties b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXRhomeoWizard.properties new file mode 100644 index 0000000..1067e2a --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXRhomeoWizard.properties @@ -0,0 +1,7 @@ +site=Site : +refUsed=R\u00e9f\u00e9rentiels utilis\u00e9s +stopProcess=Arr\u00eater le processus +step1=Lot de donn\u00e9es +step2=Indicateurs +step3=R\u00e9sultats +step4=Finalisation \ No newline at end of file diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXSplashScreen.fxml b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXSplashScreen.fxml new file mode 100644 index 0000000..933d5c8 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/FXSplashScreen.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/Typologie_Habitats.pdf b/desktop/src/main/resources/fr/cenra/rhomeo/fx/Typologie_Habitats.pdf new file mode 100644 index 0000000..8ca49fc Binary files /dev/null and b/desktop/src/main/resources/fr/cenra/rhomeo/fx/Typologie_Habitats.pdf differ diff --git a/desktop/src/main/resources/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.fxml b/desktop/src/main/resources/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.fxml new file mode 100644 index 0000000..1c60cd8 --- /dev/null +++ b/desktop/src/main/resources/fr/cenra/rhomeo/fx/edition/CSVConfigurationPane.fxml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +