changeset 565:8d11d6a17f3b

Mit 3.2.x zusammenführen
author Gernot Belger <g.belger@bjoernsen.de>
date Wed, 15 Jul 2020 11:53:27 +0200
parents 05caf2e731d0 (diff) e4dd898063a2 (current diff)
children
files
diffstat 10 files changed, 395 insertions(+), 158 deletions(-) [+]
line wrap: on
line diff
--- a/artifact-database/pom.xml	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/pom.xml	Wed Jul 15 11:53:27 2020 +0200
@@ -91,5 +91,10 @@
         <artifactId>jetty</artifactId>
         <version>6.1.26</version>
     </dependency>
+    <dependency>
+      <groupId>org.restlet.jse</groupId>
+      <artifactId>org.restlet.ext.httpclient</artifactId>
+      <version>2.0.7</version>
+    </dependency>
   </dependencies>
 </project>
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/ArtifactDatabaseImpl.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/ArtifactDatabaseImpl.java	Wed Jul 15 11:53:27 2020 +0200
@@ -407,6 +407,8 @@
      */
     protected HashMap     name2service;
 
+    private Map<String, Class<?>> restServices;
+
     /**
      * The factory that is used to create new artifact collections.
      */
@@ -464,6 +466,8 @@
 
     protected List<LifetimeListener> lifetimeListeners;
 
+    private String serverAddress;
+
     /**
      * Default constructor.
      */
@@ -493,10 +497,13 @@
 
         backgroundIds  = new HashSet<Integer>();
         backgroundMsgs = new HashMap<String, LinkedList<Message>>();
+        
+        this.serverAddress = bootstrap.getHTTPServer().getServerAddress();
 
         setupArtifactCollectionFactory(bootstrap);
         setupArtifactFactories(bootstrap);
         setupServices(bootstrap);
+        setupRestServices(bootstrap);
         setupUserFactory(bootstrap);
         setupCallContextListener(bootstrap);
         setupHooks(bootstrap);
@@ -638,6 +645,17 @@
     }
 
     /**
+     * Used to extract the service factories from the bootstrap
+     * parameters, setting up the services and building the internal
+     * lookup tables.
+     * @param bootstrap The bootstrap parameters.
+     */
+    private void setupRestServices(FactoryBootstrap bootstrap) {
+        
+        restServices  = new HashMap<String,Class<?>>(bootstrap.getRestServiceNames());
+    }
+
+    /**
      * Wires a storage backend to this artifact database and
      * establishes a callback to be able to revive artifacts
      * via the serializers of this artifact factories.
@@ -1984,5 +2002,15 @@
     public String findArtifactUser(final String artifactIdentifier) {
         return backend.findUserName(artifactIdentifier);
     }
+    
+    @Override
+    public String getServerAddress() {
+        return this.serverAddress;
+    }
+
+    @Override
+    public Map<String, Class<?>> getRestServices() {
+        return restServices;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/CollectionCallContext.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/CollectionCallContext.java	Wed Jul 15 11:53:27 2020 +0200
@@ -10,12 +10,10 @@
 import java.util.LinkedList;
 
 import org.apache.log4j.Logger;
-
 import org.dive4elements.artifacts.ArtifactCollection;
 import org.dive4elements.artifacts.CallMeta;
 import org.dive4elements.artifacts.Message;
 
-
 /**
  * Class that implements the call context handed to ArtifactCollection specific
  * operations.
@@ -29,51 +27,48 @@
     /**
      * The ArtifactCollection.
      */
-    protected ArtifactCollection collection;
-
+    private final ArtifactCollection collection;
 
-    public CollectionCallContext(
-        ArtifactDatabaseImpl artifactDatabase,
-        int                  action,
-        CallMeta             callMeta,
-        ArtifactCollection   collection)
-    {
+    public CollectionCallContext(final ArtifactDatabaseImpl artifactDatabase, final int action, final CallMeta callMeta, final ArtifactCollection collection) {
         super(artifactDatabase, action, callMeta);
 
         this.collection = collection;
     }
 
+    public ArtifactCollection getCollection() {
+        return this.collection;
+    }
 
-    public void afterCall(int action) {
+    @Override
+    public void afterCall(final int action) {
         log.debug("CollectionCallContext.afterCall - NOT IMPLEMENTED");
     }
 
-
-    public void afterBackground(int action) {
+    @Override
+    public void afterBackground(final int action) {
         log.debug("CollectionCallContext.afterBackground - NOT IMPLEMENTED");
     }
 
-
+    @Override
     public boolean isInBackground() {
         log.debug("CollectionCallContext.isInBackground - NOT IMPLEMENTED");
         return false;
     }
 
-
-    public void addBackgroundMessage(Message msg) {
+    @Override
+    public void addBackgroundMessage(final Message msg) {
         log.debug("CollectionCallContext.addBackgroundMessage NOT IMPLEMENTED");
     }
 
-
+    @Override
     public LinkedList<Message> getBackgroundMessages() {
         log.debug("CollectionCallContext.addBackgroundMessage NOT IMPLEMENTED");
         return null;
     }
 
-
+    @Override
     public Long getTimeToLive() {
         log.debug("CollectionCallContext.getTimeToLive - NOT IMPLEMENTED");
         return null;
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/FactoryBootstrap.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/FactoryBootstrap.java	Wed Jul 15 11:53:27 2020 +0200
@@ -20,11 +20,12 @@
 import org.dive4elements.artifacts.UserFactory;
 
 import org.dive4elements.artifacts.common.utils.StringUtils;
-
 import org.dive4elements.artifactdatabase.rest.HTTPServer;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.log4j.Logger;
 
@@ -70,6 +71,13 @@
         "/artifact-database/factories/service-factories/service-factory";
 
     /**
+     * XPath to figure out the names of the addtional restful services from
+     * the global configuration.
+     */
+    public static final String RESTFUL_SERVICES =
+            "/artifact-database/factories/restful-services/restful-service";
+
+    /**
      * XPath to figure out the class name of the user factory from global
      * configuration.
      */
@@ -144,6 +152,8 @@
      * artifact database.
      */
     protected ArtifactFactory [] artifactFactories;
+    
+    private Map<String, Class<?>> restServices;
 
     /**
      * List of service factories which creates services that are
@@ -361,6 +371,40 @@
             new ServiceFactory[loadedFactories.size()]);
     }
 
+    /**
+     * Scans the global configuration for the configured service factories
+     * and sets them up.
+     */
+    private void loadRestfulServices() {
+        
+        logger.info("loading additional restful services");
+        
+        restServices = new HashMap<String, Class<?>>();
+        
+        final NodeList nodes = Config.getNodeSetXPath(RESTFUL_SERVICES);
+        
+        if (nodes == null)
+            return;
+        
+        for (int i = 0; i < nodes.getLength(); ++i) {
+            
+            final Node node = nodes.item(i);
+            
+            final String className = Config.getStringXPath( node, "@class");
+            final String path = Config.getStringXPath( node, "@path");
+
+            try {
+                final Class<?> clazz = Class.forName(className);
+                restServices.put(path, clazz);
+            }
+            catch (final ClassNotFoundException cnfe) {
+                logger.error(cnfe.getLocalizedMessage(), cnfe);
+            }
+            catch (final ClassCastException cce) {
+                logger.error(cce.getLocalizedMessage(), cce);
+            }
+        }
+    }
 
     /**
      * Scans the global configuration for the configured user factory.
@@ -638,6 +682,7 @@
         loadCollectionFactory();
         loadArtifactFactories();
         loadServiceFactories();
+        loadRestfulServices();
         loadUserFactory();
         loadCallContextListener();
         loadHTTPServer();
@@ -729,5 +774,9 @@
     public List<BackendListener> getBackendListeners() {
         return backendListeners;
     }
+
+    public Map<String,Class<?>> getRestServiceNames() {
+        return restServices;
+    }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/HTTPServer.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/HTTPServer.java	Wed Jul 15 11:53:27 2020 +0200
@@ -1,13 +1,13 @@
 package org.dive4elements.artifactdatabase.rest;
 
+import org.dive4elements.artifacts.ArtifactDatabase;
 import org.w3c.dom.Document;
 
-import org.dive4elements.artifacts.ArtifactDatabase;
-
 public interface HTTPServer
 {
     void setup(Document document);
 
     void startAsServer(ArtifactDatabase database);
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+
+    String getServerAddress();
+}
\ No newline at end of file
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/RestApp.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/RestApp.java	Wed Jul 15 11:53:27 2020 +0200
@@ -8,14 +8,14 @@
 
 package org.dive4elements.artifactdatabase.rest;
 
-import org.dive4elements.artifacts.ArtifactDatabase;
-
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.concurrent.ConcurrentMap;
 
+import org.dive4elements.artifacts.ArtifactDatabase;
 import org.restlet.Application;
 import org.restlet.Context;
 import org.restlet.Restlet;
-
 import org.restlet.routing.Router;
 
 /**
@@ -25,32 +25,21 @@
  *
  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
-public class RestApp
-extends      Application
-{
+public class RestApp extends Application {
+
     /**
      * The central artifact database instance to work with.
      */
-    protected ArtifactDatabase database;
-
-    /**
-     * Default constructor
-     */
-    public RestApp() {
-    }
-
-    public RestApp(Context context, ArtifactDatabase database) {
-        super(context);
-        this.database = database;
-    }
+    private final ArtifactDatabase database;
 
     /**
      * Constructor to create REST appliction bound to a specific
      * artifact database.
      *
-     * @param database The artifact database to be used.
+     * @param database
+     *            The artifact database to be used.
      */
-    public RestApp(ArtifactDatabase database) {
+    public RestApp(final ArtifactDatabase database) {
         this.database = database;
     }
 
@@ -63,35 +52,35 @@
     @Override
     public Restlet createRoot() {
 
-        Context context = getContext();
-
-        ConcurrentMap map = context.getAttributes();
-        map.put("database", database);
+        final Context context = getContext();
 
-        Router router = new Router(context);
+        final ConcurrentMap<String, Object> map = context.getAttributes();
+        map.put("database", this.database);
 
-        router.attach(ServicesResource.PATH,    ServicesResource.class);
-        router.attach(ServiceResource.PATH,     ServiceResource.class);
-        router.attach(FactoriesResource.PATH,   FactoriesResource.class);
-        router.attach(CreateResource.PATH,      CreateResource.class);
-        router.attach(ArtifactResource.PATH,    ArtifactResource.class);
+        final Router router = new Router(context);
+
+        router.attach(ServicesResource.PATH, ServicesResource.class);
+        router.attach(ServiceResource.PATH, ServiceResource.class);
+        router.attach(FactoriesResource.PATH, FactoriesResource.class);
+        router.attach(CreateResource.PATH, CreateResource.class);
+        router.attach(ArtifactResource.PATH, ArtifactResource.class);
         router.attach(ArtifactOutResource.PATH, ArtifactOutResource.class);
-        router.attach(ExportResource.PATH,      ExportResource.class);
-        router.attach(ImportResource.PATH,      ImportResource.class);
-        router.attach(CreateUserResource.PATH,  CreateUserResource.class);
-        router.attach(ListUsersResource.PATH,   ListUsersResource.class);
-        router.attach(UserResource.PATH,        UserResource.class);
-        router.attach(FindUserResource.PATH,    FindUserResource.class);
-        router.attach(
-            CreateCollectionResource.PATH, CreateCollectionResource.class);
-        router.attach(
-            ListCollectionsResource.PATH, ListCollectionsResource.class);
-        router.attach(
-            CollectionResource.PATH, CollectionResource.class);
-        router.attach(
-            CollectionOutResource.PATH, CollectionOutResource.class);
+        router.attach(ExportResource.PATH, ExportResource.class);
+        router.attach(ImportResource.PATH, ImportResource.class);
+        router.attach(CreateUserResource.PATH, CreateUserResource.class);
+        router.attach(ListUsersResource.PATH, ListUsersResource.class);
+        router.attach(UserResource.PATH, UserResource.class);
+        router.attach(FindUserResource.PATH, FindUserResource.class);
+        router.attach(CreateCollectionResource.PATH, CreateCollectionResource.class);
+        router.attach(ListCollectionsResource.PATH, ListCollectionsResource.class);
+        router.attach(CollectionResource.PATH, CollectionResource.class);
+        router.attach(CollectionOutResource.PATH, CollectionOutResource.class);
+
+        /* register any additional services */
+        final Map<String, Class<?>> restServices = this.database.getRestServices();
+        for (final Entry<String, Class<?>> entry : restServices.entrySet())
+            router.attach(entry.getKey(), entry.getValue());
 
         return router;
     }
-}
-// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
+}
\ No newline at end of file
--- a/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/Standalone.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifact-database/src/main/java/org/dive4elements/artifactdatabase/rest/Standalone.java	Wed Jul 15 11:53:27 2020 +0200
@@ -89,6 +89,12 @@
         listen     = XMLUtils.xpathString(document, LISTEN_INTERFACE, null);
         maxThreads = XMLUtils.xpathString(document, MAX_THREADS, null);
     }
+    
+    @Override
+    public String getServerAddress() {
+        String host = (this.listen == null || this.listen.trim().length() == 0 ) ? "localhost" : this.listen;
+        return String.format("http://%s:%d", host, this.port);
+    }
 
     protected Server createServer() {
         return listen != null && listen.length() > 0
--- a/artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/Config.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/Config.java	Wed Jul 15 11:53:27 2020 +0200
@@ -8,22 +8,22 @@
 
 package org.dive4elements.artifacts.common.utils;
 
+import java.io.BufferedInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
 
 import javax.xml.namespace.QName;
-
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
-
 import javax.xml.xpath.XPathConstants;
 
 import org.apache.log4j.Logger;
-
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
-
 import org.xml.sax.SAXException;
 
 /**
@@ -33,8 +33,7 @@
  *
  * @author <a href="mailto:sascha.teichmann@intevation.de">Sascha L. Teichmann</a>
  */
-public final class Config
-{
+public final class Config {
     private static Logger logger = Logger.getLogger(Config.class);
 
     /**
@@ -46,22 +45,19 @@
      * Default path to the configuration directory if none
      * was specified by the CONFIG_DIR system property.
      */
-    public static final File CONFIG_DIR_DEFAULT =
-        new File(new File(System.getProperty("user.home",
-            System.getProperty("user.dir", "."))), ".artifactdb");
+    public static final File CONFIG_DIR_DEFAULT = new File(new File(System.getProperty("user.home", System.getProperty("user.dir", "."))), ".artifactdb");
 
     /**
      * Name of the central configuration XML file.
      */
-    public static final String CONFIG_FILE  = "conf.xml";
+    public static final String CONFIG_FILE = "conf.xml";
 
     /**
      * Name of the configuration filename alias to be use
      * within the configuration. This alias is replaced by
      * the real path.
      */
-    public static final String CONFIG_DIR_PLACEHOLDER =
-        "${artifacts.config.dir}";
+    public static final String CONFIG_DIR_PLACEHOLDER = "${artifacts.config.dir}";
 
     private static Document config;
 
@@ -70,6 +66,7 @@
 
     /**
      * Singleton access to the central XML configuration document.
+     *
      * @return The central XML configuration document.
      */
     public static synchronized final Document getConfig() {
@@ -79,18 +76,38 @@
         return config;
     }
 
+    public static Properties loadProperties(final String CONFIG_FILE_LOCAL, final File configDir) throws IOException {
+        final File configFile = new File(configDir, CONFIG_FILE_LOCAL);
+
+        InputStream reader = null;
+        try {
+            reader = new BufferedInputStream(new FileInputStream(configFile));
+
+            final Properties properties = new Properties();
+            properties.load(reader);
+            return properties;
+        } finally {
+            try {
+                if (reader != null)
+                    reader.close();
+            }
+            catch (final IOException e1) {
+                e1.printStackTrace();
+            }
+        }
+    }
+
     /**
      * Returns the path to the configuartion directory. If a path
      * was specified via the CONFIG_DIR system property this one
      * is used. Else it falls back to default configuration path.
+     *
      * @return The path to the configuartion directory.
      */
     public static File getConfigDirectory() {
-        String configDirString = System.getProperty(CONFIG_DIR);
+        final String configDirString = System.getProperty(CONFIG_DIR);
 
-        File configDir = configDirString != null
-            ? new File(configDirString)
-            : CONFIG_DIR_DEFAULT;
+        File configDir = configDirString != null ? new File(configDirString) : CONFIG_DIR_DEFAULT;
 
         if (!configDir.isDirectory()) {
             logger.warn("'" + configDir + "' is not a directory.");
@@ -101,42 +118,57 @@
     }
 
     /**
+     * returns the path to modules-config, which is a subfolder of config
+     */
+    public static File getModulesConfigDirectory() {
+
+        final File configDir = getConfigDirectory();
+        final File modulesCfgFile = new File(configDir, "modules");
+
+        if (!modulesCfgFile.isDirectory()) {
+            logger.warn("'" + modulesCfgFile + "' is not a directory.");
+            return CONFIG_DIR_DEFAULT; // or null?
+        }
+        return modulesCfgFile;
+    }
+
+    /**
      * Replaces the CONFIG_DIR_PLACEHOLDER alias with the real path
      * of the configuration directory.
-     * @param path The path containing the CONFIG_DIR_PLACEHOLDER placeholder.
+     *
+     * @param path
+     *            The path containing the CONFIG_DIR_PLACEHOLDER placeholder.
      * @return The path where the CONFIG_DIR_PLACEHOLDER placeholders are
-     * replaced by the real path name.
+     *         replaced by the real path name.
      */
-    public static String replaceConfigDir(String path) {
-        String configDir = getConfigDirectory().getAbsolutePath();
+    public static String replaceConfigDir(final String path) {
+        final String configDir = getConfigDirectory().getAbsolutePath();
         return path.replace(CONFIG_DIR_PLACEHOLDER, configDir);
     }
 
     private static Document loadConfig() {
 
-        File configDir = getConfigDirectory();
+        final File configDir = getConfigDirectory();
 
-        File file = new File(configDir, CONFIG_FILE);
+        final File file = new File(configDir, CONFIG_FILE);
 
         if (!file.canRead() && !file.isFile()) {
-            logger.error("Cannot read config file '"
-                + file + "'.");
+            logger.error("Cannot read config file '" + file + "'.");
             return null;
         }
 
         try {
-            DocumentBuilderFactory factory =
-                DocumentBuilderFactory.newInstance();
+            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
             factory.setValidating(false); // XXX: This may change in future.
             return factory.newDocumentBuilder().parse(file);
         }
-        catch (SAXException se) {
+        catch (final SAXException se) {
             logger.error(se.getLocalizedMessage(), se);
         }
-        catch (ParserConfigurationException pce) {
+        catch (final ParserConfigurationException pce) {
             logger.error(pce.getLocalizedMessage(), pce);
         }
-        catch (IOException ioe) {
+        catch (final IOException ioe) {
             logger.error(ioe.getLocalizedMessage());
         }
 
@@ -146,131 +178,152 @@
     /**
      * Convenience method to search within a given document tree via XPath.
      * See {@link XMLUtils#xpath(Object, String, QName) } for details.
-     * @param root The object which is used as the root of the tree to
-     * be searched in.
-     * @param query The XPath query.
-     * @param returnType The type of the result.
+     *
+     * @param root
+     *            The object which is used as the root of the tree to
+     *            be searched in.
+     * @param query
+     *            The XPath query.
+     * @param returnType
+     *            The type of the result.
      * @return The result of type 'returnTyp' or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final Object getXPath(
-        Object root, String query, QName returnType
-    ) {
+    public static final Object getXPath(final Object root, final String query, final QName returnType) {
         return XMLUtils.xpath(root, query, returnType);
     }
 
     /**
      * Convenience method to search within the central configuration via XPath.
      * See {@link XMLUtils#xpath(Object, String, QName) } for details.
-     * @param query The XPath query.
-     * @param returnType The type of the result.
+     *
+     * @param query
+     *            The XPath query.
+     * @param returnType
+     *            The type of the result.
      * @return The result of type 'returnTyp' or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final Object getXPath(String query, QName returnType) {
+    public static final Object getXPath(final String query, final QName returnType) {
         return XMLUtils.xpath(getConfig(), query, returnType);
     }
 
     /**
      * Convenience method to search for a node list within the central
      * configuation document via XPath.
-     * @param query The XPath query.
+     *
+     * @param query
+     *            The XPath query.
      * @return The queried node list or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final NodeList getNodeSetXPath(String query) {
-        return (NodeList)getXPath(query, XPathConstants.NODESET);
+    public static final NodeList getNodeSetXPath(final String query) {
+        return (NodeList) getXPath(query, XPathConstants.NODESET);
     }
 
     /**
      * Convenience method to search for a node within the central
      * configuation document via XPath.
-     * @param query The XPath query.
+     *
+     * @param query
+     *            The XPath query.
      * @return The queried node or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final Node getNodeXPath(String query) {
-        return (Node)getXPath(query, XPathConstants.NODE);
+    public static final Node getNodeXPath(final String query) {
+        return (Node) getXPath(query, XPathConstants.NODE);
     }
 
     /**
      * Convenience method to search for a string within the central
      * configuation document via XPath.
-     * @param xpath The XPath query.
+     *
+     * @param xpath
+     *            The XPath query.
      * @return The queried string or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final String getStringXPath(String xpath) {
+    public static final String getStringXPath(final String xpath) {
         return getStringXPath(xpath, null);
     }
 
     /**
      * Convenience method to search for a string within the central
      * configuation document via XPath.
-     * @param query The XPath query.
-     * @param def The string to be returned if the search has no results.
+     *
+     * @param query
+     *            The XPath query.
+     * @param def
+     *            The string to be returned if the search has no results.
      * @return The queried string or the default value if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final String getStringXPath(String query, String def) {
-        String s = (String)getXPath(query, XPathConstants.STRING);
-        return s == null || s.length() == 0
-            ? def
-            : s;
+    public static final String getStringXPath(final String query, final String def) {
+        final String s = (String) getXPath(query, XPathConstants.STRING);
+        return s == null || s.length() == 0 ? def : s;
     }
 
     /**
      * Convenience method to search for a node list within a given tree
      * via XPath.
-     * @param root The root of the tree to be searched in.
-     * @param query The XPath query.
+     *
+     * @param root
+     *            The root of the tree to be searched in.
+     * @param query
+     *            The XPath query.
      * @return The queried node list or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final NodeList getNodeSetXPath(Object root, String query) {
-        return (NodeList)getXPath(root, query, XPathConstants.NODESET);
+    public static final NodeList getNodeSetXPath(final Object root, final String query) {
+        return (NodeList) getXPath(root, query, XPathConstants.NODESET);
     }
 
     /**
      * Convenience method to search for a node within a given tree
      * via XPath.
-     * @param root The root of the tree to be searched in.
-     * @param query The XPath query.
+     *
+     * @param root
+     *            The root of the tree to be searched in.
+     * @param query
+     *            The XPath query.
      * @return The queried node or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final Node getNodeXPath(Object root, String query) {
-        return (Node)getXPath(root, query, XPathConstants.NODE);
+    public static final Node getNodeXPath(final Object root, final String query) {
+        return (Node) getXPath(root, query, XPathConstants.NODE);
     }
 
     /**
      * Convenience method to search for a string within a given tree
      * via XPath.
-     * @param root The root of the tree to be searched in.
-     * @param xpath The XPath query.
+     *
+     * @param root
+     *            The root of the tree to be searched in.
+     * @param xpath
+     *            The XPath query.
      * @return The queried string or null if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final String getStringXPath(Object root, String xpath) {
+    public static final String getStringXPath(final Object root, final String xpath) {
         return getStringXPath(root, xpath, null);
     }
 
     /**
      * Convenience method to search for a string within a given tree
      * via XPath.
-     * @param root The root of the tree to be searched in.
-     * @param query xpath The XPath query.
-     * @param def The string to be returned if the search has no results.
+     *
+     * @param root
+     *            The root of the tree to be searched in.
+     * @param query
+     *            xpath The XPath query.
+     * @param def
+     *            The string to be returned if the search has no results.
      * @return The queried string or the default value if something went
-     * wrong during XPath evaluation.
+     *         wrong during XPath evaluation.
      */
-    public static final String getStringXPath(
-        Object root, String query, String def
-    ) {
-        String s = (String)getXPath(root, query, XPathConstants.STRING);
-        return s == null || s.length() == 0
-            ? def
-            : s;
+    public static final String getStringXPath(final Object root, final String query, final String def) {
+        final String s = (String) getXPath(root, query, XPathConstants.STRING);
+        return s == null || s.length() == 0 ? def : s;
     }
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :
--- a/artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/DateUtils.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifacts-common/src/main/java/org/dive4elements/artifacts/common/utils/DateUtils.java	Wed Jul 15 11:53:27 2020 +0200
@@ -17,14 +17,120 @@
      *
      * @return the year as integer or -1 if date is empty.
      */
-    public static int getYearFromDate(Date date) {
+    public static int getYearFromDate(final Date date) {
         if (date == null) {
             return -1;
         }
 
-        Calendar cal = Calendar.getInstance();
+        final Calendar cal = Calendar.getInstance();
         cal.setTime(date);
 
         return cal.get(Calendar.YEAR);
     }
+
+    /**
+     * Gets the abfluss year (1.11. - 31.10.) a date belongs to
+     *
+     * @return the abfluss year, or -1
+     */
+    public static int getAbflussYearFromDate(final Date date) {
+        if (date == null)
+            return -1;
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(getAbflussYear(date)[1]);
+        return cal.get(Calendar.YEAR);
+    }
+
+    /**
+     * Gets the date range of the abfluss year (1.11. - 31.10.) a date belongs to
+     *
+     * @return [abfluss year start date, abfluss year end date], or null
+     */
+    public static Date[] getAbflussYear(final Date date) {
+        if (date == null)
+            return null;
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        final int qYear = (cal.get(Calendar.MONTH) >= 10) ? cal.get(Calendar.YEAR) + 1 : cal.get(Calendar.YEAR);
+        return getAbflussYear(qYear);
+    }
+
+    /**
+     * Gets the date range of the abfluss year (1.11. - 31.10.) a date belongs to
+     *
+     * @return [abfluss year start date, abfluss year end date], or null
+     */
+    public static Date[] getAbflussYear(final int year) {
+        final Calendar calStart = Calendar.getInstance();
+        calStart.clear();
+        calStart.set(year - 1, 10, 1, 0, 0, 0);
+        final Calendar calEnd = Calendar.getInstance();
+        calEnd.clear();
+        calEnd.set(year, 9, 31, 23, 59, 59);
+        return new Date[] { calStart.getTime(), calEnd.getTime() };
+    }
+
+    /**
+     * Gets the date range of the abfluss year following a date, or that of the date itself if it's a nov-1
+     *
+     * @return [abfluss year start date, abfluss year end date], or null
+     */
+    public static Date[] getNextAbflussYear(final Date date) {
+        if (date == null)
+            return null;
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        final int nextQYear = cal.get(Calendar.YEAR) + 1;
+        switch (cal.get(Calendar.MONTH)) {
+        case 10:
+            if (cal.get(Calendar.DAY_OF_MONTH) >= 2)
+                return getAbflussYear(nextQYear + 1);
+            break;
+        case 11:
+            return getAbflussYear(nextQYear + 1);
+        default:
+            break;
+        }
+        return getAbflussYear(nextQYear);
+    }
+
+    /**
+     * Gets the date range of the abfluss year preceeding a date, or that of the date itself if it's a oct-31
+     *
+     * @return [abfluss year start date, abfluss year end date], or null
+     */
+    public static Date[] getPreviousAbflussYear(final Date date) {
+        if (date == null)
+            return null;
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        final int previousQYear = cal.get(Calendar.YEAR);
+        switch (cal.get(Calendar.MONTH)) {
+        case 10:
+        case 11:
+            break;
+        case 9:
+            if (cal.get(Calendar.DAY_OF_MONTH) <= 30)
+                return getAbflussYear(previousQYear - 1);
+            break;
+        default:
+            return getAbflussYear(previousQYear - 1);
+        }
+        return getAbflussYear(previousQYear);
+    }
+
+    /**
+     * Gets the date of the new year's day following a date, or the date itself with 0-time if it's a jan-1
+     *
+     * @return a newyear's date, or null
+     */
+    public static Date getNextNewYear(final Date date) {
+        if (date == null)
+            return null;
+        final Calendar cal = Calendar.getInstance();
+        cal.setTime(date);
+        final int offset = (cal.get(Calendar.DAY_OF_YEAR) == 1) ? 0 : 1;
+        cal.set(cal.get(Calendar.YEAR) + offset, 0, 1, 0, 0, 0);
+        return cal.getTime();
+    }
 }
--- a/artifacts/src/main/java/org/dive4elements/artifacts/ArtifactDatabase.java	Thu Jun 04 19:40:29 2020 +0200
+++ b/artifacts/src/main/java/org/dive4elements/artifacts/ArtifactDatabase.java	Wed Jul 15 11:53:27 2020 +0200
@@ -14,6 +14,7 @@
 import org.w3c.dom.Document;
 
 import java.util.Date;
+import java.util.Map;
 
 /**
  * Interface of an artifact managing database.
@@ -296,5 +297,10 @@
         throws ArtifactDatabaseException;
 
     String findArtifactUser(String artifactIdentifier);
+
+    String getServerAddress();
+
+    /** an additional list of rest services (path and implementing class) that should be registered */
+    Map<String, Class<?>> getRestServices();
 }
 // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :

http://dive4elements.wald.intevation.org