com.steelbridgelabs.oss.neo4j.structure.providers.DatabaseSequenceElementIdProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-gremlin-bolt Show documentation
Show all versions of neo4j-gremlin-bolt Show documentation
SteelBridge Labs Neo4J Gremlin (Bolt) integration
/*
* Copyright 2016 SteelBridge Laboratories, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* For more information: http://steelbridgelabs.com
*/
package com.steelbridgelabs.oss.neo4j.structure.providers;
import com.steelbridgelabs.oss.neo4j.structure.Neo4JElementIdProvider;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.types.Entity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
/**
* {@link Neo4JElementIdProvider} implementation based on a sequence generator stored in a Neo4J database Node.
*/
public class DatabaseSequenceElementIdProvider implements Neo4JElementIdProvider {
private static final Logger logger = LoggerFactory.getLogger(DatabaseSequenceElementIdProvider.class);
public static final String DefaultIdFieldName = "id";
public static final String DefaultSequenceNodeLabel = "UniqueIdentifierGenerator";
public static final long DefaultPoolSize = 1000;
private final Driver driver;
private final String idFieldName;
private final String sequenceNodeLabel;
private final long poolSize;
private final AtomicLong atomicLong = new AtomicLong(0L);
private final Object monitor = new Object();
private AtomicLong maximum = new AtomicLong(0L);
public DatabaseSequenceElementIdProvider(Driver driver) {
Objects.requireNonNull(driver, "driver cannot be null");
// initialize fields
this.driver = driver;
this.poolSize = DefaultPoolSize;
this.idFieldName = DefaultIdFieldName;
this.sequenceNodeLabel = DefaultSequenceNodeLabel;
}
public DatabaseSequenceElementIdProvider(Driver driver, long poolSize, String idFieldName, String sequenceNodeLabel) {
Objects.requireNonNull(driver, "driver cannot be null");
Objects.requireNonNull(idFieldName, "idFieldName cannot be null");
Objects.requireNonNull(sequenceNodeLabel, "sequenceNodeLabel cannot be null");
// initialize fields
this.driver = driver;
this.poolSize = poolSize;
this.idFieldName = idFieldName;
this.sequenceNodeLabel = sequenceNodeLabel;
}
/**
* Gets the field name used for {@link Entity} identifier.
*
* @return The field name used for {@link Entity} identifier or null
if not using field for identifier.
*/
@Override
public String fieldName() {
return idFieldName;
}
/**
* Gets the identifier value from a neo4j {@link Entity}.
*
* @param entity The neo4j {@link Entity}.
* @return The neo4j {@link Entity} identifier.
*/
@Override
public Long get(Entity entity) {
Objects.requireNonNull(entity, "entity cannot be null");
// return property value
return entity.get(idFieldName).asLong();
}
/**
* Generates a new identifier value. This {@link Neo4JElementIdProvider} will fetch a pool of identifiers
* from a Neo4J database Node.
*
* @return A unique identifier within the database sequence generator.
*/
@Override
public Long generate() {
// get maximum identifier we can use (before obtaining new identifier to make sure it is in the current pool)
long max = maximum.get();
// generate new identifier
long identifier = atomicLong.incrementAndGet();
// check we need to obtain new identifier pool (identifier is out of range for current pool)
if (identifier > max) {
// loop until we get an identifier value
do {
// log information
logger.debug("About to request a pool of identifiers from database, maximum id: {}", max);
// make sure only one thread gets a new range of identifiers
synchronized (monitor) {
// update maximum number in pool, do not switch the next two statements (in case another thread was executing the synchronized block while the current thread was waiting)
max = maximum.get();
identifier = atomicLong.incrementAndGet();
// verify a new identifier is needed (compare it with current maximum)
if (identifier >= max) {
// create database session
try (Session session = driver.session()) {
// create transaction
try (Transaction transaction = session.beginTransaction()) {
// execute statement
Result result = transaction.run("MERGE (g:`" + sequenceNodeLabel + "`) ON CREATE SET g.nextId = 1 ON MATCH SET g.nextId = g.nextId + $poolSize RETURN g.nextId", Collections.singletonMap("poolSize", poolSize));
// process result
if (result.hasNext()) {
// get record
Record record = result.next();
// get nextId value
long nextId = record.get(0).asLong();
// set value for next identifier (do not switch the next two statements!)
atomicLong.set(nextId - poolSize);
maximum.set(nextId);
}
// commit
transaction.commit();
}
}
// update maximum number in pool
max = maximum.get();
// get a new identifier
identifier = atomicLong.incrementAndGet();
// log information
if (logger.isDebugEnabled())
logger.debug("Requested new pool of identifiers from database, current id: {}, maximum id: {}", identifier, max);
}
else if (logger.isDebugEnabled())
logger.debug("No need to request pool of identifiers, current id: {}, maximum id: {}", identifier, max);
}
}
while (identifier > max);
}
else if (logger.isDebugEnabled())
logger.debug("Current identifier: {}", identifier);
// return identifier
return identifier;
}
/**
* Process the given identifier converting it to the correct type if necessary.
*
* @param id The {@link org.apache.tinkerpop.gremlin.structure.Element} identifier.
* @return The {@link org.apache.tinkerpop.gremlin.structure.Element} identifier converted to the correct type if necessary.
*/
@Override
public Long processIdentifier(Object id) {
Objects.requireNonNull(id, "Element identifier cannot be null");
// check for Long
if (id instanceof Long)
return (Long)id;
// check for numeric types
if (id instanceof Number)
return ((Number)id).longValue();
// check for string
if (id instanceof String)
return Long.valueOf((String)id);
// error
throw new IllegalArgumentException(String.format("Expected an id that is convertible to Long but received %s", id.getClass()));
}
/**
* Gets the MATCH WHERE predicate operand.
*
* @param alias The neo4j {@link Entity} alias in a MATCH statement.
* @return The MATCH WHERE predicate operand.
*/
@Override
public String matchPredicateOperand(String alias) {
Objects.requireNonNull(alias, "alias cannot be null");
// alias.identifier
return alias + "." + idFieldName;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy