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.
/*
* 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