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

com.graphaware.relcount.common.internal.node.BaseRelationshipCountCachingNode Maven / Gradle / Ivy

package com.graphaware.relcount.common.internal.node;

import com.graphaware.framework.NeedsInitializationException;
import com.graphaware.propertycontainer.dto.common.relationship.SerializableTypeAndDirection;
import org.apache.log4j.Logger;
import org.neo4j.graphdb.Node;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Base-class for {@link BaseRelationshipCountCachingNode} implementations. For efficiency, it keeps track of all the
 * cached counts changes and only writes them to the underlying {@link Node} once.
 */
public abstract class BaseRelationshipCountCachingNode {

    private static final Logger LOG = Logger.getLogger(BaseRelationshipCountCachingNode.class);

    protected final Node node;
    protected final String prefix;
    protected final String separator;

    private final Map cachedCounts;
    private final Set updatedCounts;
    private final Set removedCounts;

    /**
     * Construct a new caching node.
     *
     * @param node      backing Neo4j node.
     * @param prefix    of the cached relationship string representation.
     * @param separator of information in the cached relationship string representation.
     */
    protected BaseRelationshipCountCachingNode(Node node, String prefix, String separator) {
        this.node = node;
        this.prefix = prefix;
        this.separator = separator;

        cachedCounts = newMap();
        for (String key : node.getPropertyKeys()) {
            if (key.startsWith(prefix)) {
                cachedCounts.put(newCachedRelationship(key), (Integer) node.getProperty(key));
            }
        }

        updatedCounts = new HashSet<>();
        removedCounts = new HashSet<>();
    }

    /**
     * Create a new empty map that cached counts will be put into.
     *
     * @return new map.
     */
    protected abstract Map newMap();

    /**
     * Create a cached relationship representation from a String representation of the cached relationship, coming from
     * a node's property key.
     *
     * @param string string representation of the cached relationship.
     * @return object representation of the cached relationship.
     */
    protected abstract CACHED newCachedRelationship(String string);

    /**
     * @see {@link RelationshipCountCachingNode#getId()}
     */
    public long getId() {
        return node.getId();
    }

    /**
     * @see {@link RelationshipCountCachingNode#getCachedCounts()}
     */
    public Map getCachedCounts() {
        return Collections.unmodifiableMap(cachedCounts);
    }

    /**
     * @see {@link RelationshipCountCachingNode#incrementCount(com.graphaware.propertycontainer.dto.common.relationship.HasTypeAndDirection, int)}
     */
    public final void incrementCount(CACHED relationship, int delta) {
        for (CACHED cachedRelationship : cachedCounts.keySet()) {
            if (cachedMatch(cachedRelationship, relationship)) {
                int newValue = cachedCounts.get(cachedRelationship) + delta;
                put(cachedRelationship, newValue);
                flush();
                return;
            }
        }

        put(relationship, delta);
        propertyCreated();
        flush();
    }

    /**
     * Allow subclasses to react to the fact that a new internal property has been created on a node.
     */
    protected void propertyCreated() {
        //allow subclasses to react
    }

    /**
     * @see {@link RelationshipCountCachingNode#decrementCount(com.graphaware.propertycontainer.dto.common.relationship.HasTypeAndDirection, int)}
     */
    public final void decrementCount(CACHED relationship, int delta) {
        for (CACHED cachedRelationship : cachedCounts.keySet()) {
            if (cachedMatch(cachedRelationship, relationship)) {
                int newValue = cachedCounts.get(cachedRelationship) - delta;
                put(cachedRelationship, newValue);

                if (newValue <= 0) {
                    delete(cachedRelationship);
                }

                if (newValue < 0) {
                    LOG.warn(cachedRelationship.toString() + " was out of sync on node " + node.getId());
                    throw new NeedsInitializationException(cachedRelationship.toString() + " was out of sync on node " + node.getId());
                }

                flush();
                return;
            }
        }

        LOG.warn(relationship.toString() + " was not present on node " + node.getId());
        throw new NeedsInitializationException(relationship.toString() + " was not present on node " + node.getId());
    }

    /**
     * Should the cached relationship be affected (incremented, decremented) by the given relationship representation?
     *
     * @param cached       cached relationship that could correspond to the given relationship being updated.
     * @param relationship representation being updated.
     * @return true iff the cached relationship is to be treated as the about-to-be-updated relationship's representation.
     */
    protected abstract boolean cachedMatch(CACHED cached, CACHED relationship);

    /**
     * @see {@link RelationshipCountCachingNode#deleteCount(com.graphaware.propertycontainer.dto.common.relationship.HasTypeAndDirection)}
     */
    public final void deleteCount(CACHED relationship) {
        delete(relationship);
        flush();
    }

    /**
     * Update the value (count) of a cached relationship.
     *
     * @param cachedRelationship to update.
     * @param value              new value.
     */
    private void put(CACHED cachedRelationship, int value) {
        cachedCounts.put(cachedRelationship, value);
        updatedCounts.add(cachedRelationship);
        removedCounts.remove(cachedRelationship);
    }

    /**
     * Delete a cached relationship.
     *
     * @param cachedRelationship to update.
     */
    private void delete(CACHED cachedRelationship) {
        cachedCounts.remove(cachedRelationship);
        updatedCounts.remove(cachedRelationship);
        removedCounts.add(cachedRelationship);
    }

    /**
     * Write all the changes to cached counts to the underlying node.
     */
    private void flush() {
        for (CACHED updated : updatedCounts) {
            node.setProperty(updated.toString(prefix, separator), cachedCounts.get(updated));
        }

        for (CACHED removed : removedCounts) {
            node.removeProperty(removed.toString(prefix, separator));
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy