Package com.graphaware.tx.event.batch.data

Source Code of com.graphaware.tx.event.batch.data.BatchTransactionData

/*
* Copyright (c) 2013 GraphAware
*
* This file is part of GraphAware.
*
* GraphAware 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 com.graphaware.tx.event.batch.data;

import org.apache.commons.collections4.CollectionUtils;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.event.LabelEntry;
import org.neo4j.graphdb.event.PropertyEntry;
import org.neo4j.graphdb.event.TransactionData;
import org.neo4j.graphdb.event.TransactionEventHandler;

import java.util.*;

/**
* Simulation of {@link org.neo4j.graphdb.event.TransactionData} for non-transactional (batch) environments.
* It maintains information about changes performed on the database continuously and notifies the registered
* {@link org.neo4j.graphdb.event.TransactionEventHandler}s (simulates a transaction commit) when the number of
* mutations performed exceeds {@link #commitTxAfterMutations}.
*/
public class BatchTransactionData implements TransactionData {

    private final Set<Node> createdNodes = new HashSet<>();
    private final Map<IdAndKey, PropertyEntry<Node>> assignedNodeProperties = new HashMap<>();
    private final Map<IdAndKey, PropertyEntry<Node>> removedNodeProperties = new HashMap<>();
    private final Set<LabelEntry> assignedNodeLabels = new HashSet<>();
    private final Set<LabelEntry> removedNodeLabels = new HashSet<>();

    private final Set<Relationship> createdRelationships = new HashSet<>();
    private final Map<IdAndKey, PropertyEntry<Relationship>> assignedRelationshipProperties = new HashMap<>();
    private final Map<IdAndKey, PropertyEntry<Relationship>> removedRelationshipProperties = new HashMap<>();

    private final List<TransactionEventHandler> transactionEventHandlers = new LinkedList<>();
    private static final int COMMIT_TX_AFTER_MUTATIONS = 1000;
    private int numberOfMutations = 0;
    private int commitTxAfterMutations = COMMIT_TX_AFTER_MUTATIONS;
    private boolean commitInProgress = false;

    /**
     * Create a new instance of this transaction data with default {@link #commitTxAfterMutations} ({@link #COMMIT_TX_AFTER_MUTATIONS}.
     */
    public BatchTransactionData() {
    }

    /**
     * Create a new instance of this transaction data, providing the number of mutations before "commit" is simulated.
     *
     * @param commitTxAfterMutations number of mutations before commit is simulated.
     */
    public BatchTransactionData(int commitTxAfterMutations) {
        this.commitTxAfterMutations = commitTxAfterMutations;
    }

    /**
     * @see {@link org.neo4j.graphdb.GraphDatabaseService#registerTransactionEventHandler(org.neo4j.graphdb.event.TransactionEventHandler)}.
     */
    public <T> TransactionEventHandler<T> registerTransactionEventHandler(TransactionEventHandler<T> handler) {
        clear(); //event handlers should be registered in the beginning, so no modules should depend on the cleared data

        if (!transactionEventHandlers.contains(handler)) {
            transactionEventHandlers.add(handler);
        }
        return handler;
    }

    /**
     * Simulate a transaction commit.
     */
    public void simulateCommit() {
        if (noChangesOccurred()) {
            clear();
            return;
        }

        commitInProgress = true;

        try {
            Queue<Object> states = new LinkedList<>();

            for (TransactionEventHandler handler : transactionEventHandlers) {
                try {
                    states.add(handler.beforeCommit(this));
                } catch (RuntimeException e) {
                    throw e;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            for (TransactionEventHandler handler : transactionEventHandlers) {
                handler.afterCommit(this, states.poll());
            }

        } finally {
            clear();
            commitInProgress = false;
        }
    }

    private boolean noChangesOccurred() {
        return createdNodes.isEmpty()
                && assignedNodeProperties.isEmpty()
                && removedNodeProperties.isEmpty()
                && assignedNodeLabels.isEmpty()
                && removedNodeLabels.isEmpty()
                && createdRelationships.isEmpty()
                && assignedRelationshipProperties.isEmpty()
                && removedRelationshipProperties.isEmpty();
    }

    /**
     * Increment the number of mutations performed since the last "commit" and simulate a commit if needed.
     */
    private void incrementMutationsAndCommitIfNeeded() {
        if (++numberOfMutations >= commitTxAfterMutations) {
            simulateCommit();
        }
    }

    /**
     * Clear all transaction data.
     */
    private void clear() {
        numberOfMutations = 0;
        createdNodes.clear();
        assignedNodeProperties.clear();
        removedNodeProperties.clear();
        assignedNodeLabels.clear();
        removedNodeLabels.clear();
        createdRelationships.clear();
        assignedRelationshipProperties.clear();
        removedRelationshipProperties.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Node> createdNodes() {
        return Collections.unmodifiableCollection(createdNodes);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Node> deletedNodes() {
        return Collections.emptySet(); //batch mode - no deletions
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDeleted(Node node) {
        return false; //batch mode - no deletions
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<PropertyEntry<Node>> assignedNodeProperties() {
        return Collections.unmodifiableCollection(assignedNodeProperties.values());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<PropertyEntry<Node>> removedNodeProperties() {
        return Collections.unmodifiableCollection(removedNodeProperties.values());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<LabelEntry> assignedLabels() {
        return Collections.unmodifiableSet(assignedNodeLabels);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<LabelEntry> removedLabels() {
        return Collections.unmodifiableSet(removedNodeLabels);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Relationship> createdRelationships() {
        return Collections.unmodifiableCollection(createdRelationships);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<Relationship> deletedRelationships() {
        return Collections.emptySet()//batch mode - no deletions
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isDeleted(Relationship relationship) {
        return false; //batch mode - no deletions
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<PropertyEntry<Relationship>> assignedRelationshipProperties() {
        return Collections.unmodifiableCollection(assignedRelationshipProperties.values());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Iterable<PropertyEntry<Relationship>> removedRelationshipProperties() {
        return Collections.unmodifiableCollection(removedRelationshipProperties.values());
    }

    /**
     * Inform this object about a node that has been created.
     *
     * @param node that has been created.
     */
    public void nodeCreated(Node node) {
        if (commitInProgress) {
            return;
        }

        createdNodes.add(node);

        for (String key : node.getPropertyKeys()) {
            assignedNodeProperties.put(new IdAndKey(node.getId(), key),
                    PropertyEntryImpl.assigned(node, key, node.getProperty(key), null));
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a relationship that has been created.
     *
     * @param relationship that has been created.
     */
    public void relationshipCreated(Relationship relationship) {
        if (commitInProgress) {
            return;
        }

        createdRelationships.add(relationship);

        for (String key : relationship.getPropertyKeys()) {
            assignedRelationshipProperties.put(new IdAndKey(relationship.getId(), key),
                    PropertyEntryImpl.assigned(relationship, key, relationship.getProperty(key), null));
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a new value of a node's property about to be set. After it has been done,
     * {@link #nodePropertySet(org.neo4j.graphdb.Node, String, Object)} must be called.
     *
     * @param node  on which a new property value is about to be set.
     * @param key   of the property.
     * @param value of the property about to be set.
     */
    public void nodePropertyToBeSet(Node node, String key, Object value) {
        if (commitInProgress) {
            return;
        }

        IdAndKey idAndKey = new IdAndKey(node.getId(), key);

        Object previousValue;
        if (removedNodeProperties.containsKey(idAndKey)) {
            previousValue = removedNodeProperties.get(idAndKey).previouslyCommitedValue();
            removedNodeProperties.remove(idAndKey);
        } else if (assignedNodeProperties.containsKey(idAndKey)) {
            previousValue = assignedNodeProperties.get(idAndKey).previouslyCommitedValue();
        } else {
            previousValue = node.getProperty(key, null);
        }

        assignedNodeProperties.put(idAndKey, PropertyEntryImpl.assigned(node, key, value, previousValue));
    }

    /**
     * Inform this object about a new value of a node's property that has been set. Before this is called,
     * {@link #nodePropertyToBeSet(org.neo4j.graphdb.Node, String, Object)} must be called.
     *
     * @param node  on which a new property value has been set.
     * @param key   of the property.
     * @param value of the property set.
     */
    public void nodePropertySet(Node node, String key, Object value) {
        if (commitInProgress) {
            return;
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a new set of labels about to be assigned to a node. After it has been done,
     * {@link #nodeLabelsSet(org.neo4j.graphdb.Node, org.neo4j.graphdb.Label...)} must be called.
     *
     * @param node   to which a new set of labels is about to be assigned.
     * @param labels new labels.
     */
    public void nodeLabelsToBeSet(Node node, Label... labels) {
        if (commitInProgress) {
            return;
        }

        Set<Label> newLabels = new HashSet<>();
        for (Label label : labels) {
            newLabels.add(label);
        }

        Set<Label> existingLabels = new HashSet<>();
        for (Label label : node.getLabels()) {
            existingLabels.add(label);
        }

        Collection<Label> removedLabels = CollectionUtils.subtract(existingLabels, newLabels);
        Collection<Label> assignedLabels = CollectionUtils.subtract(newLabels, existingLabels);

        for (Label removedLabel : removedLabels) {
            LabelEntry entry = new LabelEntryImpl(removedLabel, node);
            assignedNodeLabels.remove(entry);
            removedNodeLabels.add(entry);
        }

        for (Label assignedLabel : assignedLabels) {
            LabelEntry entry = new LabelEntryImpl(assignedLabel, node);
            removedNodeLabels.remove(entry);
            assignedNodeLabels.add(entry);
        }
    }

    /**
     * Inform this object about a new set of labels assigned to a node. Before this is called,
     * {@link #nodeLabelsToBeSet(org.neo4j.graphdb.Node, org.neo4j.graphdb.Label...)} must be called.
     *
     * @param node   to which a new set of labels has been assigned.
     * @param labels new labels.
     */
    public void nodeLabelsSet(Node node, Label... labels) {
        if (commitInProgress) {
            return;
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a new value of a relationship's property about to be set. After it has been done,
     * {@link #relationshipPropertySet(org.neo4j.graphdb.Relationship, String, Object)}  must be called.
     *
     * @param relationship on which a new property value is about to be set.
     * @param key          of the property.
     * @param value        of the property about to be set.
     */
    public void relationshipPropertyToBeSet(Relationship relationship, String key, Object value) {
        if (commitInProgress) {
            return;
        }

        IdAndKey idAndKey = new IdAndKey(relationship.getId(), key);

        Object previousValue;
        if (removedRelationshipProperties.containsKey(idAndKey)) {
            previousValue = removedRelationshipProperties.get(idAndKey).previouslyCommitedValue();
            removedRelationshipProperties.remove(idAndKey);
        } else if (assignedRelationshipProperties.containsKey(idAndKey)) {
            previousValue = assignedRelationshipProperties.get(idAndKey).previouslyCommitedValue();
        } else {
            previousValue = relationship.getProperty(key, null);
        }

        assignedRelationshipProperties.put(idAndKey, PropertyEntryImpl.assigned(relationship, key, value, previousValue));
    }

    /**
     * Inform this object about a new value of a relationship's property that has been set. Before this is called,
     * {@link #relationshipPropertyToBeSet(org.neo4j.graphdb.Relationship, String, Object)}  must be called.
     *
     * @param relationship on which a new property value has been set.
     * @param key          of the property.
     * @param value        of the property set.
     */
    public void relationshipPropertySet(Relationship relationship, String key, Object value) {
        if (commitInProgress) {
            return;
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a node's property about to be removed. After is has been done,
     * {@link #nodePropertyRemoved(org.neo4j.graphdb.Node, String)}  must be called.
     *
     * @param node     whose property is about to be removed.
     * @param property about to be removed.
     */
    public void nodePropertyToBeRemoved(Node node, String property) {
        if (commitInProgress) {
            return;
        }

        IdAndKey idAndKey = new IdAndKey(node.getId(), property);

        if (removedNodeProperties.containsKey(idAndKey)) {
            return;
        }

        Object previousValue;
        if (assignedNodeProperties.containsKey(idAndKey)) {
            previousValue = assignedNodeProperties.get(idAndKey).previouslyCommitedValue();
            assignedNodeProperties.remove(idAndKey);

            if (previousValue == null) {
                return;
            }
        } else {
            previousValue = node.getProperty(property, null);
        }

        removedNodeProperties.put(idAndKey, PropertyEntryImpl.removed(node, property, previousValue));
    }

    /**
     * Inform this object about a node's property has been removed. Before this is called,
     * {@link #nodePropertyToBeRemoved(org.neo4j.graphdb.Node, String)} must be called.
     *
     * @param node     whose property has been removed.
     * @param property removed.
     */
    public void nodePropertyRemoved(Node node, String property) {
        if (commitInProgress) {
            return;
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Inform this object about a relationship's property about to be removed.  After it has been done,
     * {@link #relationshipPropertyRemoved(org.neo4j.graphdb.Relationship, String)}  must be called.
     *
     * @param relationship whose property is about to be removed.
     * @param property     about to be removed.
     */
    public void relationshipPropertyToBeRemoved(Relationship relationship, String property) {
        if (commitInProgress) {
            return;
        }

        IdAndKey idAndKey = new IdAndKey(relationship.getId(), property);

        if (removedRelationshipProperties.containsKey(idAndKey)) {
            return;
        }

        Object previousValue;
        if (assignedRelationshipProperties.containsKey(idAndKey)) {
            previousValue = assignedRelationshipProperties.get(idAndKey).previouslyCommitedValue();
            assignedRelationshipProperties.remove(idAndKey);

            if (previousValue == null) {
                return;
            }
        } else {
            previousValue = relationship.getProperty(property, null);
        }

        removedRelationshipProperties.put(idAndKey, PropertyEntryImpl.removed(relationship, property, previousValue));
    }

    /**
     * Inform this object about a relationship's property has been removed.  Before this is called,
     * {@link #relationshipPropertyToBeRemoved(org.neo4j.graphdb.Relationship, String)} must be called.
     *
     * @param relationship whose property has been removed.
     * @param property     removed.
     */
    public void relationshipPropertyRemoved(Relationship relationship, String property) {
        if (commitInProgress) {
            return;
        }

        incrementMutationsAndCommitIfNeeded();
    }

    /**
     * Key for maps, containing ID of a property container and key of a property.
     */
    private class IdAndKey {
        private final long id;
        private final String key;

        private IdAndKey(long id, String key) {
            this.id = id;
            this.key = key;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            IdAndKey idAndKey = (IdAndKey) o;

            if (id != idAndKey.id) return false;
            if (!key.equals(idAndKey.key)) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = (int) (id ^ (id >>> 32));
            result = 31 * result + key.hashCode();
            return result;
        }
    }

    /**
     * Implementation of {@link LabelEntry} (the Neo4j one is private).
     */
    private class LabelEntryImpl implements LabelEntry {
        private final Label label;
        private final Node node;

        private LabelEntryImpl(Label label, Node node) {
            this.label = label;
            this.node = node;
        }

        /**
         * {@inheritDoc
         */
        @Override
        public Label label() {
            return label;
        }

        /**
         * {@inheritDoc
         */
        @Override
        public Node node() {
            return node;
        }

        /**
         * {@inheritDoc
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            LabelEntryImpl that = (LabelEntryImpl) o;

            if (!label.equals(that.label)) return false;
            if (!node.equals(that.node)) return false;

            return true;
        }

        /**
         * {@inheritDoc
         */
        @Override
        public int hashCode() {
            int result = label.hashCode();
            result = 31 * result + node.hashCode();
            return result;
        }
    }
}
TOP

Related Classes of com.graphaware.tx.event.batch.data.BatchTransactionData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.