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.
This is the core library for Active Objects. It is generic and can be embedded in any environment.
As such it is generic and won't contain all connection pooling, etc.
/*
* Copyright 2007 Daniel Spiewak
*
* 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 net.java.ao;
import com.atlassian.plugin.util.PluginKeyStack;
import com.atlassian.util.profiling.Ticker;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ExecutionError;
import com.google.common.util.concurrent.UncheckedExecutionException;
import net.java.ao.db.MySQLDatabaseProvider;
import net.java.ao.db.PostgreSQLDatabaseProvider;
import net.java.ao.schema.CachingNameConverters;
import net.java.ao.schema.FieldNameConverter;
import net.java.ao.schema.NameConverters;
import net.java.ao.schema.SchemaGenerator;
import net.java.ao.schema.TableNameConverter;
import net.java.ao.schema.info.EntityInfo;
import net.java.ao.schema.info.EntityInfoResolver;
import net.java.ao.schema.info.FieldInfo;
import net.java.ao.types.LogicalType;
import net.java.ao.types.TypeInfo;
import net.java.ao.util.StringUtils;
import org.apache.commons.lang3.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Array;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static com.atlassian.util.profiling.Metrics.metric;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import static net.java.ao.Common.getValueFieldsNames;
import static net.java.ao.Common.preloadValue;
import static net.java.ao.sql.SqlUtils.closeQuietly;
import static org.apache.commons.lang3.ArrayUtils.contains;
/**
*
The root control class for the entire ActiveObjects API. EntityManager is the source of all {@link
* RawEntity} objects, as well as the dispatch layer between the entities, the pluggable table name converters, and the
* database abstraction layers. This is the entry point for any use of the API.
*
*
EntityManager is designed to be used in an instance fashion with each instance corresponding to a
* single database. Thus, rather than a singleton instance or a static factory method, EntityManager does
* have a proper constructor. Any static instance management is left up to the developer using the API.
*
* @author Daniel Spiewak
*/
public class EntityManager {
private static final String DB_AO_ENTITY_MANAGER_TIMER_NAME = "db.ao.entityManager";
private static final String ENTITY_TYPE_TAG = "entityType";
private static final Logger log = LoggerFactory.getLogger(EntityManager.class);
private final DatabaseProvider provider;
private final EntityManagerConfiguration configuration;
private final SchemaConfiguration schemaConfiguration;
private final NameConverters nameConverters;
private final EntityInfoResolver entityInfoResolver;
private PolymorphicTypeMapper typeMapper;
private final ReadWriteLock typeMapperLock = new ReentrantReadWriteLock(true);
private final LoadingCache>, ValueGenerator>> valGenCache;
/**
* Creates a new instance of EntityManager using the specified {@link DatabaseProvider}.
*
* @param provider the {@link DatabaseProvider} to use in all database operations.
* @param configuration the configuration for this entity manager
*/
public EntityManager(DatabaseProvider provider, EntityManagerConfiguration configuration) {
this.provider = requireNonNull(provider, "provider can't be null");
this.configuration = requireNonNull(configuration);
valGenCache = CacheBuilder.newBuilder().build(new CacheLoader>, ValueGenerator>>() {
@Override
public ValueGenerator> load(Class extends ValueGenerator>> generatorClass) throws Exception {
return generatorClass.newInstance();
}
});
// TODO: move caching out of there!
nameConverters = new CachingNameConverters(configuration.getNameConverters());
schemaConfiguration = requireNonNull(configuration.getSchemaConfiguration(), "schema configuration can't be null");
typeMapper = new DefaultPolymorphicTypeMapper(new HashMap>, String>());
entityInfoResolver = requireNonNull(configuration.getEntityInfoResolverFactory().create(nameConverters, provider.getTypeManager()), "entityInfoResolver");
}
/**
* Convenience method to create the schema for the specified entities using the current settings (table/field name
* converter and database provider).
*
* @param entities the "list" of entity classes to consider for migration.
* @see SchemaGenerator#migrate(DatabaseProvider, SchemaConfiguration, NameConverters, boolean, Class[])
*/
public void migrate(Class extends RawEntity>>... entities) throws SQLException {
SchemaGenerator.migrate(provider, schemaConfiguration, nameConverters, false, entities);
}
/**
* Convenience method to create the schema for the specified entities using the current settings (table/field name
* converter and database provider). Note that if the given entities do not include the full set of entities, or
* those entities have removed any fields, then the corresponding tables or columns will be dropped, and any data
* they contained will be lost. Use this at your own risk.
*
* @param entities the "list" of entity classes to consider for migration.
* @see SchemaGenerator#migrate(DatabaseProvider, SchemaConfiguration, NameConverters, boolean, Class[])
*/
public void migrateDestructively(Class extends RawEntity>>... entities) throws SQLException {
SchemaGenerator.migrate(provider, schemaConfiguration, nameConverters, true, entities);
}
/**
* @deprecated since 0.23. EntityManager now no longer caches entities.
* use {@link #flush(RawEntity[])} to flush values for individual entities
*/
@Deprecated
public void flushAll() {
// no-op
}
/**
* @deprecated since 0.23. EntityManager now no longer caches entities.
* use {@link #flush(RawEntity[])} to flush values for individual entities
*/
@Deprecated
public void flushEntityCache() {
// no-op
}
/**
* @deprecated since 0.25. Entities and values now no longer cached.
*/
@Deprecated
public void flush(RawEntity>... entities) {
// no-op
}
/**
*
Returns an array of entities of the specified type corresponding to the
* varargs primary keys. If an in-memory reference already exists to a corresponding
* entity (of the specified type and key), it is returned rather than creating
* a new instance.
*
*
If the entity is known to exist in the database, then no checks are performed
* and the method returns extremely quickly. However, for any key which has not
* already been verified, a query to the database is performed to determine whether
* or not the entity exists. If the entity does not exist, then null
* is returned.
*
* @param type The type of the entities to retrieve.
* @param keys The primary keys corresponding to the entities to retrieve. All
* keys must be typed according to the generic type parameter of the entity's
* {@link RawEntity} inheritence (if inheriting from {@link Entity}, this is Integer
* or int). Thus, the keys array is type-checked at compile
* time.
* @return An array of entities of the given type corresponding with the specified
* primary keys. Any entities which are non-existent will correspond to a null
* value in the resulting array.
*/
public , K> T[] get(final Class type, K... keys) throws SQLException {
try (Ticker ignored = metric(DB_AO_ENTITY_MANAGER_TIMER_NAME+".get")
.tag(ENTITY_TYPE_TAG, type.getCanonicalName())
.withAnalytics()
.invokerPluginKey(PluginKeyStack.getFirstPluginKey())
.startTimer()) {
EntityInfo entityInfo = resolveEntityInfo(type);
final String primaryKeyField = entityInfo.getPrimaryKey().getName();
return get(type, findByPrimaryKey(type, primaryKeyField), keys);
}
}
private , K> Function findByPrimaryKey(final Class type, final String primaryKeyField) {
return new Function() {
@Override
public T invoke(K k) throws SQLException {
final T[] ts = find(type, primaryKeyField + " = ?", k);
if (ts.length == 1) {
return ts[0];
} else if (ts.length == 0) {
return null;
} else {
throw new ActiveObjectsException("Found more that one object of type '" + type.getName() + "' for key '" + k + "'");
}
}
};
}
protected , K> T[] peer(final EntityInfo entityInfo, K... keys) throws SQLException {
return get(entityInfo.getEntityType(), new Function() {
public T invoke(K key) {
return getAndInstantiate(entityInfo, key);
}
}, keys);
}
private , K> T[] get(Class type, Function create, K... keys) throws SQLException {
//noinspection unchecked
T[] back = (T[]) Array.newInstance(type, keys.length);
int index = 0;
for (K key : keys) {
back[index++] = create.invoke(key);
}
return back;
}
/**
* Creates a new instance of the entity of the specified type corresponding to the given primary key. This is used
* by {@link #get(Class, Object[])}} to create the entity.
*
* @param entityInfo The type of the entity to create.
* @param key The primary key corresponding to the entity instance required.
* @return An entity instance of the specified type and primary key.
*/
protected , K> T getAndInstantiate(EntityInfo entityInfo, K key) {
Class type = entityInfo.getEntityType();
EntityProxy proxy = new EntityProxy(this, entityInfo, key);
T entity = type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type, EntityProxyAccessor.class}, proxy));
return entity;
}
/**
* Cleverly overloaded method to return a single entity of the specified type rather than an array in the case where
* only one ID is passed. This method meerly delegates the call to the overloaded get method and
* functions as syntactical sugar.
*
* @param type The type of the entity instance to retrieve.
* @param key The primary key corresponding to the entity to be retrieved.
* @return An entity instance of the given type corresponding to the specified primary key, or null if
* the entity does not exist in the database.
* @see #get(Class, Object[])
*/
public , K> T get(Class type, K key) throws SQLException {
return get(type, toArray(key))[0];
}
protected , K> T peer(EntityInfo entityInfo, K key) throws SQLException {
if (null == key) {
return null;
}
return peer(entityInfo, toArray(key))[0];
}
@SuppressWarnings("unchecked")
private static K[] toArray(K key) {
return (K[]) new Object[]{key};
}
/**
*
Creates a new entity of the specified type with the optionally specified
* initial parameters. This method actually inserts a row into the table represented
* by the entity type and returns the entity instance which corresponds to that
* row.
*
*
The {@link DBParam} object parameters are designed to allow the creation
* of entities which have non-null fields which have no defalut or auto-generated
* value. Insertion of a row without such field values would of course fail,
* thus the need for db params. The db params can also be used to set
* the values for any field in the row, leading to more compact code under
* certain circumstances.
*
*
Unless within a transaction, this method will commit to the database
* immediately and exactly once per call. Thus, care should be taken in
* the creation of large numbers of entities. There doesn't seem to be a more
* efficient way to create large numbers of entities, however one should still
* be aware of the performance implications.
*
*
This method delegates the action INSERT action to
* {@link DatabaseProvider#insertReturningKey}.
* This is necessary because not all databases support the JDBC RETURN_GENERATED_KEYS
* constant (e.g. PostgreSQL and HSQLDB). Thus, the database provider itself is
* responsible for handling INSERTion and retrieval of the correct primary key
* value.
*
* @param type The type of the entity to INSERT.
* @param params An optional varargs array of initial values for the fields in the row. These
* values will be passed to the database within the INSERT statement.
* @return The new entity instance corresponding to the INSERTed row.
* @see net.java.ao.DBParam
*/
public , K> T create(Class type, DBParam... params) throws SQLException {
try (Ticker ignored = metric(DB_AO_ENTITY_MANAGER_TIMER_NAME+".create")
.tag(ENTITY_TYPE_TAG, type.getCanonicalName())
.withAnalytics()
.invokerPluginKey(PluginKeyStack.getFirstPluginKey())
.startTimer()) {
T back = null;
EntityInfo entityInfo = resolveEntityInfo(type);
final String table = entityInfo.getName();
Set listParams = new HashSet<>();
listParams.addAll(Arrays.asList(params));
final Collection fieldsInfoWithGenerators = entityInfo.getFields()
.stream()
.filter(FieldInfo.HAS_GENERATOR)
.collect(toSet());
for (FieldInfo fieldInfo : fieldsInfoWithGenerators) {
ValueGenerator> generator;
try {
generator = valGenCache.get(fieldInfo.getGeneratorType());
} catch (ExecutionException e) {
throw Throwables.propagate(e.getCause());
} catch (UncheckedExecutionException e) {
throw Throwables.propagate(e.getCause());
} catch (ExecutionError e) {
throw Throwables.propagate(e.getCause());
}
listParams.add(new DBParam(fieldInfo.getName(), generator.generateValue(this)));
}
final Set requiredFields = entityInfo.getFields()
.stream()
.filter(FieldInfo.IS_REQUIRED)
.collect(toSet());
for (DBParam param : listParams) {
FieldInfo field = entityInfo.getField(param.getField());
requireNonNull(field, format("Entity %s does not have field %s", type.getName(), param.getField()));
if (field.isPrimary()) {
//noinspection unchecked
Common.validatePrimaryKey(field, param.getValue());
} else if (!field.isNullable()) {
Validate.isTrue(param.getValue() != null, "Cannot set non-null field %s to null", param.getField());
if (param.getValue() instanceof String) {
Validate.isTrue(!StringUtils.isBlank((String) param.getValue()), "Cannot set non-null String field %s to ''", param.getField());
}
}
requiredFields.remove(field);
final TypeInfo dbType = field.getTypeInfo();
if (dbType != null && param.getValue() != null) {
dbType.getLogicalType().validate(param.getValue());
}
}
if (!requiredFields.isEmpty()) {
final String requiredFieldsAsString = requiredFields
.stream()
.map(Object::toString)
.collect(joining(", "));
throw new IllegalArgumentException("The follow required fields were not set when trying to create entity '" + type.getName() + "', those fields are: " + requiredFieldsAsString);
}
Connection connection = null;
try {
connection = provider.getConnection();
back = peer(entityInfo, provider.insertReturningKey(this, connection,
type,
entityInfo.getPrimaryKey().getJavaType(),
entityInfo.getPrimaryKey().getName(),
entityInfo.getPrimaryKey().hasAutoIncrement(),
table, listParams.toArray(new DBParam[listParams.size()])));
} finally {
closeQuietly(connection);
}
back.init();
return back;
}
}
/**
* Creates and INSERTs a new entity of the specified type with the given map of parameters. This method merely
* delegates to the {@link #create(Class, DBParam...)} method. The idea behind having a separate convenience method
* taking a map is in circumstances with large numbers of parameters or for people familiar with the anonymous inner
* class constructor syntax who might be more comfortable with creating a map than with passing a number of
* objects.
*
* @param type The type of the entity to INSERT.
* @param params A map of parameters to pass to the INSERT.
* @return The new entity instance corresponding to the INSERTed row.
* @see #create(Class, DBParam...)
*/
public , K> T create(Class type, Map params) throws SQLException {
DBParam[] arrParams = new DBParam[params.size()];
int i = 0;
for (String key : params.keySet()) {
arrParams[i++] = new DBParam(key, params.get(key));
}
return create(type, arrParams);
}
/**
* Creates and INSERTs a batch of new entities represented by {@code rows} of the given type. Each entity
* corresponds to a single row.
*
* @param type The type of the entity to INSERT.
* @param rows A list of rows to be INSERTed. A row is represented as a map from column name to its value.
* All rows must have the same columns.
*/
public , K> void create(Class type, List