/* Copyright (C) 2016 maik.jablonski@jease.org This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package jease.cmf.service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Supplier; import jease.cmf.domain.Node; import jease.cmf.domain.NodeException; import jfix.db4o.Database; import org.apache.commons.lang3.ArrayUtils; /** * Static utility to ease the persistence-handling of Nodes. * * Each Jease-Repository has one special Node called "root". The root is the * only Node in a repository which doesn't have a parent node. All Nodes which * are not the root and don't have a parent, are deleted when a persistence * operation is performed. */ public class Nodes { private static Node root = queryRoot(); private static Supplier<Map<String, Node>> nodesByPath = ConcurrentHashMap::new; /** * Returns the root node derived by a database-query. */ public static Node queryRoot() { return Database.queryUnique(Node.class, $node -> $node.getParent() == null); } /** * Sets the root-node for a repository. This method should only be called * once to initialize a repository. */ public static void setRoot(Node rootNode) { root = rootNode; } /** * Returns the root-node of the repository. A root node is the only node in * a repository which doesn't have a parent. */ public static Node getRoot() { return root; } /** * Returns the system time (milliseconds) of the last change in database. */ public static long queryLastChange() { return Database.ext().getTimestamp(); } /** * Returns true if given node is root or attached to a parent. */ public static boolean isRooted(Node node) { return node == root || ArrayUtils.contains(node.getParents(), root); } /** * Returns node from root by given path. */ public static Node getByPath(String path) { if (root == null) { return null; } Map<String, Node> cache = Database.query(nodesByPath); if (!cache.containsKey(path)) { Node node = root.getChild(path); if (node != null) { cache.put(path, node); } } return cache.get(path); } /** * Appends given child to given node and saves changes to database. * * Please note: appending a child to a node automatically removes the child * from the former container. */ public static void append(Node node, Node child) throws NodeException { node.validateChild(child, child.getId()); node.appendChild(child); Nodes.save(node); } /** * Appends given children to given node and save changes to database. * * Please note: appending a child to a node automatically removes the child * from the former container. */ public static void append(Node node, Node[] children) throws NodeException { for (Node child : children) { node.validateChild(child, child.getId()); } node.appendChildren(children); Nodes.save(node); } /** * Saves all changes of a given node to database. */ public static void save(Node node) { Processor.save.accept(node); } /** * Deletes given node from repository. */ public static void delete(Node node) { Processor.delete.accept(node); } /** * Inner class to customize save & delete operations. Calls to save / delete * are delegated to Processor. Set a customized processor for save / delete * / traverse for logging, workflow, etc.pp. */ public static class Processor { private static Save save = new Save(); private static Delete delete = new Delete(); private static Traverse traverse = new Traverse(); public static class Save implements Consumer<Node> { public void accept(final Node node) { Database.write(() -> { node.markChanged(); root.processChangedNodes(traverse); }); } } public static class Delete implements Consumer<Node> { public void accept(final Node node) { Database.write(() -> { node.detach(); root.processChangedNodes(traverse); }); } } public static class Traverse implements Consumer<Node> { public void accept(Node node) { if (isRooted(node)) { Database.save(node); } else { Database.ext().deleteDeliberately(node); } } } public static void set(Save processor) { save = processor; } public static void set(Delete processor) { delete = processor; } public static void set(Traverse processor) { traverse = processor; } } }