All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.dbms.prefs.DbPreferencesFactory Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org
 *
 * 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; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.dbms.prefs;

import org.tentackle.common.Service;
import org.tentackle.dbms.Db;
import org.tentackle.dbms.prefs.DbPreferencesOperation.RefreshInfo;
import org.tentackle.log.Logger;
import org.tentackle.misc.IdSerialTuple;
import org.tentackle.prefs.PersistedPreferencesFactory;
import org.tentackle.reflect.ReflectionHelper;
import org.tentackle.session.ModificationEvent;
import org.tentackle.session.ModificationEventDetail;
import org.tentackle.session.ModificationListenerAdapter;
import org.tentackle.session.ModificationTracker;
import org.tentackle.session.PersistenceException;
import org.tentackle.session.Session;
import org.tentackle.session.SessionUtilities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.prefs.BackingStoreException;
import java.util.prefs.NodeChangeEvent;
import java.util.prefs.NodeChangeListener;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;

/**
 * Repository and factory for database backed preferences.
 * 

* The DbPreferencesFactory implements {@link PersistedPreferencesFactory} which in turn * extends {@link java.util.prefs.PreferencesFactory}. As such, it can be used as a drop-in * replacement for the default JRE preferences factory. By default, however, it is available * to the application only via {@link PersistedPreferencesFactory#getInstance()}, which is the * preferred way for tentackle applications to use preferences. * * @author harald */ @Service(PersistedPreferencesFactory.class) public class DbPreferencesFactory implements PersistedPreferencesFactory { /** * Gets the factory singleton. * * @return the factory */ public static DbPreferencesFactory getInstance() { return (DbPreferencesFactory) PersistedPreferencesFactory.getInstance(); } private static final Logger LOGGER = Logger.get(DbPreferencesFactory.class); /** * Modification tracker name. */ private static final String TRACKER_NAME = "preferences"; /** * Listeners are managed by the repository, not the nodes. * This allows un- and reloading the nodes without losing the listeners. *

* Notice that when a node is removed, their listeners will not be removed. * So, if the node is added again, the listeners are still there. * Listeners have to be removed by the application explicitly. */ private static class ListenerKey { private final String user; // "" = system private final String path; private ListenerKey(DbPreferences node) { user = node.isUserNode() ? node.getUser() : ""; path = node.absolutePath(); } @Override public int hashCode() { int hash = 7; hash = 31 * hash + Objects.hashCode(this.user); hash = 31 * hash + Objects.hashCode(this.path); 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 ListenerKey other = (ListenerKey) obj; if (!Objects.equals(this.user, other.user)) { return false; } return Objects.equals(this.path, other.path); } } /** * The root node with all nodes and keys loaded. */ private static class Root { private static long lastSerial; // last serial private final DbPreferences node; // the root node holding the whole tree private long serial; // the serial version of the whole tree /** * Creates a root node. * * @param node the preferences node */ private Root(DbPreferences node) { this.node = node; countUpdate(); } private synchronized void countUpdate() { serial = ++lastSerial; } } private boolean autoSync; // auto-sync flag private boolean readOnly; // readonly flag private boolean systemOnly; // system-only flag private final Map roots; // loaded roots, key is username ("" for system) private final Map> nodeListeners; // listeners for added/removed nodes private final Map> prefListeners; // listeners for changed keys private long nodeTableSerial; // highest tableSerial of _all_ DbPreferencesNodes or remote repo private long keyTableSerial; // highest tableSerial of _all_ DbPreferencesKeys /** * Creates the factory. */ public DbPreferencesFactory() { roots = new HashMap<>(); nodeListeners = new HashMap<>(); prefListeners = new HashMap<>(); autoSync = true; Db db = requestDb(false, false); try { if (db.isRemote()) { nodeTableSerial = ModificationTracker.getInstance().getSerial(TRACKER_NAME); keyTableSerial = 0; ModificationTracker.getInstance().addModificationListener( new ModificationListenerAdapter(TRACKER_NAME) { @Override public void dataChanged(ModificationEvent ev) { if (isAutoSync()) { updateRepository((Db) ev.getSession(), ev.getSerial(), 0); } } }); } else { nodeTableSerial = new DbPreferencesNode(db).getModificationCount(); keyTableSerial = new DbPreferencesKey(db).getModificationCount(); ModificationTracker.getInstance().addModificationListener( new ModificationListenerAdapter(DbPreferencesNode.CLASSVARIABLES.tableName, DbPreferencesKey.CLASSVARIABLES.tableName) { @Override public void dataChanged(ModificationEvent ev) { long nodeMaxSerial = 0; long keyMaxSerial = 0; for (ModificationEventDetail detail : ev.getDetails()) { if (DbPreferencesNode.CLASSVARIABLES.tableName.equals(detail.getName())) { nodeMaxSerial = detail.getSerial(); } else if (DbPreferencesKey.CLASSVARIABLES.tableName.equals(detail.getName())) { keyMaxSerial = detail.getSerial(); } } if (isAutoSync()) { updateRepository((Db) ev.getSession(), nodeMaxSerial, keyMaxSerial); } } }); } } finally { releaseDb(db); } } @Override public synchronized void invalidate() { roots.clear(); } @Override public boolean isAutoSync() { return autoSync; } @Override public void setAutoSync(boolean autoSync) { this.autoSync = autoSync; } @Override public boolean isReadOnly() { return readOnly; } @Override public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } @Override public boolean isSystemOnly() { return systemOnly; } @Override public void setSystemOnly(boolean systemOnly) { this.systemOnly = systemOnly; } @Override public DbPreferences getSystemRoot() { return systemRoot(); } @Override public DbPreferences getUserRoot() { return isSystemOnly() ? systemRoot() : userRoot(); } @Override public DbPreferences userNodeForPackage(Class c) { return getUserRoot().node(nodeName(c)); } @Override public DbPreferences systemNodeForPackage(Class c) { return getSystemRoot().node(nodeName(c)); } @Override public DbPreferences systemRoot() { return getRootTree(null, null); } @Override public DbPreferences userRoot() { return getRootTree(null, getUserName()); } /** * Gets the current serial version of given user- or system root. * * @param user the username, null of "" if system * @return the serial, 0 if no such root loaded */ public long getSerial(String user) { String key = user == null ? "" : user; Root root = roots.get(key); return root == null ? 0 : root.serial; } /** * Adds a preferences changed listener. *

* Notice that when a node is removed, their listeners will not be removed. * So, if the node is added again, the listeners are still there. * Listeners have to be removed by the application explicitly. * * @param node the node * @param pcl the listener */ public synchronized void addPreferenceChangeListener(DbPreferences node, PreferenceChangeListener pcl) { ListenerKey lk = new ListenerKey(node); Set listeners = prefListeners.computeIfAbsent(lk, k -> new HashSet<>()); listeners.add(pcl); } /** * Removes a preferences changed listener. * * @param node the node * @param pcl the listener */ public synchronized void removePreferenceChangeListener(DbPreferences node, PreferenceChangeListener pcl) { ListenerKey lk = new ListenerKey(node); Set listeners = prefListeners.get(lk); if (listeners != null) { listeners.remove(pcl); if (listeners.isEmpty()) { prefListeners.remove(lk); } } } /** * Adds a node listener. *

* Notice that when a node is removed, their listeners will not be removed. * So, if the node is added again, the listeners are still there. * Listeners have to be removed by the application explicitly. * * @param node the node * @param ncl the listener */ public synchronized void addNodeChangeListener(DbPreferences node, NodeChangeListener ncl) { ListenerKey lk = new ListenerKey(node); Set listeners = nodeListeners.computeIfAbsent(lk, k -> new HashSet<>()); listeners.add(ncl); } /** * Removes a node listener. * * @param node the node * @param ncl the listener */ public synchronized void removeNodeChangeListener(DbPreferences node, NodeChangeListener ncl) { ListenerKey lk = new ListenerKey(node); Set listeners = nodeListeners.get(lk); if (listeners != null) { listeners.remove(ncl); if (listeners.isEmpty()) { nodeListeners.remove(lk); } } } /** * Invoke listeners when a node is added or removed. * * @param node the added or removed node */ public void nodeChanged(DbPreferences node) { DbPreferences parent = node.parent(); if (parent != null) { Set listeners = nodeListeners.get(new ListenerKey(parent)); if (listeners != null) { NodeChangeEvent ev = new NodeChangeEvent(node.parent(), node); for (NodeChangeListener listener: listeners) { if (node.getPersistentNode().isRemoved()) { listener.childRemoved(ev); } else { listener.childAdded(ev); } } } } if (node.isRootNode() && node.getPersistentNode().isRemoved()) { removeRoot(node); } } /** * Invoke listeners when a key is added, updated or removed. * * @param node the node holding the key * @param key the changed key */ public void keyChanged(DbPreferences node, DbPreferencesKey key) { Set listeners = prefListeners.get(new ListenerKey(node)); if (listeners != null) { PreferenceChangeEvent ev = new PreferenceChangeEvent(node, key.getKey(), key.isRemoved() ? null : key.getValue()); for (PreferenceChangeListener listener: listeners) { listener.preferenceChange(ev); } } } /** * Flushes a node and all of its sub-nodes to the backing store. * * @param node the preferences node * @param sync true if load modifications from storage before flush * @throws BackingStoreException if failed */ public synchronized void flush(DbPreferences node, boolean sync) throws BackingStoreException { Db db = requestDb(node.isUserNode(), true); try { boolean updated = db.transaction(() -> { try { return new DbPreferencesOperation(db).flush(node, sync, false); } catch (RuntimeException rex) { // remove the root node from the repo (forces reload on next access) removeRoot(node); throw rex; } }); if (updated) { LOGGER.warning("nodes where updated or deleted by another JVM"); } } catch (RuntimeException rex) { throw new BackingStoreException(rex); } finally { releaseDb(db); } } /** * Gets the root node for given user and all of its sub-nodes and keys at once. * * @param db the optional db, null if request a db temporarily * @param user the username, null or empty if system user * @return the root node, never null */ public synchronized DbPreferences getRootTree(Db db, String user) { String key = user == null ? "" : user; Root root = roots.get(key); if (root == null) { boolean needRelease = false; if (db == null) { db = requestDb(true, false); needRelease = true; } try { root = new Root(new DbPreferencesOperation(db).loadRootTree(user, !db.isRemote())); roots.put(key, root); } finally { if (needRelease) { releaseDb(db); } } } return root.node; } /** * Checks all preferences and tries to fix if possible.
* The method is provided since {@link DbPreferencesNode} and {@link DbPreferencesKey} provide no referential integrity * via database-enforced foreign keys, but only at application level.
* Invoke this method, whenever the database has been modified manually via SQL. * * @param db the session (must be local) */ public void checkAllPreferences(Db db) { db.assertNotRemote(); // must be a direct JDBC link db.transaction(() -> { Map nodeMap = new HashMap<>(); // map ID:Node Set processedNodes = new HashSet<>(); // already processed nodes Collection allNodes = new DbPreferencesNode(db).selectAllObjects(); Collection allKeys = new DbPreferencesKey(db).selectAllObjects(); LOGGER.info("processing {0} nodes and {1} keys", allNodes.size(), allKeys.size()); for (DbPreferencesNode node : allNodes) { nodeMap.put(node.getId(), node); } processPreferences(processedNodes, nodeMap, allKeys); allNodes.removeAll(processedNodes); for (DbPreferencesNode node: allNodes) { LOGGER.severe("orphan node found: {0}", node); } for (DbPreferencesKey key: allKeys) { LOGGER.severe("orphan key found: {0}", key); } return null; }); } /** * Returns the absolute path name of the node corresponding to the package of the specified object. * * @param clazz the class * @return the path name * @throws IllegalArgumentException if the package has no preferences node associated with it */ protected String nodeName(Class clazz) { if (clazz.isArray()) { throw new IllegalArgumentException("Arrays have no associated preferences node"); } return ReflectionHelper.getPackagePathName(clazz); } /** * Removes a root node of given node from the repository. * * @param node the root node or one of its sub-nodes */ protected synchronized void removeRoot(DbPreferences node) { String user = node.getUser(); if (user == null) { user = ""; } roots.remove(user); } /** * Updates the repository.
* Invoked whenever the data in prefnode or prefkey has changed. * * @param db the session * @param nodeMaxSerial the new table serial for prefnodes * @param keyMaxSerial the new table serial for prefkeys */ protected synchronized void updateRepository(Db db, long nodeMaxSerial, long keyMaxSerial) { if (nodeMaxSerial != nodeTableSerial || keyMaxSerial != keyTableSerial) { if (db.isRemote()) { // rebuild all roots if serials don't match with the ones in the remote repository for (Root root: roots.values()) { rebuildRoot(db, root); } } else { // get tableserial/id-pairs boolean someRemoved = false; List nodeExpireSet = null; List keyExpireSet = null; if (nodeMaxSerial > 0) { nodeExpireSet = new DbPreferencesNode(db).getExpiredTableSerials(nodeTableSerial, nodeMaxSerial); if (SessionUtilities.getInstance().isSomeRemoved(nodeTableSerial, nodeExpireSet, nodeMaxSerial)) { someRemoved = true; } } if (!someRemoved && keyMaxSerial > 0) { keyExpireSet = new DbPreferencesKey(db).getExpiredTableSerials(keyTableSerial, keyMaxSerial); if (SessionUtilities.getInstance().isSomeRemoved(keyTableSerial, keyExpireSet, keyMaxSerial)) { someRemoved = true; } } Set usersModified = new HashSet<>(); // roots affected, "" is system // TreeSet to sort by id (because children are always added _after_ their parents, see flushImpl) Set changedNodes = new TreeSet<>(); Set changedKeys = new TreeSet<>(); Map idNodeMap = new HashMap<>(); // related nodes map by ID if (!someRemoved) { if (nodeExpireSet != null) { // figure out roots to update List prefNodes = new DbPreferencesNode(db).selectObjectsWithExpiredTableSerials(nodeTableSerial); if (prefNodes.size() == nodeExpireSet.size()) { for (DbPreferencesNode prefNode : prefNodes) { changedNodes.add(prefNode); idNodeMap.put(prefNode.getId(), prefNode); String user = prefNode.getUser(); usersModified.add(user == null ? "" : user); } } else { // removed in the meantime??? someRemoved = true; } } if (!someRemoved && keyExpireSet != null) { // update keys List prefKeys = new DbPreferencesKey(db).selectObjectsWithExpiredTableSerials(keyTableSerial); if (prefKeys.size() == keyExpireSet.size()) { for (DbPreferencesKey prefKey : prefKeys) { changedKeys.add(prefKey); DbPreferencesNode prefNode = idNodeMap.get(prefKey.getNodeId()); if (prefNode == null) { prefNode = new DbPreferencesNode(db).selectObject(prefKey.getNodeId()); if (prefNode != null) { idNodeMap.put(prefNode.getId(), prefNode); } } if (prefNode != null) { changedNodes.add(prefNode); // add even if node itself wasn't modified bec. of listeners String user = prefNode.getUser(); usersModified.add(user == null ? "" : user); } } } else { someRemoved = true; } } } if (someRemoved) { // rebuild all roots for (Root root: roots.values()) { rebuildRoot(db, root); } } else { // only added or updated nodes or keys: update roots for (String user: usersModified) { Root root = roots.get(user); if (root != null) { updateRoot(root, changedNodes, changedKeys); } } } } nodeTableSerial = nodeMaxSerial; keyTableSerial = keyMaxSerial; if (!db.isRemote()) { ModificationTracker.getInstance().countModification(db, TRACKER_NAME); } } } /** * Requests for a session to work with.
* By default, the thread-local session is used. * If there is no default session, the modification tracker's session is used. *

* In preparation for replication layers, a requested session must be released after use. * * @param forUser true if a user session is required (no modification tracker fallback) * @param forWrite true if the session is used for writing (flush), else read * @return the session */ protected Db requestDb(boolean forUser, boolean forWrite) { Db db = (Db) Session.getCurrentSession(); if (db == null) { if (forUser) { throw new PersistenceException("no thread-local session for " + Thread.currentThread()); } // no thread-local session: use the one from the mod tracker db = (Db) ModificationTracker.getInstance().getSession(); if (db == null) { throw new PersistenceException("no session for " + ModificationTracker.getInstance()); } } return db; } /** * Releases the session after use. * * @param db the session */ protected void releaseDb(Db db) { // default does nothing } /** * Gets the current username. * * @return the username */ protected String getUserName() { return requestDb(true, false).getSessionInfo().getUserName(); } /** * Rebuilds the given root from scratch.
* Reloads all nodes and keys. * * @param db the session * @param root the root node */ private void rebuildRoot(Db db, Root root) { // load all nodes and keys for this root (ordered by id, i.e. creation) RefreshInfo info = new DbPreferencesOperation(db).loadRefreshInfo(root.node, db.isRemote() ? root.serial : 0); if (info != null) { Collection prefNodes = info.prefNodes; Collection prefKeys = info.prefKeys; // to speed up: build maps of ID:node and ID:key Map idNodeMap = new HashMap<>(); Map pathNodeMap = new HashMap<>(); Map idKeyMap = new HashMap<>(); addNodeToMaps(root.node, idNodeMap, pathNodeMap, idKeyMap); // add or update nodes for (DbPreferencesNode prefNode : prefNodes) { DbPreferences node = pathNodeMap.get(prefNode.getName()); if (node == null) { // node is new DbPreferences parent = idNodeMap.get(prefNode.getParentId()); // must exist bec. nodes are sorted by id if (parent == null) { // must exist, because prefNodes are sorted by id! LOGGER.severe("parent ID={0} missing for node ID={1}", prefNode.getParentId(), prefNode.getId()); removeRoot(root.node); // force reload next access } else { node = new DbPreferences(parent, prefNode, null, null); parent.addChild(node); idNodeMap.put(prefNode.getId(), node); nodeChanged(node); } } else { // just id,serial or tableserial changed if (node.updatePersistentNode(prefNode)) { nodeChanged(node); idNodeMap.put(prefNode.getId(), node); // in case ID has changed (node was new) } } } // add or update keys for (DbPreferencesKey prefKey: prefKeys) { DbPreferences node = idNodeMap.get(prefKey.getNodeId()); // must exist now! if (node == null) { LOGGER.severe("node ID={0} missing for key ID={1}", prefKey.getNodeId(), prefKey.getId()); removeRoot(root.node); // force reload next access } else { boolean updated; DbPreferencesKey key = node.getPersistentKey(prefKey.getKey()); if (key == null) { node.putPersistentKey(prefKey); idKeyMap.put(prefKey.getId(), prefKey); updated = true; } else { updated = node.updatePersistentKey(prefKey); } if (updated) { keyChanged(node, prefKey); } } } // remove keys Set persistedKeys = new HashSet<>(prefKeys); for (DbPreferencesKey prefKey: idKeyMap.values()) { if (!prefKey.isNew() && !persistedKeys.contains(prefKey)) { // if not created in this JVM DbPreferences node = idNodeMap.get(prefKey.getNodeId()); if (node != null) { prefKey = node.removePersistentKey(prefKey.getKey()); if (prefKey != null) { keyChanged(node, prefKey); } } } } // remove nodes Set persistedNodes = new HashSet<>(prefNodes); for (DbPreferences node: idNodeMap.values()) { if (!node.getPersistentNode().isNew() && !persistedNodes.contains(node.getPersistentNode())) { // if not created in this JVM DbPreferences parent = node.parent(); if (parent != null) { node = parent.removeChild(node.name()); if (node != null) { nodeChanged(node); } } } } if (db.isRemote()) { root.serial = info.serial; } else { root.countUpdate(); } } } /** * Updates a root node. * * @param root the root preferences node * @param changedNodes the changed nodes * @param changedKeys the set of changes keys */ private void updateRoot(Root root, Set changedNodes, Set changedKeys) { // to speed up: build maps of ID:node and ID:key Map idNodeMap = new HashMap<>(); Map pathNodeMap = new HashMap<>(); addNodeToMaps(root.node, idNodeMap, pathNodeMap); long rootId = root.node.getPersistentNode().getId(); if (rootId == 0) { // special handling if this is the first node for this user at all: // in this case the changedNodes contain the root id for (DbPreferencesNode node: changedNodes) { if (Objects.equals(root.node.getUser(), node.getUser())) { rootId = node.getRootNodeId(); break; } } } // add or update nodes for (DbPreferencesNode prefNode: changedNodes) { if (prefNode.getRootNodeId() == rootId) { // only nodes belonging to this root DbPreferences node = pathNodeMap.get(prefNode.getName()); if (node == null) { // add node DbPreferences parent = idNodeMap.get(prefNode.getParentId()); // must exist bec. nodes are sorted by id if (parent != null) { node = new DbPreferences(parent, prefNode, null, null); parent.addChild(node); idNodeMap.put(prefNode.getId(), node); nodeChanged(node); } else { LOGGER.warning("parent node ID=" + prefNode.getParentId() + " vanished for node " + prefNode); } } else { if (node.updatePersistentNode(prefNode)) { nodeChanged(node); idNodeMap.put(prefNode.getId(), node); // in case ID changed } } } } // now that all nodes are added: add or update the keys for (DbPreferencesKey prefKey: changedKeys) { if (prefKey.getRootNodeId() == rootId) { DbPreferences node = idNodeMap.get(prefKey.getNodeId()); // must exist now if (node != null) { if (node.updatePersistentKey(prefKey)) { keyChanged(node, prefKey); } } else { LOGGER.warning("node ID=" + prefKey.getNodeId() + " vanished for key " + prefKey); } } } root.countUpdate(); } /** * Adds all sub-nodes and keys of a node to given maps. * * @param node the node * @param idNodeMap the nodes mapped by ID * @param pathNodeMap the nodes mapped by path * @param idKeyMap the keys mapped by ID */ private void addNodeToMaps(DbPreferences node, Map idNodeMap, Map pathNodeMap, Map idKeyMap) { if (!node.getPersistentNode().isVirgin()) { idNodeMap.put(node.getPersistentNode().getId(), node); } pathNodeMap.put(node.absolutePath(), node); for (DbPreferencesKey key: node.getKeys()) { if (!key.isVirgin()) { idKeyMap.put(key.getId(), key); } } for (DbPreferences child: node.getChildren()) { addNodeToMaps(child, idNodeMap, pathNodeMap, idKeyMap); } } /** * Adds all sub-nodes of a node to given maps. * * @param node the node * @param idNodeMap the nodes mapped by ID * @param pathNodeMap the nodes mapped by path */ private void addNodeToMaps(DbPreferences node, Map idNodeMap, Map pathNodeMap) { if (!node.getPersistentNode().isVirgin()) { idNodeMap.put(node.getPersistentNode().getId(), node); } pathNodeMap.put(node.absolutePath(), node); for (DbPreferences child: node.getChildren()) { addNodeToMaps(child, idNodeMap, pathNodeMap); } } private void processPreferences(Set processedNodes, Map nodeMap, Collection allKeys) { // check root nodes for (DbPreferencesNode node : nodeMap.values()) { if (node.getParentId() == 0 && node.getRootNodeId() != node.getId()) { node.setRootNodeId(node.getId()); node.saveObject(); LOGGER.info("fixed rootNodeId of root-node {0} to {1}", node, node.getRootNodeId()); } } // process parent chain to root for (DbPreferencesNode node: nodeMap.values()) { if (node.getParentId() != 0) { DbPreferencesNode root = findRoot(nodeMap, node); if (root == null) { LOGGER.severe("missing root for node {0}", node.getId()); } else { // go down all sub-nodes and keys and set the rootNodeId if missing processNode(processedNodes, nodeMap.values(), allKeys, root); } } } } private void processNode(Set processedNodes, Collection allNodes, Collection allKeys, DbPreferencesNode node) { if (!processedNodes.contains(node)) { for (DbPreferencesKey key: findKeys(allKeys, node)) { if (key.getRootNodeId() != node.getRootNodeId()) { key.setRootNodeId(node.getRootNodeId()); key.saveObject(); LOGGER.info("fixed rootNodeId of key {0} to {1}", key, key.getRootNodeId()); } } for (DbPreferencesNode child: findChildren(allNodes, node)) { if (child.getRootNodeId() != node.getRootNodeId()) { child.setRootNodeId(node.getRootNodeId()); child.saveObject(); LOGGER.info("fixed rootNodeId of node {0} to {1}", child, child.getRootNodeId()); } processNode(processedNodes, allNodes, allKeys, child); } processedNodes.add(node); } } private DbPreferencesNode findRoot(Map nodeMap, DbPreferencesNode node) { while (node != null && node.getParentId() != 0) { node = nodeMap.get(node.getParentId()); } return node; } private Collection findChildren(Collection allNodes, DbPreferencesNode node) { Collection children = new ArrayList<>(); for (DbPreferencesNode child: allNodes) { if (child.getParentId() == node.getId()) { children.add(child); } } return children; } private Collection findKeys(Collection allKeys, DbPreferencesNode node) { Collection keys = new ArrayList<>(); for (Iterator iter = allKeys.iterator(); iter.hasNext(); ) { DbPreferencesKey key = iter.next(); if (key.getNodeId() == node.getId()) { keys.add(key); iter.remove(); } } return keys; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy