info.archinnov.achilles.persistence.AsyncManager Maven / Gradle / Ivy
Show all versions of achilles-core Show documentation
/*
* Copyright (C) 2012-2014 DuyHai DOAN
*
* 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.
*/
package info.archinnov.achilles.persistence;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import info.archinnov.achilles.async.AchillesFuture;
import info.archinnov.achilles.internal.context.ConfigurationContext;
import info.archinnov.achilles.internal.context.DaoContext;
import info.archinnov.achilles.internal.context.PersistenceContextFactory;
import info.archinnov.achilles.internal.metadata.holder.EntityMeta;
import info.archinnov.achilles.internal.validation.Validator;
import info.archinnov.achilles.query.cql.AsyncNativeQuery;
import info.archinnov.achilles.query.slice.AsyncSliceQueryBuilder;
import info.archinnov.achilles.query.typed.AsyncTypedQuery;
import info.archinnov.achilles.type.IndexCondition;
import info.archinnov.achilles.options.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.datastax.driver.core.BatchStatement.Type.LOGGED;
import static com.datastax.driver.core.BatchStatement.Type.UNLOGGED;
import static info.archinnov.achilles.options.OptionsBuilder.noOptions;
/**
*
* Stateless object to manage asynchronous entity persistence.
* This class is totally thread-safe and can be shared by many threads.
* You should normally have only one instance of PersistenceMananger across the application
*
*
* An AsyncMananger is very cheap to create from an {@link PersistenceManagerFactory}
*
*
*
*
I Persist asynchronously transient entity
*
* // Insert
* AchillesFuture managedEntityFuture = asyncManager.insert(myEntity);
*
*
* II Update asynchronously for modifications
*
* User managedUser = asyncManager.find(User.class,1L).get();
* user.setFirstname("DuyHai");
*
* AchillesFuture userFuture = asyncManager.update(user);
*
*
* III Removing asynchronously entities
*
* // Simple removed
* User managedUser = asyncManager.find(User.class,1L).get();
* AchillesFuture userFuture = asyncManager.remove(managedUser);
*
* // Direct remove without read-before-write
* AchillesFuture emptyFuture = asyncManager.removeById(User.class,1L);
*
*
* IV Loading entities asynchronously
*
* // Read data from Cassandra
* AchillesFuture managedUserFuture = asyncManager.find(User.class,1L);
*
*
* V Direct update
* Please note that proxy creation always return immediately since we do not hit the database
*
* // No data read from Cassandra
* User managedUserProxy = asyncManager.forUpdate(User.class,1L).get();
* managedUser.setAge(30);
*
* // Direct update, no read from Cassandra has been done
* AchillesFuture userFuture = asyncManager.update(managedUser);
*
*
* VI Reloading state asynchronously for managed entities
*
* // Read data from Cassandra
* User managedUser = asyncManager.find(User.class,1L).get();
* ...
* // Perform some logic
*
* // Reload data from Cassandra into the managed entity
* AchillesFuture userFuture = asyncManager.refresh(managedUser);
*
*
* VII Removing proxy from managed entities
*
* // Create managed entity
* User managedUser = asyncManager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Removing proxy before passing it to client via serialization
* User transientUser = asyncManager.removeProxy(managedUser);
*
*
* VIII Accessing native Session object
*
* Session session = asyncManager.getNativeSession();
* ...
*
* // Issue simple CQL queries
* session.execute("UPDATE users SET age=:age WHERE id=:id",30,10);
*
*
* IX JSON serialization/deserialization
*
* // Serialize an object to JSON using the registered or default object mapper
* String json = asyncManager.serializeToJSON(myModel);
* ...
*
* // Deserialize a JSON string into an object the registered or default object mapper
* MyModel myModel = asyncManager.deserializeFromJSON(json);
*
*
* X Initializing all counter fields
*
* // Create managed entity
* AchillesFuture@lt;User> futureUser = asyncManager.find(User.class,1L);
* ...
* // Perform some logic
* ...
*
* // Load all lazy counter fields
* asyncManager.initialize(futureUser.get());
*
*
*
*
* @see Persistence Manager operations
*/
public class AsyncManager extends CommonAsyncManager {
private static final Logger log = LoggerFactory.getLogger(AsyncManager.class);
protected AsyncManager(Map, EntityMeta> entityMetaMap, PersistenceContextFactory contextFactory, DaoContext daoContext, ConfigurationContext configContext) {
super(entityMetaMap, contextFactory, daoContext, configContext);
}
/**
* Find an entity, asynchronously.
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to load
*
* @return AchillesFuture<T> future managed entity
*/
public AchillesFuture find(Class entityClass, Object primaryKey) {
log.debug("Find entity class '{}' with primary key '{}'", entityClass, primaryKey);
return super.asyncFind(entityClass, primaryKey, noOptions());
}
/**
* Find an entity with the given Consistency Level for read, asynchronously
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to load
* @param options
* Options
*
* @return AchillesFuture<T> future managed entity
*/
public AchillesFuture find(final Class entityClass, final Object primaryKey, Options options) {
log.debug("Find entity class '{}' with primary key '{}' and options '{}'", entityClass, primaryKey, options);
return super.asyncFind(entityClass, primaryKey, options);
}
/**
* Create a proxy for the entity update. An new empty entity will be created,
* populated with the provided primary key and then proxified. This method
* never returns null. Use this method to perform direct update without
* read-before-write
*
*
* // No data read from Cassandra
* User managedUser = manager.forUpdate(User.class,1L);
* managedUser.setAge(33);
* manager.update(managedUser);
*
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to initialize
*
* @return T proxy
*/
public T forUpdate(Class entityClass, Object primaryKey) {
log.debug("Get reference for entity class '{}' with primary key {}", entityClass, primaryKey);
return super.getProxyForUpdateInternal(entityClass, primaryKey);
}
/**
* Initialize all lazy fields of a set of 'managed' entities
*
*
* // Create a managed entity
* User userProxy = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Initialize all fields not yet loaded into the managed entity, including counter fields
* manager.initialize(userProxy);
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public T initialize(final T entity) {
log.debug("Force lazy fields initialization for entity {}", proxifier.removeProxy(entity));
return super.initialize(entity);
}
/**
* Initialize all counter fields of a list of 'managed' entities
*
*
* // Create managed entities
* User user1 = manager.find(User.class,1L);
* User user2 = manager.find(User.class,2L);
* ...
* // Perform some logic
* ...
*
* // Initialize all counter fields not yet loaded into the managed entity
* manager.initialize(Sets.newHashSet(user1, user2));
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public Set initialize(final Set entities) {
return super.initialize(entities);
}
/**
* Initialize all counter fields of a list of 'managed' entities
*
*
* // Create managed entities
* User user1 = manager.find(User.class,1L);
* User user2 = manager.find(User.class,2L);
* ...
* // Perform some logic
* ...
*
* // Initialize all counter fields not yet loaded into the managed entity
* manager.initialize(Arrays.asList(user1, user2));
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public List initialize(final List entities) {
log.debug("Force lazy fields initialization for entity set {}", entities);
return super.initialize(entities);
}
/**
* Shorthand for manager.removeProxy(manager.initialize(T entity))
*
*/
public T initAndRemoveProxy(T entity) {
return super.removeProxy(super.initialize(entity));
}
/**
* Shorthand for manager.removeProxy(manager.initialize(Set entities))
*
*/
public Set initAndRemoveProxy(Set entities) {
return super.removeProxy(super.initialize(entities));
}
/**
* Shorthand for manager.removeProxy(manager.initialize(List entities))
*
*/
public List initAndRemoveProxy(List entities) {
return super.removeProxy(super.initialize(entities));
}
/**
* Remove the proxy of a 'managed' entity and return the underlying "raw"
* entity
*
*
* If the argument is not a proxy object, return itself
* Else, return the target object behind the proxy
*
*
* // Create managed entity
* User managedUser = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Removing proxy before passing it to client via serialization
* User transientUser = manager.removeProxy(managedUser);
*
*
* @param proxy
* @return real object
*/
public T removeProxy(T proxy) {
return super.removeProxy(proxy);
}
/**
* Remove the proxy of a list of 'managed' entities and return the
* underlying "raw" entities
*
* See {@link #removeProxy}
*
* @param proxies
* list of proxified entity
* @return real object list
*/
public List removeProxy(List proxies) {
log.debug("Removing proxy for a list of entities {}", proxies);
return super.removeProxy(proxies);
}
/**
* Remove the proxy of a set of 'managed' entities return the underlying
* "raw" entities
*
* See {@link #removeProxy}
*
* @param proxies
* set of proxified entities
* @return real object set
*/
public Set removeProxy(Set proxies) {
log.debug("Removing proxy for a set of entities {}", proxies);
return super.removeProxy(proxies);
}
/**
* Create a builder to start an asynchronous slice query DSL. The provided entity class must be:
*
*
* - a entity type managed by Achilles
* - a clustered entity, slicing is irrelevant for non-clustered entity
*
*
* @see Slice query API
*
* @param entityClass type of the clustered entity
* @param : type of the clustered entity
* @return AsyncSliceQueryBuilder<T>
*/
public AsyncSliceQueryBuilder sliceQuery(Class entityClass) {
log.debug("Execute slice query for entity class {}", entityClass);
final EntityMeta meta = super.validateSliceQueryInternal(entityClass);
return new AsyncSliceQueryBuilder<>(sliceQueryExecutor, entityClass, meta);
}
/**
* Return a CQL native query DSL
*
*
*
*
* Native query without bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",Arrays.asList(10,11))).limit(20);
* AchillesFuture<List<TypedMap>> actual = asyncManager.nativeQuery(nativeQuery).get();
*
*
*
*
*
* Native query with bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",bindMarker())).limit(bindMarker());
* AchillesFuture<List<TypedMap>> actual = asyncManager.nativeQuery(nativeQuery,Arrays.asList(10,11),20).get();
*
*
* @see Native query API
*
* @param statement
* native CQL regularStatement, including limit, ttl and consistency
* options
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return AsyncNativeQuery
*/
public AsyncNativeQuery nativeQuery(Statement statement, Object... boundValues) {
log.debug("Execute native query {}", statement);
Validator.validateNotNull(statement, "The statement for native query should not be null");
return new AsyncNativeQuery(daoContext, configContext, statement, noOptions(), boundValues);
}
/**
* Return an asynchronous CQL native query
*
*
*
*
* Native query without bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",Arrays.asList(10,11))).limit(20);
* AchillesFuture<List<TypedMap>> actual = asyncManager.nativeQuery(nativeQuery).get();
*
*
*
*
*
* Native query with bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",bindMarker())).limit(bindMarker());
* AchillesFuture<List<TypedMap>> actual = asyncManager.nativeQuery(nativeQuery,Arrays.asList(10,11),20).get();
*
*
* @see Native query API
*
* @param statement
* native CQL regularStatement, including limit, ttl and consistency
* options
*
* @param options
* options for the query. Only LWT Result listener passed as option is taken
* into account. For timestamp, TTL and LWT conditions you must specify them
* directly in the query string
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return AsyncNativeQuery
*/
public AsyncNativeQuery nativeQuery(Statement statement, Options options, Object... boundValues) {
log.debug("Execute native query {}", statement);
Validator.validateNotNull(statement, "The statement for native query should not be null");
return new AsyncNativeQuery(daoContext, configContext, statement, options, boundValues);
}
/**
* Return an asynchronous CQL typed query
*
* All found entities will be in managed state
*
*
*
*
* Typed query without bound values
*
* RegularStatement nativeQuery = select().from("MyEntity").where().limit(3);
* AchillesFuture<List<MyEntity>> actual = asyncManager.typedQuery(MyEntity.class, nativeQuery).get();
*
*
*
*
*
* Typed query with bound values
*
* RegularStatement statement = select().from("MyEntity").limit(bindMarker());
* AchillesFuture<List<MyEntity>> actual = asyncManager.typedQuery(MyEntity.class, statement,3).get();
*
*
* @see Typed query API
*
* @param entityClass
* type of entity to be returned
*
* @param statement
* native CQL regularStatement, including limit, ttl and consistency
* options
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return AsyncTypedQuery<T>
*/
public AsyncTypedQuery typedQuery(Class entityClass, Statement statement, Object... boundValues) {
final EntityMeta meta = super.typedQueryInternal(entityClass, statement,boundValues);
return new AsyncTypedQuery<>(entityClass, daoContext, configContext, statement, meta, contextFactory, boundValues);
}
/**
* Return an asynchronous CQL indexed query
*
* All found entities will be in 'managed' state
*
* @param entityClass
* type of entity to be returned
*
* @param indexCondition
* index condition
*
* @return AsyncTypedQuery<T>
*/
public AsyncTypedQuery indexedQuery(Class entityClass, IndexCondition indexCondition) {
log.debug("Execute indexed query for entity class {}", entityClass);
final Statement statement = super.indexedQueryInternal(entityClass, indexCondition);
final EntityMeta meta = super.typedQueryInternal(entityClass, statement, indexCondition.getColumnValue());
return new AsyncTypedQuery<>(entityClass, daoContext, configContext, statement, meta, contextFactory, new Object[]{indexCondition.getColumnValue()});
}
/**
* Serialize the entity in JSON using a registered Object Mapper or default Achilles Object Mapper
* @param entity
* @return serialized entity in JSON
* @throws java.io.IOException
*/
public String serializeToJSON(Object entity) throws IOException {
return super.serializeToJSON(entity);
}
/**
* Deserialize the given JSON into entity using a registered Object Mapper or default Achilles Object Mapper
* @param type
* @param serialized
* @param
* @return deserialized entity from JSON
* @throws java.io.IOException
*/
public T deserializeFromJSON(Class type, String serialized) throws IOException {
return super.deserializeFromJSON(type, serialized);
}
/**
* Return Session object from Java Driver
* @return Session
*/
public Session getNativeSession() {
return super.getNativeSession();
}
/**
* Create a new state-full asynchronous LOGGED Batch
*
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full AsyncBatch
*/
public AsyncBatch createLoggedBatch() {
log.debug("Spawn new Logged AsyncBatch");
return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, false);
}
/**
* Create a new state-full asynchronous UNLOGGED Batch
*
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full AsyncBatch
*/
public AsyncBatch createUnloggedBatch() {
log.debug("Spawn new Unlogged AsyncBatch");
return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, false);
}
/**
* Create a new state-full asynchronous ordered and LOGGED Batch
*
*
* This Batch respect insertion order by generating increasing timestamp with micro second resolution.
* If you use ordered Batch in multiple clients, do not forget to synchronize the clock between those clients
* to avoid statements interleaving
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full AsyncBatch
*/
public AsyncBatch createOrderedLoggedBatch() {
log.debug("Spawn new ordered Logged AsyncBatch");
return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, true);
}
/**
* Create a new state-full asynchronous ordered and UNLOGGED Batch
*
*
* This Batch respect insertion order by generating increasing timestamp with micro second resolution.
* If you use ordered Batch in multiple clients, do not forget to synchronize the clock between those clients
* to avoid statements interleaving
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full AsyncBatch
*/
public AsyncBatch createOrderedUnloggedBatch() {
log.debug("Spawn new ordered Unlogged AsyncBatch");
return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, true);
}
}