Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.fnklabs.draenei.orm.DataProvider Maven / Gradle / Ivy
package com.fnklabs.draenei.orm;
import com.codahale.metrics.Timer;
import com.datastax.driver.core.*;
import com.datastax.driver.core.exceptions.SyntaxError;
import com.datastax.driver.core.querybuilder.Delete;
import com.datastax.driver.core.querybuilder.Insert;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.fnklabs.draenei.CassandraClient;
import com.fnklabs.draenei.ExecutorServiceFactory;
import com.fnklabs.draenei.MetricsFactory;
import com.fnklabs.draenei.orm.exception.CanNotBuildEntryCacheKey;
import com.fnklabs.draenei.orm.exception.MetadataException;
import com.fnklabs.draenei.orm.exception.QueryException;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.function.Function;
public class DataProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(DataProvider.class);
/**
* hashing function to build Entity hash code
*/
private static final HashFunction HASH_FUNCTION = Hashing.murmur3_128();
/**
* Map for saving DataProviders by DataProvider class
*/
private static final Map DATA_PROVIDERS_REGISTRY = new ConcurrentHashMap<>();
/**
* Entity class
*/
@NotNull
private final Class clazz;
/**
* Current entity metadata
*/
@NotNull
private final EntityMetadata entityMetadata;
@NotNull
private final CassandraClientFactory cassandraClient;
@NotNull
private final MetricsFactory metricsFactory;
@NotNull
private final Function mapToObjectFunction;
@NotNull
private final ExecutorService executorService;
/**
* Construct provider
*
* @param clazz Entity class
* @param cassandraClientFactory CassandraClientFactory instance
* @param executorService ExecutorService that will be used for processing ResultSetFuture to occupy CassandraDriver ThreadPool
* @param metricsFactory MetricsFactory MetricsFactory instance
*/
public DataProvider(@NotNull Class clazz,
@NotNull CassandraClientFactory cassandraClientFactory,
@NotNull ExecutorService executorService,
@NotNull MetricsFactory metricsFactory) {
this.clazz = clazz;
this.cassandraClient = cassandraClientFactory;
this.executorService = executorService;
this.metricsFactory = metricsFactory;
this.entityMetadata = build(clazz);
this.mapToObjectFunction = new MapToObjectFunction<>(clazz, entityMetadata);
}
public DataProvider(@NotNull Class clazz,
@NotNull CassandraClientFactory cassandraClient,
@NotNull MetricsFactory metricsFactory) {
this.clazz = clazz;
this.cassandraClient = cassandraClient;
this.metricsFactory = metricsFactory;
this.executorService = ExecutorServiceFactory.DEFAULT_EXECUTOR;
this.entityMetadata = build(clazz);
this.mapToObjectFunction = new MapToObjectFunction<>(clazz, entityMetadata);
}
/**
* Get DataProvider service
*
* @param clazz DataProvider class
* @param cassandraClientFactory CassandraClientFactory instance
* @param metricsFactory MetricsFactory instance
* @param DataProvider class type
*
* @return DataProvider instance
*/
public static DataProvider getDataProvider(Class clazz,
@NotNull CassandraClientFactory cassandraClientFactory,
@NotNull MetricsFactory metricsFactory) {
return DATA_PROVIDERS_REGISTRY.compute(clazz, (dataProviderClass, dataProvider) -> {
if (dataProvider == null) {
return new DataProvider(clazz, cassandraClientFactory, metricsFactory);
}
return dataProvider;
});
}
/**
* Build hash code for entity
*
* @param entity Input entity
*
* @return Cache key
*/
public long buildHashCode(@NotNull V entity) {
Timer.Context timer = getMetricsFactory().getTimer(MetricsType.DATA_PROVIDER_CREATE_KEY).time();
int primaryKeysSize = getEntityMetadata().getPrimaryKeysSize();
List keys = new ArrayList<>();
for (int i = 0; i < primaryKeysSize; i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (primaryKey.isPresent()) {
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
Object value = primaryKeyMetadata.readValue(entity);
keys.add(value);
}
}
long hashCode = buildHashCode(keys);
timer.stop();
return hashCode;
}
/**
* Save entity asynchronously
*
* @param entity Target entity
*
* @return Operation status result
*/
public ListenableFuture saveAsync(@NotNull V entity) {
Timer.Context saveAsyncTimer = metricsFactory.getTimer(MetricsType.DATA_PROVIDER_SAVE).time();
Insert insert = QueryBuilder.insertInto(getEntityMetadata().getTableName());
List columns = getEntityMetadata().getFieldMetaData();
columns.forEach(column -> insert.value(column.getName(), QueryBuilder.bindMarker()));
ListenableFuture resultFuture;
try {
PreparedStatement prepare = getCassandraClient().prepare(insert.getQueryString());
prepare.setConsistencyLevel(getWriteConsistencyLevel());
BoundStatement boundStatement = createBoundStatement(prepare, entity, columns);
ResultSetFuture input = getCassandraClient().executeAsync(boundStatement);
resultFuture = Futures.transform(input, ResultSet::wasApplied, getExecutorService());
} catch (SyntaxError e) {
LOGGER.warn("Can't prepare query: " + insert.getQueryString(), e);
resultFuture = Futures.immediateFailedFuture(e);
}
monitorFuture(saveAsyncTimer, resultFuture);
return resultFuture;
}
/**
* Remove entity asynchronously
*
* @param entity Target entity
*
* @return Operation status result
*/
public ListenableFuture removeAsync(@NotNull V entity) {
Timer.Context removeAsyncTimer = metricsFactory.getTimer(MetricsType.DATA_PROVIDER_REMOVE).time();
Delete from = QueryBuilder.delete()
.from(getEntityMetadata().getTableName());
int primaryKeysSize = getEntityMetadata().getPrimaryKeysSize();
Delete.Where where = null;
for (int i = 0; i < primaryKeysSize; i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (!primaryKey.isPresent()) {
throw new QueryException(String.format("Invalid primary key index: %d", i));
}
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
if (i == 0) {
where = from.where(QueryBuilder.eq(primaryKeyMetadata.getName(), QueryBuilder.bindMarker()));
} else {
where = where.and(QueryBuilder.eq(primaryKeyMetadata.getName(), QueryBuilder.bindMarker()));
}
}
assert where != null;
PreparedStatement prepare = getCassandraClient().prepare(where.getQueryString());
prepare.setConsistencyLevel(getWriteConsistencyLevel());
BoundStatement boundStatement = new BoundStatement(prepare);
for (int i = 0; i < primaryKeysSize; i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (!primaryKey.isPresent()) {
throw new QueryException(String.format("Invalid primary key index: %d", i));
}
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
Object value = primaryKeyMetadata.readValue(entity);
boundStatement.setBytesUnsafe(i, primaryKeyMetadata.serialize(value));
}
ResultSetFuture resultSetFuture = getCassandraClient().executeAsync(boundStatement);
ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::wasApplied, getExecutorService());
monitorFuture(removeAsyncTimer, transform);
return transform;
}
/**
* Get record async by specified keys and send result to consumer
*
* @param keys Primary keys
*
* @return True if result will be completed successfully and False if result will be completed with error
*/
public ListenableFuture findOneAsync(Object... keys) {
Timer.Context time = getMetricsFactory().getTimer(MetricsType.DATA_PROVIDER_FIND_ONE).time();
ListenableFuture transform = Futures.transform(findAsync(keys), (List result) -> result.isEmpty() ? null : result.get(0));
monitorFuture(time, transform);
return transform;
}
/**
* Get record async by specified keys and send result to consumer
*
* @param keys Primary keys
*
* @return True if result will be completed successfully and False if result will be completed with error
*/
public ListenableFuture> findAsync(Object... keys) {
Timer.Context timer = getMetricsFactory().getTimer(MetricsType.DATA_PROVIDER_FIND).time();
List parameters = new ArrayList<>();
Collections.addAll(parameters, keys);
ListenableFuture> resultFuture = fetch(parameters);
monitorFuture(timer, resultFuture);
return resultFuture;
}
public > int load(long startToken, long endToken, UserCallback consumer) {
Select select = QueryBuilder.select()
.all()
.from(getEntityMetadata().getTableName());
String[] primaryKeys = new String[getEntityMetadata().getPartitionKeySize()];
for (int i = 0; i < getEntityMetadata().getPartitionKeySize(); i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (!primaryKey.isPresent()) {
throw new QueryException(String.format("Invalid primary key index: %d", i));
}
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
String columnName = primaryKeyMetadata.getName();
primaryKeys[i] = columnName;
}
Select.Where where = select.where(QueryBuilder.gte(QueryBuilder.token(primaryKeys), QueryBuilder.bindMarker()));
if (endToken != Long.MAX_VALUE) {
where = where.and(QueryBuilder.lt(QueryBuilder.token(primaryKeys), QueryBuilder.bindMarker()));
} else {
where = where.and(QueryBuilder.lte(QueryBuilder.token(primaryKeys), QueryBuilder.bindMarker()));
}
PreparedStatement prepare = getCassandraClient().prepare(where.getQueryString());
prepare.setConsistencyLevel(getReadConsistencyLevel());
BoundStatement boundStatement = new BoundStatement(prepare);
boundStatement.bind(startToken, endToken);
boundStatement.setFetchSize(getEntityMetadata().getMaxFetchSize());
boundStatement.setConsistencyLevel(getEntityMetadata().getReadConsistencyLevel());
ResultSet resultSet = getCassandraClient().execute(boundStatement);
Iterator iterator = resultSet.iterator();
int loadedItems = 0;
while (iterator.hasNext()) {
Row next = iterator.next();
V instance = mapToObject(next);
if (instance != null) {
consumer.accept(instance);
}
loadedItems++;
}
return loadedItems;
}
public Class getEntityClass() {
return clazz;
}
/**
* Build cache key
*
* @param keys Entity keys
*
* @return Cache key
*/
protected final long buildHashCode(Object... keys) {
ArrayList keyList = new ArrayList<>();
Collections.addAll(keyList, keys);
return buildHashCode(keyList);
}
@NotNull
protected MetricsFactory getMetricsFactory() {
return metricsFactory;
}
protected String getTableName() {
return getEntityMetadata().getTableName();
}
protected int getMaxFetchSize() {
return getEntityMetadata().getMaxFetchSize();
}
protected ListenableFuture> fetch(List keys) {
List result = new ArrayList<>();
return Futures.transform(fetch(keys, result::add), (Boolean fetchResult) -> result);
}
protected ListenableFuture fetch(List keys, Consumer consumer) {
BoundStatement boundStatement;
Select select = QueryBuilder.select()
.all()
.from(getEntityMetadata().getTableName());
int parametersLength = keys.size();
if (parametersLength > 0) {
if (parametersLength < getEntityMetadata().getMinPrimaryKeys() || parametersLength > getEntityMetadata().getPrimaryKeysSize()) {
throw new QueryException(String.format("Invalid number of parameters at least composite keys must me provided. Expected: %d Actual: %d", getEntityMetadata().getPartitionKeySize(), parametersLength));
}
Select.Where where = null;
for (int i = 0; i < parametersLength; i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (!primaryKey.isPresent()) {
throw new QueryException(String.format("Invalid primary key index: %d", i));
}
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
String columnName = primaryKeyMetadata.getName();
if (i == 0) {
where = select.where(QueryBuilder.eq(columnName, QueryBuilder.bindMarker()));
} else {
where = where.and(QueryBuilder.eq(columnName, QueryBuilder.bindMarker()));
}
}
assert where != null;
PreparedStatement prepare = getCassandraClient().prepare(where.getQueryString());
prepare.setConsistencyLevel(getReadConsistencyLevel());
boundStatement = new BoundStatement(prepare);
bindPrimaryKeysParameters(keys, boundStatement);
} else {
PreparedStatement statement = getCassandraClient().prepare(select.getQueryString());
statement.setConsistencyLevel(getReadConsistencyLevel());
boundStatement = new BoundStatement(statement);
}
boundStatement.setFetchSize(getEntityMetadata().getMaxFetchSize());
boundStatement.setConsistencyLevel(getEntityMetadata().getReadConsistencyLevel());
ResultSetFuture resultSetFuture = getCassandraClient().executeAsync(boundStatement);
return Futures.transform(resultSetFuture, (ResultSet resultSet) -> {
Iterator iterator = resultSet.iterator();
while (iterator.hasNext()) {
Row next = iterator.next();
V instance = mapToObject(next);
consumer.accept(instance);
}
return true;
}, getExecutorService());
}
/**
* Map row result to object
*
* @param row ResultSet row
*
* @return Mapped object or null if can't map fields
*/
@Nullable
protected V mapToObject(@NotNull Row row) {
return mapToObjectFunction.apply(row);
}
@NotNull
protected CassandraClient getCassandraClient() {
return cassandraClient.create();
}
@NotNull
protected final EntityMetadata getEntityMetadata() {
return entityMetadata;
}
protected ConsistencyLevel getReadConsistencyLevel() {
return getEntityMetadata().getReadConsistencyLevel();
}
protected ConsistencyLevel getWriteConsistencyLevel() {
return getEntityMetadata().getWriteConsistencyLevel();
}
protected ListenableFuture monitorFuture(Timer.Context timer, ListenableFuture listenableFuture) {
return monitorFuture(timer, listenableFuture, new Function () {
@Override
public Boolean apply(Input input) {
return true;
}
});
}
/**
* Monitor future completion
*
* @param timer Timer that will be close on Future success or failure
* @param listenableFuture Listenable future
* @param userCallback User callback that will be executed on Future success
* @param Future class type
* @param User callback output
*
* @return Listenable future
*/
protected ListenableFuture monitorFuture(Timer.Context timer, ListenableFuture listenableFuture, Function userCallback) {
Futures.addCallback(listenableFuture, new FutureTimerCallback (timer));
return Futures.transform(listenableFuture, new JdkFunctionWrapper (userCallback));
}
@NotNull
private ExecutorService getExecutorService() {
return executorService;
}
private long buildHashCode(List keys) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
for (Object key : keys) {
if (key instanceof ByteBuffer) {
objectOutputStream.write(((ByteBuffer) key).array());
} else {
objectOutputStream.writeObject(key);
}
}
return HASH_FUNCTION.hashBytes(out.toByteArray()).asLong();
} catch (IOException e) {
LOGGER.warn("Can't build cache key", e);
throw new CanNotBuildEntryCacheKey(getEntityClass(), e);
}
}
private void bindPrimaryKeysParameters(@NotNull List keys, @NotNull BoundStatement boundStatement) {
for (int i = 0; i < keys.size(); i++) {
Optional primaryKey = getEntityMetadata().getPrimaryKey(i);
if (!primaryKey.isPresent()) {
throw new QueryException(String.format("Invalid primary key index: %d", i));
}
PrimaryKeyMetadata primaryKeyMetadata = primaryKey.get();
boundStatement.setBytesUnsafe(i, primaryKeyMetadata.serialize(keys.get(i)));
}
}
/**
* Create BoundStatement from PreparedStatement and bind parameter values from entity
*
* @param prepare PreparedStatement
* @param entity Entity from which will be read data
* @param columns Bind columns
*
* @return BoundStatement
*/
@NotNull
private BoundStatement createBoundStatement(@NotNull PreparedStatement prepare, @NotNull V entity, @NotNull List columns) {
BoundStatement boundStatement = new BoundStatement(prepare);
boundStatement.setConsistencyLevel(getEntityMetadata().getWriteConsistencyLevel());
for (int i = 0; i < columns.size(); i++) {
ColumnMetadata column = columns.get(i);
Object value = column.readValue(entity);
boundStatement.setBytesUnsafe(i, column.serialize(value));
}
return boundStatement;
}
/**
* Build entity metadata from entity class
*
* @param clazz Entity class
*
* @return EntityMetadata
*
* @throws MetadataException
*/
private EntityMetadata build(Class clazz) throws MetadataException {
return EntityMetadata.buildEntityMetadata(clazz, getCassandraClient());
}
private enum MetricsType implements MetricsFactory.Type {
DATA_PROVIDER_FIND_ONE,
DATA_PROVIDER_SAVE,
DATA_PROVIDER_REMOVE,
DATA_PROVIDER_FIND,
DATA_PROVIDER_CREATE_KEY,
}
}