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

info.archinnov.achilles.persistence.PersistenceManagerFactory Maven / Gradle / Ivy

There is a newer version: 6.1.0
Show newest version
/*
 * 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 java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
import com.fasterxml.jackson.databind.ObjectMapper;
import info.archinnov.achilles.configuration.ArgumentExtractor;
import info.archinnov.achilles.configuration.ConfigurationParameters;
import info.archinnov.achilles.interceptor.Interceptor;
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.context.SchemaContext;
import info.archinnov.achilles.internal.metadata.discovery.AchillesBootstrapper;
import info.archinnov.achilles.internal.metadata.holder.EntityMeta;
import info.archinnov.achilles.internal.metadata.parsing.context.ParsingResult;
import info.archinnov.achilles.internal.proxy.ProxyClassFactory;
import info.archinnov.achilles.internal.utils.ConfigMap;
import info.archinnov.achilles.internal.validation.Validator;
import info.archinnov.achilles.json.JacksonMapperFactory;
import info.archinnov.achilles.type.ConsistencyLevel;
import info.archinnov.achilles.type.InsertStrategy;

import javax.annotation.PreDestroy;

import static com.datastax.driver.core.BatchStatement.Type.LOGGED;
import static com.datastax.driver.core.BatchStatement.Type.UNLOGGED;
import static info.archinnov.achilles.configuration.ConfigurationParameters.*;

/**
 * 

* Stateless factory to create persistence manager. * This class is totally thread-safe and can be shared by many threads. * You should normally have only one instance of PersistenceManagerFactory across the application * */ public class PersistenceManagerFactory { private static final Logger log = LoggerFactory.getLogger(PersistenceManagerFactory.class); Map, EntityMeta> entityMetaMap = new HashMap<>(); ConfigurationContext configContext; DaoContext daoContext; PersistenceContextFactory contextFactory; ConfigMap configurationMap; private ArgumentExtractor argumentExtractor = new ArgumentExtractor(); private AchillesBootstrapper bootstrapper = new AchillesBootstrapper(); private ProxyClassFactory proxyClassFactory = ProxyClassFactory.Singleton.INSTANCE.get(); private Cluster cluster; PersistenceManagerFactory(Cluster cluster, Map configurationMap) { this.cluster = cluster; Validator.validateNotNull(configurationMap, "Configuration map for PersistenceManagerFactory should not be null"); Validator.validateNotEmpty(configurationMap, "Configuration map for PersistenceManagerFactory should not be empty"); this.configurationMap = ConfigMap.fromMap(configurationMap); } PersistenceManagerFactory bootstrap() { final String keyspaceName = configurationMap.getTyped(KEYSPACE_NAME); log.info("Bootstrapping Achilles PersistenceManagerFactory for keyspace {}", keyspaceName); configContext = argumentExtractor.initConfigContext(configurationMap); Session session = argumentExtractor.initSession(cluster, configurationMap); final ClassLoader classLoader = argumentExtractor.initOSGIClassLoader(configurationMap); List> interceptors = argumentExtractor.initInterceptors(configurationMap); List> candidateClasses = argumentExtractor.initEntities(configurationMap, classLoader); ParsingResult parsingResult = parseEntities(candidateClasses); this.entityMetaMap = parsingResult.getMetaMap(); bootstrapper.addInterceptorsToEntityMetas(interceptors, parsingResult.getMetaMap()); SchemaContext schemaContext = new SchemaContext(configContext, session, keyspaceName, cluster, parsingResult); bootstrapper.validateOrCreateTables(schemaContext); daoContext = bootstrapper.buildDaoContext(session, parsingResult, configContext); contextFactory = new PersistenceContextFactory(daoContext, configContext, parsingResult.getMetaMap()); warmUpProxies(); return this; } private void warmUpProxies() { if (argumentExtractor.initProxyWarmUp(configurationMap)) { long start = System.nanoTime(); for (Class clazz : entityMetaMap.keySet()) { proxyClassFactory.createProxyClass(clazz, configContext); } long end = System.nanoTime(); long duration = (end - start) / 1000000; log.info("Entity proxies warm up took {} milli secs for {} entities", duration, entityMetaMap.size()); } } private ParsingResult parseEntities(List> candidateClasses) { return bootstrapper.buildMetaDatas(configContext, candidateClasses); } /** * Create a new PersistenceManager. This instance of PersistenceManager is * thread-safe * * @return PersistenceManager */ public PersistenceManager createPersistenceManager() { log.debug("Spawn new PersistenceManager"); return new PersistenceManager(entityMetaMap, contextFactory, daoContext, configContext); } /** * Create a new AsyncManager for asynchronous operation. This instance of PersistenceManager is * thread-safe * * @return AsyncManager */ public AsyncManager createAsyncManager() { log.debug("Spawn new AsyncManager"); return new AsyncManager(entityMetaMap, contextFactory, daoContext, configContext); } /** * Create a new state-full 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 Batch */ public Batch createLoggedBatch() { log.debug("Spawn new Logged Batch"); return new Batch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, false); } /** * Create a new state-full 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 Batch */ public Batch createUnloggedBatch() { log.debug("Spawn new Unlogged Batch"); return new Batch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, false); } /** * Create a new asynchronous state-full 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 createAsyncLoggedBatch() { log.debug("Spawn new Logged AsyncBatch"); return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, false); } /** * Create a new asynchronous state-full 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 createAsyncUnloggedBatch() { log.debug("Spawn new Unlogged AsyncBatch"); return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, false); } /** * Create a new state-full 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 Batch */ public Batch createOrderedLoggedBatch() { log.debug("Spawn new ordered Logged Batch"); return new Batch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, true); } /** * Create a new state-full 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 Batch */ public Batch createOrderedUnloggedBatch() { log.debug("Spawn new ordered Unlogged Batch"); return new Batch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, true); } /** * Create a new state-full 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 createOrderedAsyncLoggedBatch() { log.debug("Spawn new ordered Logged AsyncBatch"); return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, true); } /** * Create a new state-full 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 createOrderedAsyncUnloggedBatch() { log.debug("Spawn new ordered Unlogged AsyncBatch"); return new AsyncBatch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, true); } /** * Serialize the entity in JSON using a registered Object Mapper or default Achilles Object Mapper * @param entity * @return serialized entity in JSON * @throws IOException */ public String serializeToJSON(Object entity) throws IOException { Validator.validateNotNull(entity, "Cannot serialize to JSON null entity"); final ObjectMapper objectMapper = configContext.getMapperFor(entity.getClass()); return objectMapper.writeValueAsString(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 IOException */ public T deserializeFromJSON(Class type, String serialized) throws IOException { Validator.validateNotNull(type, "Cannot deserialize from JSON if target type is null"); final ObjectMapper objectMapper = configContext.getMapperFor(type); return objectMapper.readValue(serialized, type); } public static class PersistenceManagerFactoryBuilder { private ConfigMap configMap = new ConfigMap(); private Cluster cluster; private PersistenceManagerFactoryBuilder(Cluster cluster) { this.cluster = cluster; Validator.validateNotNull(cluster, "Cluster object should not be null"); } /** * Create a new PersistenceManagerFactory with the given configuration * map * * @see Configuration parameters * * @param cluster pre-configured {@link com.datastax.driver.core.Cluster} * @param configurationMap configuration map * @return new PersistenceManagerFactory * */ public static PersistenceManagerFactory build(Cluster cluster, Map configurationMap) { return new PersistenceManagerFactory(cluster, configurationMap).bootstrap(); } /** * Create a new builder to configure each parameter * * @param cluster pre-configured {@link com.datastax.driver.core.Cluster} * @return PersistenceManagerFactoryBuilder */ public static PersistenceManagerFactoryBuilder builder(Cluster cluster) { return new PersistenceManagerFactoryBuilder(cluster); } /** * Define entity packages to scan for '@Entity' classes The packages * should be comma-separated * * @see Entity parsing * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withEntityPackages(String entityPackages) { configMap.put(ENTITY_PACKAGES, entityPackages); return this; } /** * Define list of entities to be managed by Achilles * * @see Entity parsing * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withEntities(List> entities) { configMap.put(ENTITIES_LIST, entities); return this; } /** * Define a pre-configured Jackson Object Mapper for serialization of * non-primitive types * * @see JSON serialization * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withJacksonMapper(ObjectMapper objectMapper) { configMap.put(JACKSON_MAPPER, objectMapper); return this; } /** * Define a pre-configured map of Jackson Object Mapper for * serialization of non-primitive types * * @see JSON serialization * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withJacksonMapperFactory(JacksonMapperFactory jacksonMapperFactory) { configMap.put(JACKSON_MAPPER_FACTORY, jacksonMapperFactory); return this; } /** * Define the default Consistency level to be used for all READ * operations * * @see Consistency configuration * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withDefaultReadConsistency(ConsistencyLevel defaultReadConsistency) { configMap.put(CONSISTENCY_LEVEL_READ_DEFAULT, defaultReadConsistency); return this; } /** * Define the default Consistency level to be used for all WRITE * operations * * @see Consistency configuration * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withDefaultWriteConsistency(ConsistencyLevel defaultWriteConsistency) { configMap.put(CONSISTENCY_LEVEL_WRITE_DEFAULT, defaultWriteConsistency); return this; } /** * Define the default Consistency level map to be used for all READ * operations The map keys represent table names and values represent * the corresponding consistency level * * @see Consistency configuration * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withDefaultReadConsistencyMap(Map readConsistencyMap) { configMap.put(CONSISTENCY_LEVEL_READ_MAP, readConsistencyMap); return this; } /** * Define the default Consistency level map to be used for all WRITE * operations The map keys represent table names and values represent * the corresponding consistency level * * @see Consistency configuration * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withDefaultWriteConsistencyMap(Map writeConsistencyMap) { configMap.put(CONSISTENCY_LEVEL_WRITE_MAP, writeConsistencyMap); return this; } /** * Whether Achilles should force table creation if they do not already * exist in the keyspace This flag is useful for dev only. It * should be disabled in production * * @see Table generation * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder forceTableCreation(boolean forceTableCreation) { configMap.put(FORCE_TABLE_CREATION, forceTableCreation); return this; } /** * Whether Achilles should force update table if entities have new fields and table not. * This flag is useful for dev only. It is strongly advised to disable this feature in production * * @see Lossless Schema Updae * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder enableSchemaUpdate(boolean forceTableUpdate) { configMap.put(ENABLE_SCHEMA_UPDATE, forceTableUpdate); return this; } /** * Map to allow table schema update, same as * {@link #enableSchemaUpdate(boolean)} per table. It is strongly advised to disable this feature in production * * @see Lossless Schema Update * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder enableSchemaUpdateForTables(Map tables) { configMap.put(ENABLE_SCHEMA_UPDATE_FOR_TABLES, tables); return this; } /** * Define the pre-configured {@code com.datastax.driver.core.Session} object to * be used instead of creating a new one * * @see Native Session * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withNativeSession(Session nativeSession) { configMap.put(NATIVE_SESSION, nativeSession); return this; } /** * Define the keyspace name to be used by Achilles. Note: you should * build as many PersistenceManagerFactory as different keyspaces to be * used * * @see Keyspace * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withKeyspaceName(String keyspaceName) { configMap.put(KEYSPACE_NAME, keyspaceName); return this; } /** * Provide a list of event interceptors * * @see Event interceptors * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withEventInterceptors(List> interceptors) { configMap.put(EVENT_INTERCEPTORS, interceptors); return this; } /** * Activate Bean Validation (JSR303) * * @see Bean validation * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder enableBeanValidation(boolean enableBeanValidation) { configMap.put(BEAN_VALIDATION_ENABLE, enableBeanValidation); return this; } /** * Provide custom validator for Bean Validation (JSR303) * * @see Bean validation * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withBeanValidator(javax.validation.Validator validator) { if (validator != null) { configMap.put(BEAN_VALIDATION_VALIDATOR, validator); } return this; } /** * Specify maximum size for the internal prepared statements LRU cache. * If the cache is full, oldest prepared statements will be dropped, leading to unexpected behavior. *

* Default value is 5000, which is a pretty safe limit. *

* For information, only selects on counter fields and updates are put into the cache because they cannot be * prepared before hand since the updated properties are not known in advance. * * @see Prepared statements cache * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withMaxPreparedStatementCacheSize(int maxPreparedStatementCacheSize) { configMap.put(PREPARED_STATEMENTS_CACHE_SIZE, maxPreparedStatementCacheSize); return this; } /** * Whether to disable proxies warm up or not. * * @see Proxies Warm Up * @param disableProxiesWarmUp * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder disableProxiesWarmUp(boolean disableProxiesWarmUp) { configMap.put(PROXIES_WARM_UP_DISABLED, disableProxiesWarmUp); return this; } /** * Define the global insert strategy * * @see Insert Strategy * @param globalInsertStrategy * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder globalInsertStrategy(InsertStrategy globalInsertStrategy) { configMap.put(GLOBAL_INSERT_STRATEGY, globalInsertStrategy); return this; } /** * Whether to relax constraint on existing secondary indices validation * * @see Index Validation * @param relaxIndexValidation * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder relaxIndexValidation(boolean relaxIndexValidation) { configMap.put(RELAX_INDEX_VALIDATION, relaxIndexValidation); return this; } /** * Pass an ExecutorService (ThreadPool) to Achilles to be used internally for asynchronous operations. *
* If omitted, the default ExecutorService is configured as below: *

         *      new ThreadPoolExecutor(5, 20, 60, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(1000),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param executorService an executor service (thread pool) to be used by Achilles for internal for asynchronous operations * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withDefaultExecutorService(ExecutorService executorService) { configMap.put(EXECUTOR_SERVICE, executorService); return this; } /** * Define the min thread count for the ExecutorService (ThreadPool) to be used internally for asynchronous operations. *
* The default ExecutorService is configured as below: *

         *      // Create proxy
         *      new ThreadPoolExecutor(minThreadCount, 20, 60, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(1000),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param minThreadCount min thread count for the executor service * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withExecutorServiceMinThreadCount(int minThreadCount) { configMap.put(DEFAULT_EXECUTOR_SERVICE_MIN_THREAD, minThreadCount); return this; } /** * Define the max thread count for the ExecutorService (ThreadPool) to be used internally for asynchronous operations. *
* The default ExecutorService is configured as below: *

         *      // Create proxy
         *      new ThreadPoolExecutor(5, maxThreadCount, 60, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(1000),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param maxThreadCount max thread count for the executor service * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withExecutorServiceMaxThreadCount(int maxThreadCount) { configMap.put(DEFAULT_EXECUTOR_SERVICE_MAX_THREAD, maxThreadCount); return this; } /** * Define the thread keep-alive duration in second for the ExecutorService (ThreadPool) to be used internally for asynchronous operations. *
* The default ExecutorService is configured as below: *

         *      // Create proxy
         *      new ThreadPoolExecutor(5, 20, keepAliveDuration, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(1000),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param keepAliveDuration thread keep-alive duration in second for the executor service * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withExecutorServiceThreadKeepAliveDuration(int keepAliveDuration) { configMap.put(DEFAULT_EXECUTOR_SERVICE_THREAD_KEEPALIVE, keepAliveDuration); return this; } /** * Define the LinkedBlockingQueue size for the ExecutorService (ThreadPool) to be used internally for asynchronous operations. *
* The default ExecutorService is configured as below: *

         *      // Create proxy
         *      new ThreadPoolExecutor(5, 20, 60, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(threadQueueSize),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param threadQueueSize the LinkedBlockingQueue size for the executor service * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withExecutorServiceThreadQueueSize(int threadQueueSize) { configMap.put(DEFAULT_EXECUTOR_SERVICE_QUEUE_SIZE, threadQueueSize); return this; } /** * Define the Thread Factory for the ExecutorService (ThreadPool) to be used internally for asynchronous operations. *
* The default ExecutorService is configured as below: *

         *      // Create proxy
         *      new ThreadPoolExecutor(5, 20, 60, TimeUnit.SECONDS,
         *              new LinkedBlockingQueue(threadQueueSize),
         *              new DefaultExecutorThreadFactory())
         *  
* @see Asynchronous Operations * @param factory the thread factory * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withExecutorServiceThreadFactory(ThreadFactory factory) { configMap.put(DEFAULT_EXECUTOR_SERVICE_THREAD_FACTORY, factory); return this; } /** * Pass an arbitrary parameter to configure Achilles * * @param parameter an instance of the ConfigurationParameters enum * @param value the value of the parameter * * @return PersistenceManagerFactoryBuilder */ public PersistenceManagerFactoryBuilder withParameter(ConfigurationParameters parameter, Object value) { configMap.put(parameter, value); return this; } /** * Build a new PersistenceManagerFactory * * @return PersistenceManagerFactory */ public PersistenceManagerFactory build() { return new PersistenceManagerFactory(cluster, configMap).bootstrap(); } } /** * Call shutdown on Achilles, especially shutdown the internal thread pool handling asynchronous tasks */ @PreDestroy public void shutDown() { if(this.configurationMap.getTyped(EXECUTOR_SERVICE) == null) { this.configContext.getExecutorService().shutdown(); } } @Override public String toString() { return "PersistenceManagerFactory for keyspace : "+this.configContext.getCurrentKeyspace().or("none"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy