From f4d37d3a825d20acf4e312c12df22ead286b0de7 Mon Sep 17 00:00:00 2001 From: Developer Environment Date: Mon, 16 Oct 2017 14:56:20 +0100 Subject: [PATCH] Support for Generic entities Update most of the entityManager methods to support receiving an extra ParameterizedType in case the user need to persist a Generic entity. The type lost by erasure will then be reified using the informations from the Parameterized type and the fields will be introspected as normal. Previous behaviour is not impacted and no breaking change was needed. --- .../jmethods/catatumbo/DatastoreAccess.java | 248 ++++++++++++----- .../jmethods/catatumbo/DatastoreBatch.java | 256 ++++++++++++++--- .../catatumbo/DatastoreTransaction.java | 87 +++++- .../com/jmethods/catatumbo/EntityManager.java | 63 ++++- .../com/jmethods/catatumbo/MapperFactory.java | 28 +- .../catatumbo/impl/DatastoreUtils.java | 52 ++-- .../catatumbo/impl/DefaultDatastoreBatch.java | 121 ++++++-- .../impl/DefaultDatastoreReader.java | 151 +++++----- .../impl/DefaultDatastoreTransaction.java | 161 +++++++---- .../impl/DefaultDatastoreWriter.java | 204 +++++++------- .../catatumbo/impl/DefaultEntityManager.java | 168 +++++++---- .../impl/EmbeddableIntrospector.java | 16 +- .../catatumbo/impl/EmbeddedField.java | 54 +++- .../catatumbo/impl/EmbeddedIntrospector.java | 15 +- .../catatumbo/impl/EmbeddedMetadata.java | 30 +- .../catatumbo/impl/EntityIntrospector.java | 261 ++++++++++++------ .../catatumbo/impl/EntityMetadata.java | 53 ++-- .../catatumbo/impl/FieldMetadata.java | 22 +- .../catatumbo/impl/IdentifierMetadata.java | 22 +- .../catatumbo/impl/IntrospectionUtils.java | 54 ++-- .../jmethods/catatumbo/impl/KeyMetadata.java | 10 +- .../jmethods/catatumbo/impl/Marshaller.java | 75 +++-- .../catatumbo/impl/ParentKeyMetadata.java | 10 +- .../catatumbo/impl/PropertyMetadata.java | 31 ++- .../jmethods/catatumbo/impl/Unmarshaller.java | 53 ++-- .../catatumbo/entities/GenericEntity.java | 95 +++++++ .../entities/GenericParameterizedType.java | 29 ++ .../impl/EntityIntrospectorTest.java | 27 ++ .../catatumbo/impl/MarshallerTest.java | 14 + 29 files changed, 1674 insertions(+), 736 deletions(-) create mode 100644 src/test/java/com/jmethods/catatumbo/entities/GenericEntity.java create mode 100644 src/test/java/com/jmethods/catatumbo/entities/GenericParameterizedType.java diff --git a/src/main/java/com/jmethods/catatumbo/DatastoreAccess.java b/src/main/java/com/jmethods/catatumbo/DatastoreAccess.java index 7532bbe..abd1c6b 100644 --- a/src/main/java/com/jmethods/catatumbo/DatastoreAccess.java +++ b/src/main/java/com/jmethods/catatumbo/DatastoreAccess.java @@ -16,18 +16,19 @@ package com.jmethods.catatumbo; +import java.lang.reflect.Type; import java.util.List; /** * An interface for working with (reading from and writing to) the Cloud Datastore. - * + * * @author Sai Pullabhotla * */ public interface DatastoreAccess { /** * Inserts the given entity into the Cloud Datastore. - * + * * @param entity * the entity to insert * @return the inserted entity. The inserted entity will not be same as the passed in entity. For @@ -37,9 +38,23 @@ public interface DatastoreAccess { */ E insert(E entity); + /** + * Inserts the given entity into the Cloud Datastore. + * + * @param entity + * the entity to insert + * @param entityType + * the entity type + * @return the inserted entity. The inserted entity will not be same as the passed in entity. For + * example, the inserted entity may contain any generated ID, key, parent key, etc. + * @throws EntityManagerException + * if any error occurs while inserting. + */ + E insert(E entity, Type entityType); + /** * Inserts the given list of entities into the Cloud Datastore. - * + * * @param entities * the entities to insert. * @return the inserted entities. The inserted entities will not be same as the passed in @@ -50,10 +65,25 @@ public interface DatastoreAccess { */ List insert(List entities); + /** + * Inserts the given list of entities into the Cloud Datastore. + * + * @param entities + * the entities to insert. + * @param entityType + * the entity type + * @return the inserted entities. The inserted entities will not be same as the passed in + * entities. For example, the inserted entities may contain generated ID, key, parent key, + * etc. + * @throws EntityManagerException + * if any error occurs while inserting. + */ + List insert(List entities, Type entityType); + /** * Updates the given entity in the Cloud Datastore. The passed in Entity must have its ID set for * the update to work. - * + * * @param entity * the entity to update * @return the updated entity. @@ -62,9 +92,23 @@ public interface DatastoreAccess { */ E update(E entity); + /** + * Updates the given entity in the Cloud Datastore. The passed in Entity must have its ID set for + * the update to work. + * + * @param entity + * the entity to update + * @param entityType + * the entity type + * @return the updated entity. + * @throws EntityManagerException + * if any error occurs while updating. + */ + E update(E entity, Type entityType); + /** * Updates the given list of entities in the Cloud Datastore. - * + * * @param entities * the entities to update. The passed in entities must have their ID set for the update * to work. @@ -74,10 +118,24 @@ public interface DatastoreAccess { */ List update(List entities); + /** + * Updates the given list of entities in the Cloud Datastore. + * + * @param entities + * the entities to update. The passed in entities must have their ID set for the update + * to work. + * @param entityType + * the entity type + * @return the updated entities + * @throws EntityManagerException + * if any error occurs while inserting. + */ + List update(List entities, Type entityType); + /** * Updates or inserts the given entity in the Cloud Datastore. If the entity does not have an ID, * it may be generated. - * + * * @param entity * the entity to update or insert * @return the updated/inserted entity. @@ -86,10 +144,24 @@ public interface DatastoreAccess { */ E upsert(E entity); + /** + * Updates or inserts the given entity in the Cloud Datastore. If the entity does not have an ID, + * it may be generated. + * + * @param entity + * the entity to update or insert + * @param entityType + * the entity type + * @return the updated/inserted entity. + * @throws EntityManagerException + * if any error occurs while saving. + */ + E upsert(E entity, Type entityType); + /** * Updates or inserts the given list of entities in the Cloud Datastore. If the entities do not * have a valid ID, IDs may be generated. - * + * * @param entities * the entities to update/or insert. * @return the updated or inserted entities @@ -98,9 +170,23 @@ public interface DatastoreAccess { */ List upsert(List entities); + /** + * Updates or inserts the given list of entities in the Cloud Datastore. If the entities do not + * have a valid ID, IDs may be generated. + * + * @param entities + * the entities to update/or insert. + * @param entityType + * the entity type + * @return the updated or inserted entities + * @throws EntityManagerException + * if any error occurs while saving. + */ + List upsert(List entities, Type entityType); + /** * Deletes the given entity from the Cloud Datastore. - * + * * @param entity * the entity to delete. The entity must have it ID set for the deletion to succeed. * @throws EntityManagerException @@ -108,9 +194,21 @@ public interface DatastoreAccess { */ void delete(Object entity); + /** + * Deletes the given entity from the Cloud Datastore. + * + * @param entity + * the entity to delete. The entity must have it ID set for the deletion to succeed. + * @param entityType + * the entity type + * @throws EntityManagerException + * if any error occurs while deleting. + */ + void delete(Object entity, Type entityType); + /** * Deletes the given entities from the Cloud Datastore. - * + * * @param entities * the entities to delete. The entities must have it ID set for the deletion to succeed. * @throws EntityManagerException @@ -118,37 +216,49 @@ public interface DatastoreAccess { */ void delete(List entities); + /** + * Deletes the given entities from the Cloud Datastore. + * + * @param entities + * the entities to delete. The entities must have it ID set for the deletion to succeed. + * @param entityType + * the entity type + * @throws EntityManagerException + * if any error occurs while deleting. + */ + void delete(List entities, Type entityType); + /** * Deletes the entity with the given ID. The entity is assumed to be a root entity (no parent). * The entity kind will be determined from the supplied entity class. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, long id); + void delete(Type entityType, long id); /** * Deletes the entity with the given ID. The entity is assumed to be a root entity (no parent). * The entity kind will be determined from the supplied entity class. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, String id); + void delete(Type entityType, String id); /** * Deletes the entity with the given ID and parent key. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id @@ -156,25 +266,25 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, DatastoreKey parentKey, long id); + void delete(Type entityType, DatastoreKey parentKey, long id); /** * Deletes the entity with the given ID and parent key. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id * the ID of the entity. * @throws EntityManagerException - * if any error occurs while inserting. + * if any error occurs while inserting. */ - void delete(Class entityClass, DatastoreKey parentKey, String id); + void delete(Type entityType, DatastoreKey parentKey, String id); /** * Deletes an entity given its key. - * + * * @param key * the entity's key * @throws EntityManagerException @@ -184,7 +294,7 @@ public interface DatastoreAccess { /** * Deletes the entities having the given keys. - * + * * @param keys * the entities' keys * @throws EntityManagerException @@ -195,9 +305,9 @@ public interface DatastoreAccess { /** * Loads and returns the entity with the given ID. The entity is assumed to be a root entity (no * parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param id * the ID of the entity * @return the Entity object or null, if the the entity with the given ID does not @@ -205,14 +315,14 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - E load(Class entityClass, long id); + E load(Type entityType, long id); /** * Loads and returns the entity with the given ID. The entity is assumed to be a root entity (no * parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param id * the ID of the entity * @return the Entity object or null, if the the entity with the given ID does not @@ -220,14 +330,14 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - E load(Class entityClass, String id); + E load(Type entityType, String id); /** * Loads and returns the entity with the given ID. The entity kind is determined from the supplied * class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param parentKey * the parent key of the entity. * @param id @@ -237,14 +347,14 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - E load(Class entityClass, DatastoreKey parentKey, long id); + E load(Type entityType, DatastoreKey parentKey, long id); /** * Loads and returns the entity with the given ID. The entity kind is determined from the supplied * class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param parentKey * the parent key of the entity. * @param id @@ -254,13 +364,13 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - E load(Class entityClass, DatastoreKey parentKey, String id); + E load(Type entityType, DatastoreKey parentKey, String id); /** * Loads and returns the entity with the given key. - * - * @param entityClass - * the entity class (expected result type) + * + * @param entityType + * the entity type (expected result type) * @param key * full key of the entity * @return the Entity object or null, if the the entity with the given key does not @@ -268,14 +378,14 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while accessing the Cloud Datastore. */ - E load(Class entityClass, DatastoreKey key); + E load(Type entityType, DatastoreKey key); /** * Loads and returns the entities with the given numeric IDs. The entities are assumed to * be a root entities (no parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param identifiers * the IDs of the entities * @return the list of entity objects in the same order as the given list of identifiers. If one @@ -284,14 +394,14 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - List loadById(Class entityClass, List identifiers); + List loadById(Type entityType, List identifiers); /** * Loads and returns the entities with the given names (a.k.a String IDs). The entities are * assumed to be root entities (no parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type. * @param identifiers * the IDs of the entities * @return the list of entity objects in the same order as the given list of identifiers. If one @@ -300,29 +410,29 @@ public interface DatastoreAccess { * @throws EntityManagerException * if any error occurs while inserting. */ - List loadByName(Class entityClass, List identifiers); + List loadByName(Type entityType, List identifiers); /** * Loads and returns the entities for the given keys. - * - * @param entityClass - * the entity class (expected result type) + * + * @param entityType + * the entity type (expected result type) * @param keys * entity keys to load * @return the Entity objects for the given keys. If one or more requested keys do not exist in * the Cloud Datastore, the corresponding item in the returned list be null. - * + * * @throws EntityManagerException * if any error occurs while accessing the Cloud Datastore. */ - List loadByKey(Class entityClass, List keys); + List loadByKey(Type entityType, List keys); /** * Creates and returns a new {@link EntityQueryRequest} for the given GQL query string. The * returned {@link EntityQueryRequest} can be further customized to set any bindings (positional * or named), and then be executed by calling the execute or * executeEntityQuery methods. - * + * * @param query * the GQL query * @return a new QueryRequest for the given GQL query @@ -334,7 +444,7 @@ public interface DatastoreAccess { * returned {@link ProjectionQueryRequest} can further be customized to set any positional and/or * named bindings, and then be executed by calling the execute or * executeProjectionQuery methods. - * + * * @param query * the GQL projection query * @return a new ProjectionQueryRequest for the given query @@ -346,7 +456,7 @@ public interface DatastoreAccess { * requests must only have __key__ in the SELECT list of field. The returned * {@link KeyQueryRequest} can further be customized to set any positional and/or named bindings, * and then be executed by calling the executeKeyQuery method. - * + * * @param query * the GQL projection query * @return a new ProjectionQueryRequest for the given query @@ -355,31 +465,31 @@ public interface DatastoreAccess { /** * Executes the given {@link EntityQueryRequest} and returns the response. - * + * * @param expectedResultType * the expected type of results. * @param request * the entity query request * @return the query response */ - QueryResponse executeEntityQueryRequest(Class expectedResultType, + QueryResponse executeEntityQueryRequest(Type expectedResultType, EntityQueryRequest request); /** * Executes the given {@link ProjectionQueryRequest} and returns the response. - * + * * @param expectedResultType * the expected type of results. * @param request * the projection query request * @return the query response */ - QueryResponse executeProjectionQueryRequest(Class expectedResultType, + QueryResponse executeProjectionQueryRequest(Type expectedResultType, ProjectionQueryRequest request); /** * Executes the given {@link KeyQueryRequest} and returns the response. - * + * * @param request * the key query request * @return the query response diff --git a/src/main/java/com/jmethods/catatumbo/DatastoreBatch.java b/src/main/java/com/jmethods/catatumbo/DatastoreBatch.java index de6278b..096bc64 100644 --- a/src/main/java/com/jmethods/catatumbo/DatastoreBatch.java +++ b/src/main/java/com/jmethods/catatumbo/DatastoreBatch.java @@ -16,12 +16,13 @@ package com.jmethods.catatumbo; +import java.lang.reflect.Type; import java.util.List; /** * Interface for executing Batch updates (Insert/Update/Delete). Instances of this class are * obtained by calling {@link EntityManager#newBatch()}. - * + * * @author Sai Pullabhotla * */ @@ -31,7 +32,7 @@ public interface DatastoreBatch { * Adds the given entity to this batch for insertion. If the entity does not have an identifier * set, ID may be automatically generated. If the same entity was added to this batch for * deletion, the insert request is changed into an upsert request. - * + * * @param entity * the entity to insert. * @return the inserted entity. The inserted entity may not be same as the passed in entity. For @@ -41,11 +42,27 @@ public interface DatastoreBatch { */ E insert(E entity); + /** + * Adds the given entity to this batch for insertion. If the entity does not have an identifier + * set, ID may be automatically generated. If the same entity was added to this batch for + * deletion, the insert request is changed into an upsert request. + * + * @param entity + * the entity to insert. + * @param entityType + * the entity type + * @return the inserted entity. The inserted entity may not be same as the passed in entity. For + * example, the inserted entity may contain any generated ID, key, parent key, etc. + * @throws EntityManagerException + * if any error occurs while inserting. + */ + E insert(E entity, Type entityType); + /** * Adds the given entities to this batch for insertion. If any of the entities do not have an * identifier set, ID may be generated automatically. If any of entities were added to this batch * for deletion, the insert request is changed to an upsert request. - * + * * @param entities * the entities to insert. * @return the inserted entities. The inserted entities may not be same as the passed in entities. @@ -55,6 +72,22 @@ public interface DatastoreBatch { */ List insert(List entities); + /** + * Adds the given entities to this batch for insertion. If any of the entities do not have an + * identifier set, ID may be generated automatically. If any of entities were added to this batch + * for deletion, the insert request is changed to an upsert request. + * + * @param entities + * the entities to insert. + * @param entityType + * the entity type + * @return the inserted entities. The inserted entities may not be same as the passed in entities. + * For example, the inserted entities may contain generated ID, key, parent key, etc. + * @throws EntityManagerException + * if any error occurs while inserting. + */ + List insert(List entities, Type entityType); + /** * Adds the given entity to this batch for insertion. The ID allocation is deferred to the submit * time of this batch. Generated keys can be retrieved using @@ -62,7 +95,7 @@ public interface DatastoreBatch { * {@link EntityManagerException} if the entity is using a String identifier. String identifiers * are allocated at add time, instead of submit time. For entities with String identifiers, use * the {@link DatastoreBatch#insert(Object)} method. - * + * * @param entity * the entity to insert * @throws EntityManagerException @@ -70,6 +103,23 @@ public interface DatastoreBatch { */ void insertWithDeferredIdAllocation(E entity); + /** + * Adds the given entity to this batch for insertion. The ID allocation is deferred to the submit + * time of this batch. Generated keys can be retrieved using + * {@link DatastoreBatch.Response#getGeneratedKeys()}.This method throws + * {@link EntityManagerException} if the entity is using a String identifier. String identifiers + * are allocated at add time, instead of submit time. For entities with String identifiers, use + * the {@link DatastoreBatch#insert(Object)} method. + * + * @param entity + * the entity to insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String identifier or if any error occurs + */ + void insertWithDeferredIdAllocation(E entity, Type entityType); + /** * Adds the given entities to this batch for insertion. The ID allocation is deferred to the * submit time of this batch. Generated keys can be retrieved using @@ -77,7 +127,7 @@ public interface DatastoreBatch { * {@link EntityManagerException} if the entity is using a String identifier. String identifiers * are allocated at add time, instead of submit time. For entities with String identifiers, use * the {@link DatastoreBatch#insert(List)} method. - * + * * @param entities * the entities to insert * @throws EntityManagerException @@ -85,11 +135,28 @@ public interface DatastoreBatch { */ void insertWithDeferredIdAllocation(List entities); + /** + * Adds the given entities to this batch for insertion. The ID allocation is deferred to the + * submit time of this batch. Generated keys can be retrieved using + * {@link DatastoreBatch.Response#getGeneratedKeys()}.This method throws + * {@link EntityManagerException} if the entity is using a String identifier. String identifiers + * are allocated at add time, instead of submit time. For entities with String identifiers, use + * the {@link DatastoreBatch#insert(List)} method. + * + * @param entities + * the entities to insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity have a String identifier any error occurs + */ + void insertWithDeferredIdAllocation(List entities, Type entityType); + /** * Adds the given entity to this batch for update. The operation will fail if the entity with the * same key does not exist in the underlying Datastore. This method does not use optimistic * locking even if the given entity has a field with {@link Version} annotation. - * + * * @param entity * the entity to update * @return the updated entity. @@ -98,12 +165,27 @@ public interface DatastoreBatch { */ E update(E entity); + /** + * Adds the given entity to this batch for update. The operation will fail if the entity with the + * same key does not exist in the underlying Datastore. This method does not use optimistic + * locking even if the given entity has a field with {@link Version} annotation. + * + * @param entity + * the entity to update + * @param entityType + * the entity type + * @return the updated entity. + * @throws EntityManagerException + * if any error occurs while updating. + */ + E update(E entity, Type entityType); + /** * Adds the given entities to this batch for update. The operation will fail if the entities with * the same key do not already exist in the underlying datastore. This method does not use * optimistic locking even if the given entity has a field with {@link Version} annotation. - * - * + * + * * @param entities * the entities to update. * @return the updated entities @@ -112,11 +194,27 @@ public interface DatastoreBatch { */ List update(List entities); + /** + * Adds the given entities to this batch for update. The operation will fail if the entities with + * the same key do not already exist in the underlying datastore. This method does not use + * optimistic locking even if the given entity has a field with {@link Version} annotation. + * + * + * @param entities + * the entities to update. + * @param entityType + * the entity type + * @return the updated entities + * @throws EntityManagerException + * if any error occurs while inserting. + */ + List update(List entities, Type entityType); + /** * Adds the given entity to this batch for update or insert. Any prior writes to this entity in * this batch will be removed from this batch. If the entity does not have an ID set, ID may be * generated automatically. - * + * * @param entity * the entity to update or insert * @return the updated/inserted entity. @@ -125,11 +223,26 @@ public interface DatastoreBatch { */ E upsert(E entity); + /** + * Adds the given entity to this batch for update or insert. Any prior writes to this entity in + * this batch will be removed from this batch. If the entity does not have an ID set, ID may be + * generated automatically. + * + * @param entity + * the entity to update or insert + * @param entityType + * the entity type + * @return the updated/inserted entity. + * @throws EntityManagerException + * if any error occurs while saving. + */ + E upsert(E entity, Type entityType); + /** * Adds the given entities to this batch for update or insert. Any prior writes of any of the * entities will be removed from this batch. If any of the entities do not have their IDs set, IDs * may be generated automatically. - * + * * @param entities * the entities to update/or insert. * @return the updated or inserted entities @@ -138,12 +251,27 @@ public interface DatastoreBatch { */ List upsert(List entities); + /** + * Adds the given entities to this batch for update or insert. Any prior writes of any of the + * entities will be removed from this batch. If any of the entities do not have their IDs set, IDs + * may be generated automatically. + * + * @param entities + * the entities to update/or insert. + * @param entityType + * the entity type + * @return the updated or inserted entities + * @throws EntityManagerException + * if any error occurs while saving. + */ + List upsert(List entities, Type entityType); + /** * Adds the given entity to this batch for insert or update. The ID (for numeric ID) allocation * will be deferred to the submit time of this batch. This method throws an * {@link EntityManagerException} if the entity is using a String ID. Entity with String ID should * use {@link DatastoreBatch#upsert(Object)} method. - * + * * @param entity * the entity to update or insert * @throws EntityManagerException @@ -152,12 +280,28 @@ public interface DatastoreBatch { */ void upsertWithDeferredIdAllocation(E entity); + /** + * Adds the given entity to this batch for insert or update. The ID (for numeric ID) allocation + * will be deferred to the submit time of this batch. This method throws an + * {@link EntityManagerException} if the entity is using a String ID. Entity with String ID should + * use {@link DatastoreBatch#upsert(Object)} method. + * + * @param entity + * the entity to update or insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String ID or any other error occurs while accessing the + * underlying datastore. + */ + void upsertWithDeferredIdAllocation(E entity, Type entityType); + /** * Adds the given entities to this batch for update or insert. The ID (for numeric ID) allocation * will be deferred to the submit time of this batch. This method throws an * {@link EntityManagerException} if the entities have String identifiers. Entities with String * identifiers should use {@link DatastoreBatch#upsert(List)} method. - * + * * @param entities * the entities to update or insert * @throws EntityManagerException @@ -166,9 +310,25 @@ public interface DatastoreBatch { */ void upsertWithDeferredIdAllocation(List entities); + /** + * Adds the given entities to this batch for update or insert. The ID (for numeric ID) allocation + * will be deferred to the submit time of this batch. This method throws an + * {@link EntityManagerException} if the entities have String identifiers. Entities with String + * identifiers should use {@link DatastoreBatch#upsert(List)} method. + * + * @param entities + * the entities to update or insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entities have String identifiers or any other error occurs while accessing the + * underlying datastore. + */ + void upsertWithDeferredIdAllocation(List entities, Type entityType); + /** * Adds the given entity to this batch for deletion. - * + * * @param entity * the entity to delete. * @throws EntityManagerException @@ -176,9 +336,21 @@ public interface DatastoreBatch { */ void delete(Object entity); + /** + * Adds the given entity to this batch for deletion. + * + * @param entity + * the entity to delete. + * @param entityType + * the entity type + * @throws EntityManagerException + * if any error occurs while deleting. + */ + void delete(Object entity, Type entityType); + /** * Adds the given entities to this batch for deletion. - * + * * @param entities * the entities to delete. * @throws EntityManagerException @@ -186,35 +358,47 @@ public interface DatastoreBatch { */ void delete(List entities); + /** + * Adds the given entities to this batch for deletion. + * + * @param entities + * the entities to delete. + * @param entityType + * the entity type + * @throws EntityManagerException + * if any error occurs while deleting. + */ + void delete(List entities, Type entityType); + /** * Adds the given ID and entity type to this batch for deletion. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, long id); + void delete(Type entityType, long id); /** * Adds the given ID and entity type to this batch for deletion. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, String id); + void delete(Type entityType, String id); /** * Adds the given entity type, parent key and ID to this batch for deletion. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id @@ -222,13 +406,13 @@ public interface DatastoreBatch { * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, DatastoreKey parentKey, long id); + void delete(Type entityType, DatastoreKey parentKey, long id); /** * Adds the given entity type, parent key and ID to this batch for deletion. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id @@ -236,11 +420,11 @@ public interface DatastoreBatch { * @throws EntityManagerException * if any error occurs while inserting. */ - void delete(Class entityClass, DatastoreKey parentKey, String id); + void delete(Type entityType, DatastoreKey parentKey, String id); /** * Adds the given key to this batch for deletion. - * + * * @param key * the key to delete * @throws EntityManagerException @@ -250,7 +434,7 @@ public interface DatastoreBatch { /** * Adds the given keys to this batch for deletion. - * + * * @param keys * the keys to delete * @throws EntityManagerException @@ -261,14 +445,14 @@ public interface DatastoreBatch { /** * Tells whether or not this DatastoreBatch is still active. A DatastoreBatch is considered active * if it is not submitted yet. - * + * * @return true, if this batch is active; false, otherwise. */ boolean isActive(); /** * Submits the batch operations to the Cloud Datastore. - * + * * @return the response of the batch operation. * @throws EntityManagerException * if any error occurs @@ -277,14 +461,14 @@ public interface DatastoreBatch { /** * Response when the the batch is submitted. - * + * * @author Sai Pullabhotla * */ interface Response { /** * Returns a list of generated keys. - * + * * @return a list of generated keys. */ List getGeneratedKeys(); diff --git a/src/main/java/com/jmethods/catatumbo/DatastoreTransaction.java b/src/main/java/com/jmethods/catatumbo/DatastoreTransaction.java index 874fe39..d44aade 100644 --- a/src/main/java/com/jmethods/catatumbo/DatastoreTransaction.java +++ b/src/main/java/com/jmethods/catatumbo/DatastoreTransaction.java @@ -16,6 +16,7 @@ package com.jmethods.catatumbo; +import java.lang.reflect.Type; import java.util.List; /** @@ -25,7 +26,7 @@ * (read/write) within a transaction. Transactions are committed with the call to * {@link DatastoreTransaction#commit()} method. Transactions can be rolled back with a call to * {@link DatastoreTransaction#rollback()}. - * + * * @author Sai Pullabhotla * */ @@ -34,7 +35,7 @@ public interface DatastoreTransaction extends DatastoreAccess { /** * Inserts the given entity. ID allocation is deferred to the submit time. Generated key, if any, * can be retrieved by calling {@link DatastoreTransaction.Response#getGeneratedKeys()}. - * + * * @param entity * the entity to insert. * @throws EntityManagerException @@ -43,10 +44,24 @@ public interface DatastoreTransaction extends DatastoreAccess { */ void insertWithDeferredIdAllocation(E entity); + /** + * Inserts the given entity. ID allocation is deferred to the submit time. Generated key, if any, + * can be retrieved by calling {@link DatastoreTransaction.Response#getGeneratedKeys()}. + * + * @param entity + * the entity to insert. + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String Identifier or any other error occurs while accessing the + * underlying Datastore. + */ + void insertWithDeferredIdAllocation(E entity, Type entityType); + /** * Inserts the given entities. ID allocation is deferred to the submit time. Generated keys, if * any, can be retrieved by calling {@link DatastoreTransaction.Response#getGeneratedKeys()}. - * + * * @param entities * the entities to insert * @throws EntityManagerException @@ -55,12 +70,26 @@ public interface DatastoreTransaction extends DatastoreAccess { */ void insertWithDeferredIdAllocation(List entities); + /** + * Inserts the given entities. ID allocation is deferred to the submit time. Generated keys, if + * any, can be retrieved by calling {@link DatastoreTransaction.Response#getGeneratedKeys()}. + * + * @param entities + * the entities to insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String Identifier or any other error occurs while accessing the + * underlying Datastore. + */ + void insertWithDeferredIdAllocation(List entities, Type entityType); + /** * Updates or inserts the given entity. ID allocation is deferred to the submit time. Generated * key, if any, can be retrieved by calling * {@link DatastoreTransaction.Response#getGeneratedKeys()}. - * - * + * + * * @param entity * the entity to update or insert. * @throws EntityManagerException @@ -69,12 +98,28 @@ public interface DatastoreTransaction extends DatastoreAccess { */ void upsertWithDeferredIdAllocation(E entity); + /** + * Updates or inserts the given entity. ID allocation is deferred to the submit time. Generated + * key, if any, can be retrieved by calling + * {@link DatastoreTransaction.Response#getGeneratedKeys()}. + * + * + * @param entity + * the entity to update or insert. + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String Identifier or any other error occurs while accessing the + * underlying Datastore. + */ + void upsertWithDeferredIdAllocation(E entity, Type entityType); + /** * Updates or Inserts the given entities. ID allocation is deferred to the submit time. Generated * keys, if any, can be retrieved by calling * {@link DatastoreTransaction.Response#getGeneratedKeys()}. - * - * + * + * * @param entities * the entities to update or insert * @throws EntityManagerException @@ -83,9 +128,25 @@ public interface DatastoreTransaction extends DatastoreAccess { */ void upsertWithDeferredIdAllocation(List entities); + /** + * Updates or Inserts the given entities. ID allocation is deferred to the submit time. Generated + * keys, if any, can be retrieved by calling + * {@link DatastoreTransaction.Response#getGeneratedKeys()}. + * + * + * @param entities + * the entities to update or insert + * @param entityType + * the entity type + * @throws EntityManagerException + * if the entity has a String Identifier or any other error occurs while accessing the + * underlying Datastore. + */ + void upsertWithDeferredIdAllocation(List entities, Type entityType); + /** * Tells if this DatastoreTransaction is still active. - * + * * @return true, if this DatastoreTransaction is still active; false, * otherwise. */ @@ -93,9 +154,9 @@ public interface DatastoreTransaction extends DatastoreAccess { /** * Commits changes made within this transaction. - * + * * @return Response. The response contains any generated. - * + * * @throws EntityManagerException * if the commit fails. */ @@ -103,7 +164,7 @@ public interface DatastoreTransaction extends DatastoreAccess { /** * Rolls back the changes made in this transaction. - * + * * @throws EntityManagerException * if this transaction was already committed. */ @@ -112,14 +173,14 @@ public interface DatastoreTransaction extends DatastoreAccess { /** * Transaction's commit Response. Used for returning generated keys for entities whose id * allocation was deferred until submit/commit time. - * + * * @author Sai Pullabhotla * */ interface Response { /** * Returns a list of generated keys. - * + * * @return a list of generated keys. */ List getGeneratedKeys(); diff --git a/src/main/java/com/jmethods/catatumbo/EntityManager.java b/src/main/java/com/jmethods/catatumbo/EntityManager.java index f1f1ffb..a50bfd9 100644 --- a/src/main/java/com/jmethods/catatumbo/EntityManager.java +++ b/src/main/java/com/jmethods/catatumbo/EntityManager.java @@ -16,12 +16,13 @@ package com.jmethods.catatumbo; +import java.lang.reflect.Type; import java.util.List; /** * Manages mapping and persistence of entities. EntityManager objects are created using the * {@link EntityManagerFactory}. - * + * * @author Sai Pullabhotla * */ @@ -29,14 +30,14 @@ public interface EntityManager extends DatastoreAccess { /** * Deletes all entities of given Kind. - * - * @param entityClass - * the entity class - The entity Kind will be determined from this class. + * + * @param entityType + * the entity type - The entity Kind will be determined from this class. * @return the number of entities that were deleted * @throws EntityManagerException * if any error occurs while deleting. */ - long deleteAll(Class entityClass); + long deleteAll(Type entityType); /** * Deletes all entities of given Kind. @@ -51,7 +52,7 @@ public interface EntityManager extends DatastoreAccess { /** * Returns a new Transaction that can be used to perform a set of operations. - * + * * @return a new Transaction that can be used to perform a set of operations. */ DatastoreTransaction newTransaction(); @@ -59,7 +60,7 @@ public interface EntityManager extends DatastoreAccess { /** * Creates and returns a new {@link DatastoreBatch} that can be used for processing multiple write * operations in one request. - * + * * @return a new DatastoreBatch for processing multiple write operations in one * request. */ @@ -71,18 +72,18 @@ public interface EntityManager extends DatastoreAccess { * created {@link DatastoreTransaction} to perform reads/writes from/to the Cloud Datastore. When * the {@link TransactionalTask} finishes, the transaction is committed. If any error occurs * during the execution of the {@link TransactionalTask}, the transaction will be rolled back. - * + * * @param task * the task (or call back) to execute * @return the return value from the execution of * {@link TransactionalTask#execute(DatastoreTransaction)}. - * + * */ T executeInTransaction(TransactionalTask task); /** * Registers the given entity lifecycle listeners with this entity manager. - * + * * @param classes * the classes that should receive entity lifecycle events. Lifecycle callbacks are * executed for all types of entities that are managed by this EntityManager. @@ -91,7 +92,7 @@ public interface EntityManager extends DatastoreAccess { /** * Returns the {@link DatastoreMetadata} object that can be used to retrieve metadata information. - * + * * @return the {@link DatastoreMetadata} object that can be used to retrieve metadata information. */ DatastoreMetadata getDatastoreMetadata(); @@ -99,7 +100,7 @@ public interface EntityManager extends DatastoreAccess { /** * Returns the {@link DatastoreStats} object that can be used to retrieve various statistics on * the data stored in the Datastore. - * + * * @return the {@link DatastoreStats} object that can be used to retrieve various statistics on * the data stored in the Datastore. */ @@ -108,7 +109,7 @@ public interface EntityManager extends DatastoreAccess { /** * Allocates IDs for the given entities and returns the allocated IDs. Each entity in the list * must have a its identifier of type numeric (long/Long). - * + * * @param entities * the entities * @return a list of {@link DatastoreKey}s. @@ -120,10 +121,27 @@ public interface EntityManager extends DatastoreAccess { */ List allocateId(List entities); + /** + * Allocates IDs for the given entities and returns the allocated IDs. Each entity in the list + * must have a its identifier of type numeric (long/Long). + * + * @param entities + * the entities + * @param entityType + * the entity type + * @return a list of {@link DatastoreKey}s. + * @throws IllegalArgumentException + * if any of the entities in the list do not have a numeric ID type or a valid ID is + * already set. + * @throws EntityManagerException + * if any error occurs during key allocation + */ + List allocateId(List entities, Type entityType); + /** * Allocates ID for the given entity and returns the allocated ID. The entity must have its * identifier of type numeric (long/Long). - * + * * @param entity * the the entity. * @return the allocated ID {@link DatastoreKey}. @@ -135,4 +153,21 @@ public interface EntityManager extends DatastoreAccess { */ DatastoreKey allocateId(Object entity); + /** + * Allocates ID for the given entity and returns the allocated ID. The entity must have its + * identifier of type numeric (long/Long). + * + * @param entity + * the the entity. + * @param entityType + * the entity type + * @return the allocated ID {@link DatastoreKey}. + * @throws IllegalArgumentException + * if the ID type of the entity is not numeric, or if the entity has a valid ID + * (non-null and non-zero). + * @throws EntityManagerException + * if any error occurs during ID allocation + */ + DatastoreKey allocateId(Object entity, Type entityType); + } diff --git a/src/main/java/com/jmethods/catatumbo/MapperFactory.java b/src/main/java/com/jmethods/catatumbo/MapperFactory.java index 0c26878..d389ee9 100644 --- a/src/main/java/com/jmethods/catatumbo/MapperFactory.java +++ b/src/main/java/com/jmethods/catatumbo/MapperFactory.java @@ -65,7 +65,7 @@ /** * A factory for producing data mappers that are used for mapping fields of model class to/from the * Cloud Datastore. - * + * * @author Sai Pullabhotla * */ @@ -97,7 +97,7 @@ private MapperFactory() { /** * Returns the singleton instance of this MapperFactory. - * + * * @return the singleton instance of this MapperFactory. */ public static MapperFactory getInstance() { @@ -108,17 +108,18 @@ public static MapperFactory getInstance() { * Returns the mapper for the given field. If the field has a custom mapper, a new instance of the * specified mapper will be created and returned. Otherwise, one of the built-in mappers will be * returned based on the field type. - * + * * @param field * the field + * @param fieldType + * the field type * @return the mapper for the given field. */ - public Mapper getMapper(Field field) { + public Mapper getMapper(Field field, Class fieldType) { PropertyMapper propertyMapperAnnotation = field.getAnnotation(PropertyMapper.class); if (propertyMapperAnnotation != null) { return createCustomMapper(field, propertyMapperAnnotation); } - Class fieldType = field.getType(); if (fieldType.equals(BigDecimal.class)) { Decimal decimalAnnotation = field.getAnnotation(Decimal.class); if (decimalAnnotation != null) { @@ -128,13 +129,16 @@ public Mapper getMapper(Field field) { if (List.class.isAssignableFrom(fieldType) || Set.class.isAssignableFrom(fieldType)) { return CollectionMapperFactory.getInstance().getMapper(field); } - return getMapper(field.getGenericType()); + if (Map.class.isAssignableFrom(fieldType)) { + return getMapper(field.getGenericType()); + } + return getMapper(fieldType); } /** * Returns a mapper for the given type. If a mapper that can handle given type exists in the * cache, it will be returned. Otherwise, a new mapper will be created. - * + * * @param type * the type of field in the model class * @return a {@link Mapper} that is capable of mapping the given type. @@ -151,7 +155,7 @@ public Mapper getMapper(Type type) { * Sets or registers the given mapper for the given type. This method must be called before * performing any persistence operations, preferably, during application startup. Entities that * were introspected before calling this method will NOT use the new mapper. - * + * * @param type * the type * @param mapper @@ -171,7 +175,7 @@ public void setDefaultMapper(Type type, Mapper mapper) { /** * Creates a new mapper for the given type. - * + * * @param type * the type for which a mapper is to be created * @return a mapper that can handle the mapping of given type to/from the Cloud Datastore. @@ -200,7 +204,7 @@ private Mapper createMapper(Type type) { /** * Creates a mapper for the given class. - * + * * @param clazz * the class * @return the mapper for the given class. @@ -222,7 +226,7 @@ private Mapper createMapper(Class clazz) { /** * Creates a {@link Mapper} for the given class/type. - * + * * @param type * the type * @return a {@link Mapper} for the given class/type. @@ -287,7 +291,7 @@ private void createDefaultMappers() { /** * Creates and returns a custom mapper for the given field. - * + * * @param field * the field * @param propertyMapperAnnotation diff --git a/src/main/java/com/jmethods/catatumbo/impl/DatastoreUtils.java b/src/main/java/com/jmethods/catatumbo/impl/DatastoreUtils.java index 3a58c8c..d0eeeaf 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DatastoreUtils.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DatastoreUtils.java @@ -16,6 +16,7 @@ package com.jmethods.catatumbo.impl; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -34,7 +35,7 @@ /** * Utility methods. - * + * * @author Sai Pullabhotla * */ @@ -62,20 +63,20 @@ private DatastoreUtils() { /** * Converts the given list of native entities to a list of model objects of given type, * entityClass. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param nativeEntities * native entities to convert * @return the list of model objects */ - static List toEntities(Class entityClass, List nativeEntities) { + private static List toEntities(Type entityType, List nativeEntities) { if (nativeEntities == null || nativeEntities.isEmpty()) { return new ArrayList<>(); } List entities = new ArrayList<>(nativeEntities.size()); for (Entity nativeEntity : nativeEntities) { - E entity = Unmarshaller.unmarshal(nativeEntity, entityClass); + E entity = Unmarshaller.unmarshal(nativeEntity, entityType); entities.add(entity); } return entities; @@ -84,23 +85,23 @@ static List toEntities(Class entityClass, List nativeEntities) /** * Converts the given array of native entities to a list of model objects of given type, * entityClass. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param nativeEntities * native entities to convert * @return the list of model objects */ - static List toEntities(Class entityClass, Entity[] nativeEntities) { + private static List toEntities(Type entityType, Entity[] nativeEntities) { if (nativeEntities == null || nativeEntities.length == 0) { return new ArrayList<>(); } - return toEntities(entityClass, Arrays.asList(nativeEntities)); + return toEntities(entityType, Arrays.asList(nativeEntities)); } /** * Converts the given list of model objects to an array of FullEntity objects. - * + * * @param entities * the model objects to convert. * @param entityManager @@ -110,18 +111,18 @@ static List toEntities(Class entityClass, Entity[] nativeEntities) { * @return the equivalent FullEntity array */ static FullEntity[] toNativeFullEntities(List entities, DefaultEntityManager entityManager, - Marshaller.Intent intent) { + Marshaller.Intent intent, Type entityType) { FullEntity[] nativeEntities = new FullEntity[entities.size()]; for (int i = 0; i < entities.size(); i++) { nativeEntities[i] = (FullEntity) Marshaller.marshal(entityManager, entities.get(i), - intent); + intent, entityType); } return nativeEntities; } /** * Converts the given list of model objects to an array of native Entity objects. - * + * * @param entities * the model objects to convert. * @param entityManager @@ -131,17 +132,18 @@ static FullEntity[] toNativeFullEntities(List entities, DefaultEntityManag * @return the equivalent Entity array */ static Entity[] toNativeEntities(List entities, DefaultEntityManager entityManager, - Marshaller.Intent intent) { + Marshaller.Intent intent, Type entityType) { Entity[] nativeEntities = new Entity[entities.size()]; for (int i = 0; i < entities.size(); i++) { - nativeEntities[i] = (Entity) Marshaller.marshal(entityManager, entities.get(i), intent); + nativeEntities[i] = (Entity) Marshaller.marshal(entityManager, entities.get(i), + intent, entityType); } return nativeEntities; } /** * Increments the version property of the given entity by one. - * + * * @param nativeEntity * the target entity * @param versionMetadata @@ -156,7 +158,7 @@ static Entity incrementVersion(Entity nativeEntity, PropertyMetadata versionMeta /** * Rolls back the given transaction, if it is still active. - * + * * @param transaction * the transaction to roll back. */ @@ -172,7 +174,7 @@ static void rollbackIfActive(Transaction transaction) { /** * Converts/wraps the given native keys into a list of {@link DatastoreKey} objects. - * + * * @param nativeKeys * the native keys * @return a list of {@link DatastoreKey} objects. @@ -191,7 +193,7 @@ static List toDatastoreKeys(List nativeKeys) { /** * Converts/Unwraps the given list of {@link DatastoreKey} objects into an array of native * {@link Key}s. - * + * * @param keys * the list of {@link DatastoreKey} objects * @return the native keys @@ -207,14 +209,14 @@ static Key[] toNativeKeys(List keys) { /** * Validates if the given entity is valid for deferred ID allocation. Deferred ID allocation is * valid for entities using a numeric ID. - * + * * @param entity * the entity to validate * @throws EntityManagerException * if the given entity does not use a numeric ID */ - static void validateDeferredIdAllocation(Object entity) { - IdentifierMetadata identifierMetadata = EntityIntrospector.getIdentifierMetadata(entity); + public static void validateDeferredIdAllocation(Object entity, Type type) { + IdentifierMetadata identifierMetadata = EntityIntrospector.getIdentifierMetadata(entity, type); if (identifierMetadata.getDataType() == DataType.STRING) { throw new EntityManagerException( "Deferred ID allocation is not applicable for entities with String identifiers. "); @@ -225,7 +227,7 @@ static void validateDeferredIdAllocation(Object entity) { /** * Wraps the given DatastoreException into an {@link EntityManagerException} or a subclass of * {@link EntityManagerException}. - * + * * @param exp * the DatastoreException * @return An {@link EntityManagerException} or a subclass of {@link EntityManagerException}. diff --git a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreBatch.java b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreBatch.java index eefe054..4bb71ea 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreBatch.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreBatch.java @@ -16,6 +16,7 @@ package com.jmethods.catatumbo.impl; +import java.lang.reflect.Type; import java.util.List; import com.google.cloud.datastore.Batch; @@ -28,7 +29,7 @@ /** * Default implementation of {@link DatastoreBatch} to execute batch updates. - * + * * @author Sai Pullabhotla * */ @@ -56,7 +57,7 @@ public class DefaultDatastoreBatch implements DatastoreBatch { /** * Creates a new instance of DefaultDatastoreBatch. - * + * * @param entityManager * a reference to the entity manager */ @@ -69,7 +70,7 @@ public DefaultDatastoreBatch(DefaultEntityManager entityManager) { /** * Returns the entity manager from which this batch was created. - * + * * @return the entity manager from which this batch was created. */ public DefaultEntityManager getEntityManager() { @@ -78,7 +79,7 @@ public DefaultEntityManager getEntityManager() { /** * Returns the native batch. - * + * * @return the native batch */ public Batch getNativeBatch() { @@ -87,20 +88,35 @@ public Batch getNativeBatch() { @Override public E insert(E entity) { - return writer.insert(entity); + return insert(entity, null); + } + + @Override + public E insert(E entity, Type entityType) { + return writer.insert(entity, entityType); } @Override public List insert(List entities) { - return writer.insert(entities); + return insert(entities, null); + } + + @Override + public List insert(List entities, Type entityType) { + return writer.insert(entities, entityType); } @Override public void insertWithDeferredIdAllocation(E entity) { + insertWithDeferredIdAllocation(entity, null); + } + + @Override + public void insertWithDeferredIdAllocation(E entity, Type entityType) { try { - DatastoreUtils.validateDeferredIdAllocation(entity); + DatastoreUtils.validateDeferredIdAllocation(entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.INSERT); + Intent.INSERT, entityType); nativeBatch.addWithDeferredIdAllocation(nativeEntity); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -109,13 +125,18 @@ public void insertWithDeferredIdAllocation(E entity) { @Override public void insertWithDeferredIdAllocation(List entities) { + insertWithDeferredIdAllocation(entities, null); + } + + @Override + public void insertWithDeferredIdAllocation(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return; } try { - DatastoreUtils.validateDeferredIdAllocation(entities.get(0)); + DatastoreUtils.validateDeferredIdAllocation(entities.get(0), entityType); FullEntity[] nativeEntities = DatastoreUtils.toNativeFullEntities(entities, entityManager, - Intent.INSERT); + Intent.INSERT, entityType); nativeBatch.addWithDeferredIdAllocation(nativeEntities); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -125,30 +146,55 @@ public void insertWithDeferredIdAllocation(List entities) { @Override public E update(E entity) { - return writer.update(entity); + return update(entity, null); + } + + @Override + public E update(E entity, Type entityType) { + return writer.update(entity, entityType); } @Override public List update(List entities) { - return writer.update(entities); + return update(entities, null); + } + + @Override + public List update(List entities, Type entityType) { + return writer.update(entities, entityType); } @Override public E upsert(E entity) { - return writer.upsert(entity); + return upsert(entity, null); + } + + @Override + public E upsert(E entity, Type entityType) { + return writer.upsert(entity, entityType); } @Override public List upsert(List entities) { - return writer.upsert(entities); + return upsert(entities, null); + } + + @Override + public List upsert(List entities, Type entityType) { + return writer.upsert(entities, entityType); } @Override public void upsertWithDeferredIdAllocation(E entity) { + upsertWithDeferredIdAllocation(entity, null); + } + + @Override + public void upsertWithDeferredIdAllocation(E entity, Type entityType) { try { - DatastoreUtils.validateDeferredIdAllocation(entity); + DatastoreUtils.validateDeferredIdAllocation(entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.UPSERT); + Intent.UPSERT, entityType); nativeBatch.putWithDeferredIdAllocation(nativeEntity); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -158,13 +204,18 @@ public void upsertWithDeferredIdAllocation(E entity) { @Override public void upsertWithDeferredIdAllocation(List entities) { + upsertWithDeferredIdAllocation(entities, null); + } + + @Override + public void upsertWithDeferredIdAllocation(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return; } try { - DatastoreUtils.validateDeferredIdAllocation(entities.get(0)); + DatastoreUtils.validateDeferredIdAllocation(entities.get(0), entityType); FullEntity[] nativeEntities = DatastoreUtils.toNativeFullEntities(entities, entityManager, - Intent.UPSERT); + Intent.UPSERT, entityType); nativeBatch.putWithDeferredIdAllocation(nativeEntities); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -174,32 +225,42 @@ public void upsertWithDeferredIdAllocation(List entities) { @Override public void delete(Object entity) { - writer.delete(entity); + delete(entity, null); + } + + @Override + public void delete(Object entity, Type entityType) { + writer.delete(entity, entityType); } @Override public void delete(List entities) { - writer.delete(entities); + delete(entities, null); } @Override - public void delete(Class entityClass, long id) { - writer.delete(entityClass, id); + public void delete(List entities, Type entityType) { + writer.delete(entities, entityType); } @Override - public void delete(Class entityClass, String id) { - writer.delete(entityClass, id); + public void delete(Type entityType, long id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, long id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, String id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, String id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, DatastoreKey parentKey, long id) { + writer.delete(entityType, parentKey, id); + } + + @Override + public void delete(Type entityType, DatastoreKey parentKey, String id) { + writer.delete(entityType, parentKey, id); } @Override @@ -229,7 +290,7 @@ public Response submit() { /** * Implementation of {@link com.jmethods.catatumbo.DatastoreBatch.Response}. - * + * * @author Sai Pullabhotla * */ @@ -242,7 +303,7 @@ static class DefaultResponse implements Response { /** * Creates a new instance of DefaultResponse. - * + * * @param nativeResponse * the native response */ diff --git a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreReader.java b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreReader.java index 8aed460..9518461 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreReader.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreReader.java @@ -16,6 +16,7 @@ package com.jmethods.catatumbo.impl; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -43,7 +44,7 @@ /** * Worker class for performing read operations on the Cloud Datastore. - * + * * @author Sai Pullabhotla * */ @@ -66,7 +67,7 @@ public class DefaultDatastoreReader { /** * Creates a new instance of DefaultDatastoreReader. - * + * * @param entityManager * the entity manager that created this reader. */ @@ -78,7 +79,7 @@ public DefaultDatastoreReader(DefaultEntityManager entityManager) { /** * Creates a new instance of DefaultDatastoreReader. - * + * * @param transaction * the transaction that created this reader. */ @@ -91,9 +92,9 @@ public DefaultDatastoreReader(DefaultDatastoreTransaction transaction) { /** * Loads and returns the entity with the given ID. The entity is assumed to be a root entity (no * parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param id * the ID of the entity * @return the Entity object or null, if the the entity with the given ID does not @@ -101,16 +102,16 @@ public DefaultDatastoreReader(DefaultDatastoreTransaction transaction) { * @throws EntityManagerException * if any error occurs while inserting. */ - public E load(Class entityClass, long id) { - return load(entityClass, null, id); + public E load(Type entityType, long id) { + return load(entityType, null, id); } /** * Loads and returns the entity with the given ID. The entity kind is determined from the supplied * class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param parentKey * the parent key of the entity. * @param id @@ -120,23 +121,23 @@ public E load(Class entityClass, long id) { * @throws EntityManagerException * if any error occurs while inserting. */ - public E load(Class entityClass, DatastoreKey parentKey, long id) { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + public E load(Type entityType, DatastoreKey parentKey, long id) { + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey; if (parentKey == null) { nativeKey = entityManager.newNativeKeyFactory().setKind(entityMetadata.getKind()).newKey(id); } else { nativeKey = Key.newBuilder(parentKey.nativeKey(), entityMetadata.getKind(), id).build(); } - return fetch(entityClass, nativeKey); + return fetch(entityType, nativeKey); } /** * Loads and returns the entity with the given ID. The entity is assumed to be a root entity (no * parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param id * the ID of the entity * @return the Entity object or null, if the the entity with the given ID does not @@ -144,16 +145,16 @@ public E load(Class entityClass, DatastoreKey parentKey, long id) { * @throws EntityManagerException * if any error occurs while inserting. */ - public E load(Class entityClass, String id) { - return load(entityClass, null, id); + public E load(Type entityType, String id) { + return load(entityType, null, id); } /** * Loads and returns the entity with the given ID. The entity kind is determined from the supplied * class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param parentKey * the parent key of the entity. * @param id @@ -163,21 +164,21 @@ public E load(Class entityClass, String id) { * @throws EntityManagerException * if any error occurs while inserting. */ - public E load(Class entityClass, DatastoreKey parentKey, String id) { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + public E load(Type entityType, DatastoreKey parentKey, String id) { + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey; if (parentKey == null) { nativeKey = entityManager.newNativeKeyFactory().setKind(entityMetadata.getKind()).newKey(id); } else { nativeKey = Key.newBuilder(parentKey.nativeKey(), entityMetadata.getKind(), id).build(); } - return fetch(entityClass, nativeKey); + return fetch(entityType, nativeKey); } /** * Retrieves and returns the entity with the given key. - * - * @param entityClass + * + * @param entityType * the expected result type * @param key * the entity key @@ -186,16 +187,16 @@ public E load(Class entityClass, DatastoreKey parentKey, String id) { * @throws EntityManagerException * if any error occurs while accessing the Datastore. */ - public E load(Class entityClass, DatastoreKey key) { - return fetch(entityClass, key.nativeKey()); + public E load(Type entityType, DatastoreKey key) { + return fetch(entityType, key.nativeKey()); } /** * Loads and returns the entities with the given numeric IDs. The entities are assumed to * be a root entities (no parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param identifiers * the IDs of the entities * @return the list of entity objects in the same order as the given list of identifiers. If one @@ -204,17 +205,17 @@ public E load(Class entityClass, DatastoreKey key) { * @throws EntityManagerException * if any error occurs while inserting. */ - public List loadById(Class entityClass, List identifiers) { - Key[] nativeKeys = longListToNativeKeys(entityClass, identifiers); - return fetch(entityClass, nativeKeys); + public List loadById(Type entityType, List identifiers) { + Key[] nativeKeys = longListToNativeKeys(entityType, identifiers); + return fetch(entityType, nativeKeys); } /** * Loads and returns the entities with the given names (a.k.a String IDs). The entities are * assumed to be root entities (no parent). The entity kind is determined from the supplied class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @param identifiers * the IDs of the entities * @return the list of entity objects in the same order as the given list of identifiers. If one @@ -223,44 +224,44 @@ public List loadById(Class entityClass, List identifiers) { * @throws EntityManagerException * if any error occurs while inserting. */ - public List loadByName(Class entityClass, List identifiers) { - Key[] nativeKeys = stringListToNativeKeys(entityClass, identifiers); - return fetch(entityClass, nativeKeys); + public List loadByName(Type entityType, List identifiers) { + Key[] nativeKeys = stringListToNativeKeys(entityType, identifiers); + return fetch(entityType, nativeKeys); } /** * Retrieves and returns the entities for the given keys. - * - * @param entityClass - * the expected result type + * + * @param entityType + * the entity type * @param keys * the entity keys * @return the entities for the given keys. If one or more requested keys do not exist in the * Cloud Datastore, the corresponding item in the returned list be null. - * + * * @throws EntityManagerException * if any error occurs while accessing the Datastore. */ - public List loadByKey(Class entityClass, List keys) { + public List loadByKey(Type entityType, List keys) { Key[] nativeKeys = DatastoreUtils.toNativeKeys(keys); - return fetch(entityClass, nativeKeys); + return fetch(entityType, nativeKeys); } /** * Fetches the entity given the native key. - * - * @param entityClass + * + * @param entityType * the expected result type * @param nativeKey * the native key * @return the entity with the given key, or null, if no entity exists with the given * key. */ - private E fetch(Class entityClass, Key nativeKey) { + private E fetch(Type entityType, Key nativeKey) { try { Entity nativeEntity = nativeReader.get(nativeKey); - E entity = Unmarshaller.unmarshal(nativeEntity, entityClass); - entityManager.executeEntityListeners(CallbackType.POST_LOAD, entity); + E entity = Unmarshaller.unmarshal(nativeEntity, entityType); + entityManager.executeEntityListeners(CallbackType.POST_LOAD, entity, entityType); return entity; } catch (DatastoreException exp) { throw new EntityManagerException(exp); @@ -269,19 +270,19 @@ private E fetch(Class entityClass, Key nativeKey) { /** * Fetches a list of entities for the given native keys. - * - * @param entityClass + * + * @param entityType * the expected result type * @param nativeKeys * the native keys of the entities * @return the list of entities. If one or more keys do not exist, the corresponding item in the * returned list will be null. */ - private List fetch(Class entityClass, Key[] nativeKeys) { + private List fetch(Type entityType, Key[] nativeKeys) { try { List nativeEntities = nativeReader.fetch(nativeKeys); - List entities = DatastoreUtils.toEntities(entityClass, nativeEntities); - entityManager.executeEntityListeners(CallbackType.POST_LOAD, entities); + List entities = DatastoreUtils.toEntities(entityType, nativeEntities); + entityManager.executeEntityListeners(CallbackType.POST_LOAD, entities, entityType); return entities; } catch (DatastoreException exp) { throw new EntityManagerException(exp); @@ -293,7 +294,7 @@ private List fetch(Class entityClass, Key[] nativeKeys) { * returned {@link EntityQueryRequest} can be further customized to set any bindings (positional * or named), and then be executed by calling the execute or * executeEntityQuery methods. - * + * * @param query * the GQL query * @return a new QueryRequest for the given GQL query @@ -307,7 +308,7 @@ public EntityQueryRequest createEntityQueryRequest(String query) { * returned {@link ProjectionQueryRequest} can further be customized to set any positional and/or * named bindings, and then be executed by calling the execute or * executeProjectionQuery methods. - * + * * @param query * the GQL projection query * @return a new ProjectionQueryRequest for the given query @@ -321,7 +322,7 @@ public ProjectionQueryRequest createProjectionQueryRequest(String query) { * requests must only have __key__ in the SELECT list of field. The returned * {@link KeyQueryRequest} can further be customized to set any positional and/or named bindings, * and then be executed by calling the executeKeyQuery method. - * + * * @param query * the GQL projection query * @return a new ProjectionQueryRequest for the given query @@ -332,15 +333,15 @@ public KeyQueryRequest createKeyQueryRequest(String query) { /** * Executes the given {@link EntityQueryRequest} and returns the response. - * + * * @param expectedResultType * the expected type of results. * @param request * the entity query request * @return the query response */ - public QueryResponse executeEntityQueryRequest(Class expectedResultType, - EntityQueryRequest request) { + public QueryResponse executeEntityQueryRequest(Type expectedResultType, + EntityQueryRequest request) { try { GqlQuery.Builder queryBuilder = Query.newGqlQueryBuilder(ResultType.ENTITY, request.getQuery()); @@ -360,7 +361,7 @@ public QueryResponse executeEntityQueryRequest(Class expectedResultTyp } response.setResults(entities); response.setEndCursor(new DefaultDatastoreCursor(results.getCursorAfter().toUrlSafe())); - entityManager.executeEntityListeners(CallbackType.POST_LOAD, entities); + entityManager.executeEntityListeners(CallbackType.POST_LOAD, entities, expectedResultType); return response; } catch (DatastoreException exp) { throw new EntityManagerException(exp); @@ -369,14 +370,14 @@ public QueryResponse executeEntityQueryRequest(Class expectedResultTyp /** * Executes the given {@link ProjectionQueryRequest} and returns the response. - * + * * @param expectedResultType * the expected type of results. * @param request * the projection query request * @return the query response */ - public QueryResponse executeProjectionQueryRequest(Class expectedResultType, + public QueryResponse executeProjectionQueryRequest(Type expectedResultType, ProjectionQueryRequest request) { try { GqlQuery.Builder queryBuilder = Query @@ -406,7 +407,7 @@ public QueryResponse executeProjectionQueryRequest(Class expectedResul /** * Executes the given {@link KeyQueryRequest} and returns the response. - * + * * @param request * the key query request * @return the query response @@ -439,18 +440,18 @@ public QueryResponse executeKeyQueryRequest(KeyQueryRequest reques /** * Converts the given list of identifiers into an array of native Key objects. - * - * @param entityClass + * + * @param entityType * the entity class to which these identifiers belong to. * @param identifiers * the list of identifiers to convert. * @return an array of Key objects */ - private Key[] longListToNativeKeys(Class entityClass, List identifiers) { + private Key[] longListToNativeKeys(Type entityType, List identifiers) { if (identifiers == null || identifiers.isEmpty()) { return new Key[0]; } - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key[] nativeKeys = new Key[identifiers.size()]; KeyFactory keyFactory = entityManager.newNativeKeyFactory(); keyFactory.setKind(entityMetadata.getKind()); @@ -463,18 +464,18 @@ private Key[] longListToNativeKeys(Class entityClass, List identifiers) /** * Converts the given list of identifiers into an array of native Key objects. - * - * @param entityClass + * + * @param entityType * the entity class to which these identifiers belong to. * @param identifiers * the list of identifiers to convert. * @return an array of Key objects */ - private Key[] stringListToNativeKeys(Class entityClass, List identifiers) { + private Key[] stringListToNativeKeys(Type entityType, List identifiers) { if (identifiers == null || identifiers.isEmpty()) { return new Key[0]; } - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key[] nativeKeys = new Key[identifiers.size()]; KeyFactory keyFactory = entityManager.newNativeKeyFactory(); keyFactory.setKind(entityMetadata.getKind()); diff --git a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreTransaction.java b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreTransaction.java index 38db571..4b867f9 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreTransaction.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreTransaction.java @@ -18,6 +18,7 @@ import static com.jmethods.catatumbo.impl.DatastoreUtils.toNativeFullEntities; +import java.lang.reflect.Type; import java.util.List; import com.google.cloud.datastore.Datastore; @@ -35,7 +36,7 @@ /** * Default implementation of the {@link DatastoreTransaction} interface. - * + * * @author Sai Pullabhotla * */ @@ -68,7 +69,7 @@ public class DefaultDatastoreTransaction implements DatastoreTransaction { /** * Creates a new instance of DatastoreTransaction. - * + * * @param entityManager * the entity manager that created this transaction. */ @@ -82,7 +83,7 @@ public DefaultDatastoreTransaction(DefaultEntityManager entityManager) { /** * Returns the entity manager that created this transaction. - * + * * @return the entity manager that created this transaction. */ public DefaultEntityManager getEntityManager() { @@ -91,7 +92,7 @@ public DefaultEntityManager getEntityManager() { /** * Returns the native transaction. - * + * * @return the native transaction. */ public Transaction getNativeTransaction() { @@ -100,10 +101,15 @@ public Transaction getNativeTransaction() { @Override public void insertWithDeferredIdAllocation(E entity) { + insertWithDeferredIdAllocation(entity, null); + } + + @Override + public void insertWithDeferredIdAllocation(E entity, Type entityType) { try { - DatastoreUtils.validateDeferredIdAllocation(entity); + DatastoreUtils.validateDeferredIdAllocation(entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.INSERT); + Intent.INSERT, entityType); nativeTransaction.addWithDeferredIdAllocation(nativeEntity); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -113,12 +119,18 @@ public void insertWithDeferredIdAllocation(E entity) { @Override public void insertWithDeferredIdAllocation(List entities) { + insertWithDeferredIdAllocation(entities, null); + } + + @Override + public void insertWithDeferredIdAllocation(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return; } try { - DatastoreUtils.validateDeferredIdAllocation(entities.get(0)); - FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, Intent.INSERT); + DatastoreUtils.validateDeferredIdAllocation(entities.get(0), entityType); + FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, + Intent.INSERT, entityType); nativeTransaction.addWithDeferredIdAllocation(nativeEntities); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -127,10 +139,15 @@ public void insertWithDeferredIdAllocation(List entities) { @Override public void upsertWithDeferredIdAllocation(E entity) { + upsertWithDeferredIdAllocation(entity, null); + } + + @Override + public void upsertWithDeferredIdAllocation(E entity, Type entityType) { try { - DatastoreUtils.validateDeferredIdAllocation(entity); + DatastoreUtils.validateDeferredIdAllocation(entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.UPSERT); + Intent.UPSERT, entityType); nativeTransaction.putWithDeferredIdAllocation(nativeEntity); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -140,12 +157,18 @@ public void upsertWithDeferredIdAllocation(E entity) { @Override public void upsertWithDeferredIdAllocation(List entities) { + upsertWithDeferredIdAllocation(entities, null); + } + + @Override + public void upsertWithDeferredIdAllocation(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return; } try { - DatastoreUtils.validateDeferredIdAllocation(entities.get(0)); - FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, Intent.UPSERT); + DatastoreUtils.validateDeferredIdAllocation(entities.get(0), entityType); + FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, + Intent.UPSERT, entityType); nativeTransaction.putWithDeferredIdAllocation(nativeEntities); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -188,7 +211,7 @@ public void rollback() { /** * Transaction Response containing the results of a transaction commit. - * + * * @author Sai Pullabhotla * */ @@ -201,7 +224,7 @@ static class DefaultResponse implements Response { /** * Creates a new instance of DefaultResponse. - * + * * @param nativeResponse * the native transaction response */ @@ -217,62 +240,102 @@ public List getGeneratedKeys() { @Override public E insert(E entity) { - return writer.insert(entity); + return insert(entity, null); + } + + @Override + public E insert(E entity, Type entityType) { + return writer.insert(entity, entityType); } @Override public List insert(List entities) { - return writer.insert(entities); + return insert(entities, null); + } + + @Override + public List insert(List entities, Type entityType) { + return writer.insert(entities, entityType); } @Override public E update(E entity) { - return writer.updateWithOptimisticLock(entity); + return update(entity, null); + } + + @Override + public E update(E entity, Type entityType) { + return writer.updateWithOptimisticLock(entity, entityType); } @Override public List update(List entities) { - return writer.updateWithOptimisticLock(entities); + return update(entities, null); + } + + @Override + public List update(List entities, Type entityType) { + return writer.updateWithOptimisticLock(entities, entityType); } @Override public E upsert(E entity) { - return writer.upsert(entity); + return upsert(entity, null); + } + + @Override + public E upsert(E entity, Type entityType) { + return writer.upsert(entity, entityType); } @Override public List upsert(List entities) { - return writer.upsert(entities); + return upsert(entities, null); + } + + @Override + public List upsert(List entities, Type entityType) { + return writer.upsert(entities, entityType); } @Override public void delete(Object entity) { - writer.delete(entity); + delete(entity, null); + } + + @Override + public void delete(Object entity, Type entityType) { + writer.delete(entity, entityType); } @Override public void delete(List entities) { - writer.delete(entities); + delete(entities, null); + } + + @Override + public void delete(List entities, Type entityType) { + writer.delete(entities, entityType); } @Override - public void delete(Class entityClass, long id) { - writer.delete(entityClass, id); + public void delete(Type entityType, long id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, String id) { - writer.delete(entityClass, id); + public void delete(Type entityType, String id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, long id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, DatastoreKey parentKey, long id) { + writer.delete(entityType, parentKey, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, String id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, DatastoreKey parentKey, String id) { + writer.delete(entityType, parentKey, id); } @Override @@ -286,43 +349,43 @@ public void deleteByKey(List keys) { } @Override - public E load(Class entityClass, long id) { - return reader.load(entityClass, id); + public E load(Type entityType, long id) { + return reader.load(entityType, id); } @Override - public E load(Class entityClass, String id) { - return reader.load(entityClass, id); + public E load(Type entityType, String id) { + return reader.load(entityType, id); } @Override - public E load(Class entityClass, DatastoreKey parentKey, long id) { - return reader.load(entityClass, parentKey, id); + public E load(Type entityType, DatastoreKey parentKey, long id) { + return reader.load(entityType, parentKey, id); } @Override - public E load(Class entityClass, DatastoreKey parentKey, String id) { - return reader.load(entityClass, parentKey, id); + public E load(Type entityType, DatastoreKey parentKey, String id) { + return reader.load(entityType, parentKey, id); } @Override - public E load(Class entityClass, DatastoreKey key) { - return reader.load(entityClass, key); + public E load(Type entityType, DatastoreKey key) { + return reader.load(entityType, key); } @Override - public List loadById(Class entityClass, List identifiers) { - return reader.loadById(entityClass, identifiers); + public List loadById(Type entityType, List identifiers) { + return reader.loadById(entityType, identifiers); } @Override - public List loadByName(Class entityClass, List identifiers) { - return reader.loadByName(entityClass, identifiers); + public List loadByKey(Type entityType, List keys) { + return reader.loadByKey(entityType, keys); } @Override - public List loadByKey(Class entityClass, List keys) { - return reader.loadByKey(entityClass, keys); + public List loadByName(Type entityType, List identifiers) { + return reader.loadByName(entityType, identifiers); } @Override @@ -341,13 +404,13 @@ public KeyQueryRequest createKeyQueryRequest(String query) { } @Override - public QueryResponse executeEntityQueryRequest(Class expectedResultType, - EntityQueryRequest request) { + public QueryResponse executeEntityQueryRequest(Type expectedResultType, + EntityQueryRequest request) { return reader.executeEntityQueryRequest(expectedResultType, request); } @Override - public QueryResponse executeProjectionQueryRequest(Class expectedResultType, + public QueryResponse executeProjectionQueryRequest(Type expectedResultType, ProjectionQueryRequest request) { return reader.executeProjectionQueryRequest(expectedResultType, request); } diff --git a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreWriter.java b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreWriter.java index ff5f51a..959ee91 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreWriter.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DefaultDatastoreWriter.java @@ -21,6 +21,7 @@ import static com.jmethods.catatumbo.impl.DatastoreUtils.toNativeEntities; import static com.jmethods.catatumbo.impl.DatastoreUtils.toNativeFullEntities; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -39,7 +40,7 @@ /** * Worker class for performing write operations on the Cloud Datastore. - * + * * @author Sai Pullabhotla * */ @@ -63,7 +64,7 @@ public class DefaultDatastoreWriter { /** * Creates a new instance of DefaultDatastoreWriter. - * + * * @param entityManager * a reference to the entity manager. */ @@ -75,7 +76,7 @@ public DefaultDatastoreWriter(DefaultEntityManager entityManager) { /** * Creates a new instance of DefaultDatastoreWriter for executing batch updates. - * + * * @param batch * the {@link DefaultDatastoreBatch}. */ @@ -87,7 +88,7 @@ public DefaultDatastoreWriter(DefaultDatastoreBatch batch) { /** * Creates a new instance of DefaultDatastoreWriter for transactional updates. - * + * * @param transaction * the {@link DefaultDatastoreTransaction}. */ @@ -99,7 +100,7 @@ public DefaultDatastoreWriter(DefaultDatastoreTransaction transaction) { /** * Inserts the given entity into the Cloud Datastore. - * + * * @param entity * the entity to insert * @return the inserted entity. The inserted entity will not be same as the passed in entity. For @@ -107,15 +108,16 @@ public DefaultDatastoreWriter(DefaultDatastoreTransaction transaction) { * @throws EntityManagerException * if any error occurs while inserting. */ - public E insert(E entity) { + public E insert(E entity, Type entityType) { try { - entityManager.executeEntityListeners(CallbackType.PRE_INSERT, entity); + entityManager.executeEntityListeners(CallbackType.PRE_INSERT, entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.INSERT); + Intent.INSERT, entityType); + Type entityClass = entityType != null ? entityType : entity.getClass(); Entity insertedNativeEntity = nativeWriter.add(nativeEntity); @SuppressWarnings("unchecked") - E insertedEntity = (E) Unmarshaller.unmarshal(insertedNativeEntity, entity.getClass()); - entityManager.executeEntityListeners(CallbackType.POST_INSERT, insertedEntity); + E insertedEntity = (E) Unmarshaller.unmarshal(insertedNativeEntity, entityClass); + entityManager.executeEntityListeners(CallbackType.POST_INSERT, insertedEntity, entityType); return insertedEntity; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -124,7 +126,7 @@ public E insert(E entity) { /** * Inserts the given list of entities into the Cloud Datastore. - * + * * @param entities * the entities to insert. * @return the inserted entities. The inserted entities will not be same as the passed in @@ -134,17 +136,18 @@ public E insert(E entity) { * if any error occurs while inserting. */ @SuppressWarnings("unchecked") - public List insert(List entities) { + public List insert(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return new ArrayList<>(); } try { - entityManager.executeEntityListeners(CallbackType.PRE_INSERT, entities); - FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, Intent.INSERT); - Class entityClass = entities.get(0).getClass(); + entityManager.executeEntityListeners(CallbackType.PRE_INSERT, entities, entityType); + FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, + Intent.INSERT, entityType); + Type entityClass = entityType != null ? entityType : entities.get(0).getClass(); List insertedNativeEntities = nativeWriter.add(nativeEntities); List insertedEntities = (List) toEntities(entityClass, insertedNativeEntities); - entityManager.executeEntityListeners(CallbackType.POST_INSERT, insertedEntities); + entityManager.executeEntityListeners(CallbackType.POST_INSERT, insertedEntities, entityType); return insertedEntities; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -154,7 +157,7 @@ public List insert(List entities) { /** * Updates the given entity in the Cloud Datastore. The passed in Entity must have its ID set for * the update to work. - * + * * @param entity * the entity to update * @return the updated entity. @@ -162,14 +165,15 @@ public List insert(List entities) { * if any error occurs while updating. */ @SuppressWarnings("unchecked") - public E update(E entity) { + public E update(E entity, Type entityType) { try { - entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entity); + entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entity, entityType); Intent intent = (nativeWriter instanceof Batch) ? Intent.BATCH_UPDATE : Intent.UPDATE; - Entity nativeEntity = (Entity) Marshaller.marshal(entityManager, entity, intent); + Entity nativeEntity = (Entity) Marshaller.marshal(entityManager, entity, intent, entityType); + Type entityClass = entityType != null ? entityType : entity.getClass(); nativeWriter.update(nativeEntity); - E updatedEntity = (E) Unmarshaller.unmarshal(nativeEntity, entity.getClass()); - entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntity); + E updatedEntity = (E) Unmarshaller.unmarshal(nativeEntity, entityClass); + entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntity, entityType); return updatedEntity; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -179,7 +183,7 @@ public E update(E entity) { /** * Updates the given list of entities in the Cloud Datastore. - * + * * @param entities * the entities to update. The passed in entities must have their ID set for the update * to work. @@ -188,18 +192,18 @@ public E update(E entity) { * if any error occurs while inserting. */ @SuppressWarnings("unchecked") - public List update(List entities) { + public List update(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return new ArrayList<>(); } try { - Class entityClass = (Class) entities.get(0).getClass(); - entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entities); + Type entityClass = entityType != null ? entityType : entities.get(0).getClass(); + entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entities, entityType); Intent intent = (nativeWriter instanceof Batch) ? Intent.BATCH_UPDATE : Intent.UPDATE; - Entity[] nativeEntities = toNativeEntities(entities, entityManager, intent); + Entity[] nativeEntities = toNativeEntities(entities, entityManager, intent, entityType); nativeWriter.update(nativeEntities); List updatedEntities = toEntities(entityClass, nativeEntities); - entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntities); + entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntities, entityType); return updatedEntities; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -209,17 +213,17 @@ public List update(List entities) { /** * Updates the given entity with optimistic locking, if the entity is set up to support optimistic * locking. Otherwise, a normal update is performed. - * + * * @param entity * the entity to update * @return the updated entity which may be different than the given entity. */ - public E updateWithOptimisticLock(E entity) { - PropertyMetadata versionMetadata = EntityIntrospector.getVersionMetadata(entity); + public E updateWithOptimisticLock(E entity, Type entityType) { + PropertyMetadata versionMetadata = EntityIntrospector.getVersionMetadata(entity, entityType); if (versionMetadata == null) { - return update(entity); + return update(entity, entityType); } else { - return updateWithOptimisticLockingInternal(entity, versionMetadata); + return updateWithOptimisticLockingInternal(entity, versionMetadata, entityType); } } @@ -227,27 +231,28 @@ public E updateWithOptimisticLock(E entity) { /** * Updates the given list of entities using optimistic locking feature, if the entities are set up * to support optimistic locking. Otherwise, a normal update is performed. - * + * * @param entities * the entities to update * @return the updated entities */ - public List updateWithOptimisticLock(List entities) { + public List updateWithOptimisticLock(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return new ArrayList<>(); } - Class entityClass = entities.get(0).getClass(); + Type entityClass = entityType != null ? entityType : entities.get(0).getClass(); + PropertyMetadata versionMetadata = EntityIntrospector.getVersionMetadata(entityClass); if (versionMetadata == null) { - return update(entities); + return update(entities, entityType); } else { - return updateWithOptimisticLockInternal(entities, versionMetadata); + return updateWithOptimisticLockInternal(entities, versionMetadata, entityType); } } /** * Worker method for updating the given entity with optimistic locking. - * + * * @param entity * the entity to update * @param versionMetadata @@ -255,11 +260,14 @@ public List updateWithOptimisticLock(List entities) { * @return the updated entity */ @SuppressWarnings("unchecked") - private E updateWithOptimisticLockingInternal(E entity, PropertyMetadata versionMetadata) { + private E updateWithOptimisticLockingInternal(E entity, + PropertyMetadata versionMetadata, + Type entityType) { Transaction transaction = null; try { - entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entity); - Entity nativeEntity = (Entity) Marshaller.marshal(entityManager, entity, Intent.UPDATE); + entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entity, entityType); + Entity nativeEntity = (Entity) Marshaller.marshal(entityManager, entity, + Intent.UPDATE, entityType); transaction = datastore.newTransaction(); Entity storedNativeEntity = transaction.get(nativeEntity.getKey()); if (storedNativeEntity == null) { @@ -275,8 +283,9 @@ private E updateWithOptimisticLockingInternal(E entity, PropertyMetadata ver } transaction.update(nativeEntity); transaction.commit(); - E updatedEntity = (E) Unmarshaller.unmarshal(nativeEntity, entity.getClass()); - entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntity); + Type entityClass = entityType != null ? entityType : entity.getClass(); + E updatedEntity = (E) Unmarshaller.unmarshal(nativeEntity, entityClass); + entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntity, entityType); return updatedEntity; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -287,8 +296,9 @@ private E updateWithOptimisticLockingInternal(E entity, PropertyMetadata ver /** * Internal worker method for updating the entities using optimistic locking. - * + * * @param entities + Entity[] nativeEntities = toNativeEntities(entities, entityManager, intent, type); * the entities to update * @param versionMetadata * the metadata of the version property @@ -296,11 +306,13 @@ private E updateWithOptimisticLockingInternal(E entity, PropertyMetadata ver */ @SuppressWarnings("unchecked") private List updateWithOptimisticLockInternal(List entities, - PropertyMetadata versionMetadata) { + PropertyMetadata versionMetadata, + Type entityType) { Transaction transaction = null; try { - entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entities); - Entity[] nativeEntities = toNativeEntities(entities, entityManager, Intent.UPDATE); + entityManager.executeEntityListeners(CallbackType.PRE_UPDATE, entities, entityType); + Entity[] nativeEntities = toNativeEntities(entities, entityManager, + Intent.UPDATE, entityType); // The above native entities already have the version incremented by // the marshalling process Key[] nativeKeys = new Key[nativeEntities.length]; @@ -326,8 +338,9 @@ private List updateWithOptimisticLockInternal(List entities, } transaction.update(nativeEntities); transaction.commit(); - List updatedEntities = (List) toEntities(entities.get(0).getClass(), nativeEntities); - entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntities); + Type entityClass = entityType != null ? entityType : entities.get(0).getClass(); + List updatedEntities = (List) toEntities(entityClass, nativeEntities); + entityManager.executeEntityListeners(CallbackType.POST_UPDATE, updatedEntities, entityType); return updatedEntities; } catch (DatastoreException exp) { @@ -341,22 +354,24 @@ private List updateWithOptimisticLockInternal(List entities, /** * Updates or inserts the given entity in the Cloud Datastore. If the entity does not have an ID, * it may be generated. - * + * * @param entity * the entity to update or insert * @return the updated/inserted entity. * @throws EntityManagerException * if any error occurs while saving. */ - public E upsert(E entity) { + public E upsert(E entity, Type entityType) { try { - entityManager.executeEntityListeners(CallbackType.PRE_UPSERT, entity); + entityManager.executeEntityListeners(CallbackType.PRE_UPSERT, entity, entityType); FullEntity nativeEntity = (FullEntity) Marshaller.marshal(entityManager, entity, - Intent.UPSERT); + Intent.UPSERT, entityType); Entity upsertedNativeEntity = nativeWriter.put(nativeEntity); @SuppressWarnings("unchecked") - E upsertedEntity = (E) Unmarshaller.unmarshal(upsertedNativeEntity, entity.getClass()); - entityManager.executeEntityListeners(CallbackType.POST_UPSERT, upsertedEntity); + Type entityClass = entityType != null ? entityType : entity.getClass(); + + E upsertedEntity = (E) Unmarshaller.unmarshal(upsertedNativeEntity, entityClass); + entityManager.executeEntityListeners(CallbackType.POST_UPSERT, upsertedEntity, entityType); return upsertedEntity; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -366,7 +381,7 @@ public E upsert(E entity) { /** * Updates or inserts the given list of entities in the Cloud Datastore. If the entities do not * have a valid ID, IDs may be generated. - * + * * @param entities * the entities to update/or insert. * @return the updated or inserted entities @@ -374,17 +389,18 @@ public E upsert(E entity) { * if any error occurs while saving. */ @SuppressWarnings("unchecked") - public List upsert(List entities) { + public List upsert(List entities, Type entityType) { if (entities == null || entities.isEmpty()) { return new ArrayList<>(); } try { - entityManager.executeEntityListeners(CallbackType.PRE_UPSERT, entities); - FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, Intent.UPSERT); - Class entityClass = entities.get(0).getClass(); + entityManager.executeEntityListeners(CallbackType.PRE_UPSERT, entities, entityType); + FullEntity[] nativeEntities = toNativeFullEntities(entities, entityManager, + Intent.UPSERT, entityType); + Type entityClass = entityType != null ? entityType : entities.get(0).getClass(); List upsertedNativeEntities = nativeWriter.put(nativeEntities); List upsertedEntities = (List) toEntities(entityClass, upsertedNativeEntities); - entityManager.executeEntityListeners(CallbackType.POST_UPSERT, upsertedEntities); + entityManager.executeEntityListeners(CallbackType.POST_UPSERT, upsertedEntities, entityType); return upsertedEntities; } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); @@ -393,18 +409,18 @@ public List upsert(List entities) { /** * Deletes the given entity from the Cloud Datastore. - * + * * @param entity * the entity to delete. The entity must have it ID set for the deletion to succeed. * @throws EntityManagerException * if any error occurs while deleting. */ - public void delete(Object entity) { + public void delete(Object entity, Type entityType) { try { - entityManager.executeEntityListeners(CallbackType.PRE_DELETE, entity); - Key nativeKey = Marshaller.marshalKey(entityManager, entity); + entityManager.executeEntityListeners(CallbackType.PRE_DELETE, entity, entityType); + Key nativeKey = Marshaller.marshalKey(entityManager, entity, entityType); nativeWriter.delete(nativeKey); - entityManager.executeEntityListeners(CallbackType.POST_DELETE, entity); + entityManager.executeEntityListeners(CallbackType.POST_DELETE, entity, entityType); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); } @@ -412,21 +428,21 @@ public void delete(Object entity) { /** * Deletes the given entities from the Cloud Datastore. - * + * * @param entities * the entities to delete. The entities must have it ID set for the deletion to succeed. * @throws EntityManagerException * if any error occurs while deleting. */ - public void delete(List entities) { + public void delete(List entities, Type entityType) { try { - entityManager.executeEntityListeners(CallbackType.PRE_DELETE, entities); + entityManager.executeEntityListeners(CallbackType.PRE_DELETE, entities, entityType); Key[] nativeKeys = new Key[entities.size()]; for (int i = 0; i < entities.size(); i++) { - nativeKeys[i] = Marshaller.marshalKey(entityManager, entities.get(i)); + nativeKeys[i] = Marshaller.marshalKey(entityManager, entities.get(i), entityType); } nativeWriter.delete(nativeKeys); - entityManager.executeEntityListeners(CallbackType.POST_DELETE, entities); + entityManager.executeEntityListeners(CallbackType.POST_DELETE, entities, entityType); } catch (DatastoreException exp) { throw DatastoreUtils.wrap(exp); } @@ -435,17 +451,17 @@ public void delete(List entities) { /** * Deletes the entity with the given ID. The entity is assumed to be a root entity (no parent). * The entity kind will be determined from the supplied entity class. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - public void delete(Class entityClass, long id) { + public void delete(Type entityType, long id) { try { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey = entityManager.newNativeKeyFactory().setKind(entityMetadata.getKind()) .newKey(id); nativeWriter.delete(nativeKey); @@ -457,17 +473,17 @@ public void delete(Class entityClass, long id) { /** * Deletes the entity with the given ID. The entity is assumed to be a root entity (no parent). * The entity kind will be determined from the supplied entity class. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param id * the ID of the entity. * @throws EntityManagerException * if any error occurs while inserting. */ - public void delete(Class entityClass, String id) { + public void delete(Type entityType, String id) { try { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey = entityManager.newNativeKeyFactory().setKind(entityMetadata.getKind()) .newKey(id); nativeWriter.delete(nativeKey); @@ -478,9 +494,9 @@ public void delete(Class entityClass, String id) { /** * Deletes the entity with the given ID and parent key. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id @@ -488,9 +504,9 @@ public void delete(Class entityClass, String id) { * @throws EntityManagerException * if any error occurs while inserting. */ - public void delete(Class entityClass, DatastoreKey parentKey, long id) { + public void delete(Type entityType, DatastoreKey parentKey, long id) { try { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey = Key.newBuilder(parentKey.nativeKey(), entityMetadata.getKind(), id).build(); nativeWriter.delete(nativeKey); } catch (DatastoreException exp) { @@ -500,9 +516,9 @@ public void delete(Class entityClass, DatastoreKey parentKey, long id) { /** * Deletes the entity with the given ID and parent key. - * - * @param entityClass - * the entity class. + * + * @param entityType + * the entity type. * @param parentKey * the parent key * @param id @@ -510,9 +526,9 @@ public void delete(Class entityClass, DatastoreKey parentKey, long id) { * @throws EntityManagerException * if any error occurs while inserting. */ - public void delete(Class entityClass, DatastoreKey parentKey, String id) { + public void delete(Type entityType, DatastoreKey parentKey, String id) { try { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); Key nativeKey = Key.newBuilder(parentKey.nativeKey(), entityMetadata.getKind(), id).build(); nativeWriter.delete(nativeKey); } catch (DatastoreException exp) { @@ -522,7 +538,7 @@ public void delete(Class entityClass, DatastoreKey parentKey, String id) /** * Deletes an entity given its key. - * + * * @param key * the entity's key * @throws EntityManagerException @@ -538,7 +554,7 @@ public void deleteByKey(DatastoreKey key) { /** * Deletes the entities having the given keys. - * + * * @param keys * the entities' keys * @throws EntityManagerException diff --git a/src/main/java/com/jmethods/catatumbo/impl/DefaultEntityManager.java b/src/main/java/com/jmethods/catatumbo/impl/DefaultEntityManager.java index 0241f16..9a0a1f0 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/DefaultEntityManager.java +++ b/src/main/java/com/jmethods/catatumbo/impl/DefaultEntityManager.java @@ -17,6 +17,7 @@ package com.jmethods.catatumbo.impl; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; @@ -52,7 +53,7 @@ * Datastore such as inserting entities, updating, deleting, retrieving, etc. In addition to the * standard CRUD operations, the EntityManager allows running GQL queries to retrieve multiple * entities that match the specified criteria. - * + * * @author Sai Pullabhotla */ public class DefaultEntityManager implements EntityManager { @@ -84,7 +85,7 @@ public class DefaultEntityManager implements EntityManager { /** * Creates a new instance of DefaultEntityManager. - * + * * @param datastore * the Datastore object */ @@ -96,7 +97,7 @@ public DefaultEntityManager(Datastore datastore) { /** * Returns the underlying Datastore object. - * + * * @return the underlying Datastore object. */ public Datastore getDatastore() { @@ -104,8 +105,8 @@ public Datastore getDatastore() { } @Override - public long deleteAll(Class entityClass) { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entityClass); + public long deleteAll(Type entityType) { + EntityMetadata entityMetadata = EntityIntrospector.introspect(entityType); return deleteAll(entityMetadata.getKind()); } @@ -172,62 +173,102 @@ public T executeInTransaction(TransactionalTask task) { @Override public E insert(E entity) { - return writer.insert(entity); + return insert(entity, null); + } + + @Override + public E insert(E entity, Type entityType) { + return writer.insert(entity, entityType); } @Override public List insert(List entities) { - return writer.insert(entities); + return insert(entities, null); + } + + @Override + public List insert(List entities, Type entityType) { + return writer.insert(entities, entityType); } @Override public E update(E entity) { - return writer.updateWithOptimisticLock(entity); + return update(entity, null); + } + + @Override + public E update(E entity, Type entityType) { + return writer.updateWithOptimisticLock(entity, entityType); } @Override public List update(List entities) { - return writer.updateWithOptimisticLock(entities); + return update(entities, null); + } + + @Override + public List update(List entities, Type entityType) { + return writer.updateWithOptimisticLock(entities, entityType); } @Override public E upsert(E entity) { - return writer.upsert(entity); + return upsert(entity, null); + } + + @Override + public E upsert(E entity, Type entityType) { + return writer.upsert(entity, entityType); } @Override public List upsert(List entities) { - return writer.upsert(entities); + return upsert(entities, null); + } + + @Override + public List upsert(List entities, Type entityType) { + return writer.upsert(entities, entityType); } @Override public void delete(Object entity) { - writer.delete(entity); + delete(entity, null); + } + + @Override + public void delete(Object entity, Type entityType) { + writer.delete(entity, entityType); } @Override public void delete(List entities) { - writer.delete(entities); + delete(entities, null); } @Override - public void delete(Class entityClass, long id) { - writer.delete(entityClass, id); + public void delete(List entities, Type entityType) { + writer.delete(entities, entityType); } @Override - public void delete(Class entityClass, String id) { - writer.delete(entityClass, id); + public void delete(Type entityType, long id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, long id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, String id) { + writer.delete(entityType, id); } @Override - public void delete(Class entityClass, DatastoreKey parentKey, String id) { - writer.delete(entityClass, parentKey, id); + public void delete(Type entityType, DatastoreKey parentKey, long id) { + writer.delete(entityType, parentKey, id); + } + + @Override + public void delete(Type entityType, DatastoreKey parentKey, String id) { + writer.delete(entityType, parentKey, id); } @Override @@ -241,43 +282,43 @@ public void deleteByKey(List keys) { } @Override - public E load(Class entityClass, long id) { - return reader.load(entityClass, id); + public E load(Type entityType, long id) { + return reader.load(entityType, id); } @Override - public E load(Class entityClass, String id) { - return reader.load(entityClass, id); + public E load(Type entityType, String id) { + return reader.load(entityType, id); } @Override - public E load(Class entityClass, DatastoreKey parentKey, long id) { - return reader.load(entityClass, parentKey, id); + public E load(Type entityType, DatastoreKey parentKey, long id) { + return reader.load(entityType, parentKey, id); } @Override - public E load(Class entityClass, DatastoreKey parentKey, String id) { - return reader.load(entityClass, parentKey, id); + public E load(Type entityType, DatastoreKey parentKey, String id) { + return reader.load(entityType, parentKey, id); } @Override - public E load(Class entityClass, DatastoreKey key) { - return reader.load(entityClass, key); + public E load(Type entityType, DatastoreKey key) { + return reader.load(entityType, key); } @Override - public List loadById(Class entityClass, List identifiers) { - return reader.loadById(entityClass, identifiers); + public List loadById(Type entityType, List identifiers) { + return reader.loadById(entityType, identifiers); } @Override - public List loadByName(Class entityClass, List identifiers) { - return reader.loadByName(entityClass, identifiers); + public List loadByKey(Type entityType, List keys) { + return reader.loadByKey(entityType, keys); } @Override - public List loadByKey(Class entityClass, List keys) { - return reader.loadByKey(entityClass, keys); + public List loadByName(Type entityType, List identifiers) { + return reader.loadByName(entityType, identifiers); } @Override @@ -296,13 +337,13 @@ public KeyQueryRequest createKeyQueryRequest(String query) { } @Override - public QueryResponse executeEntityQueryRequest(Class expectedResultType, - EntityQueryRequest request) { + public QueryResponse executeEntityQueryRequest(Type expectedResultType, + EntityQueryRequest request) { return reader.executeEntityQueryRequest(expectedResultType, request); } @Override - public QueryResponse executeProjectionQueryRequest(Class expectedResultType, + public QueryResponse executeProjectionQueryRequest(Type expectedResultType, ProjectionQueryRequest request) { return reader.executeProjectionQueryRequest(expectedResultType, request); } @@ -324,14 +365,25 @@ public DatastoreStats getDatastoreStats() { @Override public DatastoreKey allocateId(Object entity) { - List keys = allocateId(Arrays.asList(new Object[] { entity })); + return allocateId(entity, null); + } + + @Override + public DatastoreKey allocateId(Object entity, Type entityType) { + List keys = allocateId(Arrays.asList(new Object[] { entity }), entityType); return keys.get(0); } @Override public List allocateId(List entities) { + return allocateId(entities, null); + } + + @Override + public List allocateId(List entities, Type entityType) { for (Object entity : entities) { - IdentifierMetadata identifierMetadata = EntityIntrospector.getIdentifierMetadata(entity); + IdentifierMetadata identifierMetadata = EntityIntrospector + .getIdentifierMetadata(entity, entityType); if (DataType.STRING == identifierMetadata.getDataType()) { throw new IllegalArgumentException( "ID allocation is only valid for entities with numeric identifiers"); @@ -345,7 +397,7 @@ public List allocateId(List entities) { IncompleteKey[] incompleteKeys = new IncompleteKey[entities.size()]; int i = 0; for (Object entity : entities) { - incompleteKeys[i++] = getIncompleteKey(entity); + incompleteKeys[i++] = getIncompleteKey(entity, entityType); } List nativeKeys = datastore.allocateId(incompleteKeys); return DatastoreUtils.toDatastoreKeys(nativeKeys); @@ -353,13 +405,13 @@ public List allocateId(List entities) { /** * Returns an IncompleteKey of the given entity. - * + * * @param entity * the entity * @return the incomplete key */ - private IncompleteKey getIncompleteKey(Object entity) { - EntityMetadata entityMetadata = EntityIntrospector.introspect(entity.getClass()); + private IncompleteKey getIncompleteKey(Object entity, Type entityType) { + EntityMetadata entityMetadata = EntityIntrospector.introspect(entity, entityType); String kind = entityMetadata.getKind(); ParentKeyMetadata parentKeyMetadata = entityMetadata.getParentKeyMetadata(); DatastoreKey parentKey = null; @@ -397,7 +449,7 @@ public void setDefaultListeners(Class... entityListeners) { /** * Puts/adds the given callback type and its metadata to the list of default listeners. - * + * * @param callbackType * the event type * @param metadata @@ -414,20 +466,20 @@ private void putDefaultCallback(CallbackType callbackType, CallbackMetadata meta /** * Executes the entity listeners associated with the given entity. - * + * * @param callbackType * the event type * @param entity * the entity that produced the event */ - public void executeEntityListeners(CallbackType callbackType, Object entity) { + public void executeEntityListeners(CallbackType callbackType, Object entity, Type entityType) { // We may get null entities here. For example loading a nonexistent ID // or IDs. if (entity == null) { return; } EntityListenersMetadata entityListenersMetadata = EntityIntrospector - .getEntityListenersMetadata(entity); + .getEntityListenersMetadata(entity, entityType); List callbacks = entityListenersMetadata.getCallbacks(callbackType); if (!entityListenersMetadata.isExcludeDefaultListeners()) { executeGlobalListeners(callbackType, entity); @@ -454,21 +506,21 @@ public void executeEntityListeners(CallbackType callbackType, Object entity) { /** * Executes the entity listeners associated with the given list of entities. - * + * * @param callbackType * the callback type * @param entities * the entities */ - public void executeEntityListeners(CallbackType callbackType, List entities) { + public void executeEntityListeners(CallbackType callbackType, List entities, Type entityType) { for (Object entity : entities) { - executeEntityListeners(callbackType, entity); + executeEntityListeners(callbackType, entity, entityType); } } /** * Executes the global listeners for the given event type for the given entity. - * + * * @param callbackType * the event type * @param entity @@ -490,7 +542,7 @@ private void executeGlobalListeners(CallbackType callbackType, Object entity) { /** * Invokes the given callback method on the given target object. - * + * * @param callbackMethod * the callback method * @param listener @@ -511,7 +563,7 @@ private static void invokeCallbackMethod(Method callbackMethod, Object listener, /** * Invokes the given callback method on the given target object. - * + * * @param callbackMethod * the callback method * @param entity @@ -531,7 +583,7 @@ private static void invokeCallbackMethod(Method callbackMethod, Object entity) { /** * Creates and returns a new native KeyFactory. If a namespace was specified using {@link Tenant}, * the returned KeyFactory will have the specified namespace. - * + * * @return a {@link KeyFactory} */ KeyFactory newNativeKeyFactory() { @@ -546,7 +598,7 @@ KeyFactory newNativeKeyFactory() { /** * Returns the effective namespace. If a namespace was specified using {@link Tenant}, it will be * returned. Otherwise, the namespace of this EntityManager is returned. - * + * * @return the effective namespace. */ String getEffectiveNamespace() { diff --git a/src/main/java/com/jmethods/catatumbo/impl/EmbeddableIntrospector.java b/src/main/java/com/jmethods/catatumbo/impl/EmbeddableIntrospector.java index e2f9262..560d96d 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EmbeddableIntrospector.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EmbeddableIntrospector.java @@ -25,7 +25,7 @@ /** * Introspects an {@link Embeddable} class and prepares the metadata for the class. - * + * * @author Sai Pullabhotla * */ @@ -43,7 +43,7 @@ public class EmbeddableIntrospector { /** * Creates a new instance if EmbeddableIntrospector. - * + * * @param embeddableClass * the Embeddable class to introspect */ @@ -53,7 +53,7 @@ private EmbeddableIntrospector(Class embeddableClass) { /** * Introspects the given Embeddable class and returns the metadata. - * + * * @param embeddableClass * the Embeddable class * @return the metadata of the given class @@ -94,12 +94,13 @@ private void processFields() { /** * Processes the given simple (or primitive) field and updates the metadata. - * + * * @param field * the field to process */ private void processSimpleField(Field field) { - PropertyMetadata propertyMetadata = IntrospectionUtils.getPropertyMetadata(field); + PropertyMetadata propertyMetadata = IntrospectionUtils + .getPropertyMetadata(field, field.getType()); if (propertyMetadata != null) { metadata.putPropertyMetadata(propertyMetadata); } @@ -107,7 +108,7 @@ private void processSimpleField(Field field) { /** * Processes a nested embedded field. - * + * * @param field * the embedded field. */ @@ -122,7 +123,8 @@ private void processEmbeddedField(Field field) { } boolean indexed = embeddedAnnotation.indexed(); boolean optional = embeddedAnnotation.optional(); - PropertyMetadata propertyMetadata = new PropertyMetadata(field, name, indexed, optional); + PropertyMetadata propertyMetadata = new PropertyMetadata(field, field.getType(), name, + indexed, optional); metadata.putPropertyMetadata(propertyMetadata); } diff --git a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedField.java b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedField.java index 6c13a8a..1aebd0e 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedField.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedField.java @@ -21,7 +21,7 @@ /** * Represents an embedded object of an entity or a nested embedded object. - * + * * @author Sai Pullabhotla * */ @@ -32,6 +32,11 @@ public class EmbeddedField { */ private Field field; + /** + * Type of the underlying field + */ + private Class type; + /** * Parent embedded field, if any */ @@ -46,31 +51,58 @@ public class EmbeddedField { /** * Creates a new instance of EmbeddedField. - * + * * @param field * the underlying field */ public EmbeddedField(Field field) { - this(field, null); + this(field, null, field.getType()); } /** * Creates a new instance of EmbeddedField. - * + * + * @param field + * the underlying field + * @param type + * the type of the field if need to be specified + */ + public EmbeddedField(Field field, Class type) { + this(field, null, type); + } + + /** + * Creates a new instance of EmbeddedField. + * * @param field * the underlying field * @param parent * the parent embedded field. May be null. */ public EmbeddedField(Field field, EmbeddedField parent) { + this(field, parent, field.getType()); + } + + /** + * Creates a new instance of EmbeddedField. + * + * @param field + * the underlying field + * @param parent + * the parent embedded field. May be null. + * @param type + * the type of the field if need to be specified + */ + public EmbeddedField(Field field, EmbeddedField parent, Class type) { this.field = field; this.parent = parent; + this.type = type; computeQualifiedName(); } /** * Returns the underlying field. - * + * * @return the field the underlying field. */ public Field getField() { @@ -79,7 +111,7 @@ public Field getField() { /** * Returns the parent field. - * + * * @return the parent field. */ public EmbeddedField getParent() { @@ -88,7 +120,7 @@ public EmbeddedField getParent() { /** * Returns the qualified name of this embedded field. - * + * * @return the qualified name of this embedded field. */ public String getQualifiedName() { @@ -97,16 +129,16 @@ public String getQualifiedName() { /** * Returns the type of this field. - * + * * @return the type of this field. */ public Class getType() { - return field.getType(); + return type; } /** * Returns the name of this field. - * + * * @return the name of this field. */ public String getName() { @@ -115,7 +147,7 @@ public String getName() { /** * Returns the class in which this field is declared. - * + * * @return the class in which this field is declared. */ public Class getDeclaringClass() { diff --git a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedIntrospector.java b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedIntrospector.java index 948d4d7..76698a3 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedIntrospector.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedIntrospector.java @@ -28,7 +28,7 @@ /** * Introspects and prepares the metadata of an embedded field. An embedded field is a complex object * that is embedded in an entity or another embedded field. - * + * * @author Sai Pullabhotla * */ @@ -56,7 +56,7 @@ public class EmbeddedIntrospector { /** * Creates a new instance of EmbeddedIntrospector. - * + * * @param field * the embedded field to introspect. * @param entityMetadata @@ -70,7 +70,7 @@ private EmbeddedIntrospector(EmbeddedField field, EntityMetadata entityMetadata) /** * Introspects the given embedded field and returns its metadata. - * + * * @param field * the embedded field to introspect * @param entityMetadata @@ -130,12 +130,13 @@ private void processFields() { /** * Processes the given simple (or primitive) field and updates the metadata. - * + * * @param child * the field to process */ private void processSimpleField(Field child) { - PropertyMetadata propertyMetadata = IntrospectionUtils.getPropertyMetadata(child); + PropertyMetadata propertyMetadata = IntrospectionUtils + .getPropertyMetadata(child, child.getType()); if (propertyMetadata != null) { // Process override processPropertyOverride(propertyMetadata); @@ -145,7 +146,7 @@ private void processSimpleField(Field child) { /** * Processes the override, if any, for the given property. - * + * * @param propertyMetadata * the metadata of the property */ @@ -164,7 +165,7 @@ private void processPropertyOverride(PropertyMetadata propertyMetadata) { /** * Processes a nested embedded field. - * + * * @param child * the nested embedded field. */ diff --git a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedMetadata.java index 17dd14b..efff69a 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EmbeddedMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EmbeddedMetadata.java @@ -20,7 +20,7 @@ /** * Objects of this class hold the metadata of an embedded field. - * + * * @author Sai Pullabhotla * */ @@ -63,7 +63,7 @@ public class EmbeddedMetadata extends MetadataBase { /** * Creates a new instance of EmbeddedMetadata. - * + * * @param field * the embedded field to which this metadata belongs */ @@ -72,13 +72,13 @@ public EmbeddedMetadata(EmbeddedField field) { this.field = field; // Default storage strategy is EXPLODED this.storageStrategy = StorageStrategy.EXPLODED; - this.readMethod = IntrospectionUtils.findReadMethodHandle(field.getField()); - this.writeMethod = IntrospectionUtils.findWriteMethodHandle(field.getField()); + this.readMethod = IntrospectionUtils.findReadMethodHandle(field.getField(), field.getType()); + this.writeMethod = IntrospectionUtils.findWriteMethodHandle(field.getField(), field.getType()); } /** * Returns the embedded field to which this metadata belongs. - * + * * @return the embedded field to which this metadata belongs. */ public EmbeddedField getField() { @@ -87,7 +87,7 @@ public EmbeddedField getField() { /** * Returns the read method of the embedded field to which this metadata belongs. - * + * * @return the read method of the embedded field to which this metadata belongs. */ public MethodHandle getReadMethod() { @@ -96,7 +96,7 @@ public MethodHandle getReadMethod() { /** * Returns the storage strategy. - * + * * @return the storage strategy. */ public StorageStrategy getStorageStrategy() { @@ -105,7 +105,7 @@ public StorageStrategy getStorageStrategy() { /** * Sets the storage strategy to the given value. - * + * * @param storageStrategy * the storage strategy */ @@ -115,7 +115,7 @@ public void setStorageStrategy(StorageStrategy storageStrategy) { /** * Returns the write method of the embedded field to which this metadata belongs. - * + * * @return the write method of the embedded field to which this metadata belongs. */ public MethodHandle getWriteMethod() { @@ -125,7 +125,7 @@ public MethodHandle getWriteMethod() { /** * Returns the property name to which this embedded field is mapped. Only used when the storage * strategy is {@link StorageStrategy#IMPLODED}. - * + * * @return the property name to which this embedded field is mapped. */ public String getMappedName() { @@ -134,7 +134,7 @@ public String getMappedName() { /** * Sets the property name to which this embedded field is mapped. - * + * * @param mappedName * the property name to which this embedded field is mapped. */ @@ -145,7 +145,7 @@ public void setMappedName(String mappedName) { /** * Returns whether or not this embedded object should be indexed. Only used when the storage * strategy is {@link StorageStrategy#IMPLODED}. - * + * * @return the indexed true, if this embedded object should be indexed; * false, otherwise. */ @@ -155,7 +155,7 @@ public boolean isIndexed() { /** * Sets whether or not to index this embedded field. - * + * * @param indexed * whether or not to index this embedded field. */ @@ -165,7 +165,7 @@ public void setIndexed(boolean indexed) { /** * Tells whether or not the field represented by this metadata is optional. - * + * * @return true, if the field represented by this metadata is optional; * false, otherwise. */ @@ -175,7 +175,7 @@ public boolean isOptional() { /** * Sets whether or not the field represented by this metadata is optional. - * + * * @param optional * whether or not the field represented by this metadata is optional. */ diff --git a/src/main/java/com/jmethods/catatumbo/impl/EntityIntrospector.java b/src/main/java/com/jmethods/catatumbo/impl/EntityIntrospector.java index 29edec6..cab04de 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EntityIntrospector.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EntityIntrospector.java @@ -17,6 +17,9 @@ package com.jmethods.catatumbo.impl; import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.time.OffsetDateTime; import java.time.ZonedDateTime; import java.util.ArrayList; @@ -70,12 +73,18 @@ public class EntityIntrospector { /** * Cache of introspected classes */ - private static final Cache, EntityMetadata> cache = new Cache<>(64); + private static final Cache cache = new Cache<>(64); /** - * The class to introspect + * The type to introspect */ - private final Class entityClass; + private final Type entityType; + + + /** + * The raw class corresponding to the type + */ + private final Class rawType; /** * Output of the introspection, the metadata about the entity @@ -85,11 +94,12 @@ public class EntityIntrospector { /** * Creates a new instance of EntityIntrospector. * - * @param entityClass - * the entity class to introspect + * @param entityType + * the entity type to introspect */ - private EntityIntrospector(Class entityClass) { - this.entityClass = entityClass; + private EntityIntrospector(Type entityType) { + this.entityType = entityType; + this.rawType = resolveRawType(entityType); } /** @@ -99,40 +109,40 @@ private EntityIntrospector(Class entityClass) { * the entity object to introspect. * @return the metadata of the entity */ - public static EntityMetadata introspect(Object entity) { - return introspect(entity.getClass()); + public static EntityMetadata introspect(Object entity, Type entityType) { + return introspect(entityType != null ? entityType : entity.getClass()); } /** * Introspects the given entity class and returns the metadata of the entity. * - * @param entityClass - * the entity class to introspect + * @param entityType + * the entity type to introspect * @return the metadata of the entity */ - public static EntityMetadata introspect(Class entityClass) { - EntityMetadata cachedMetadata = cache.get(entityClass); + public static EntityMetadata introspect(Type entityType) { + EntityMetadata cachedMetadata = cache.get(entityType); if (cachedMetadata != null) { return cachedMetadata; } - return loadMetadata(entityClass); + return loadMetadata(entityType); } /** * Loads the metadata for the given class and puts it in the cache and returns it. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @return the metadata for the given class */ - private static EntityMetadata loadMetadata(Class entityClass) { - synchronized (entityClass) { - EntityMetadata metadata = cache.get(entityClass); + private static EntityMetadata loadMetadata(Type entityType) { + synchronized (entityType) { + EntityMetadata metadata = cache.get(entityType); if (metadata == null) { - EntityIntrospector introspector = new EntityIntrospector(entityClass); + EntityIntrospector introspector = new EntityIntrospector(entityType); introspector.process(); metadata = introspector.entityMetadata; - cache.put(entityClass, metadata); + cache.put(entityType, metadata); } return metadata; } @@ -142,66 +152,126 @@ private static EntityMetadata loadMetadata(Class entityClass) { * Processes the entity class using reflection and builds the metadata. */ private void process() { - Entity entity = entityClass.getAnnotation(Entity.class); - ProjectedEntity projectedEntity = entityClass.getAnnotation(ProjectedEntity.class); + Entity entity = rawType.getAnnotation(Entity.class); + ProjectedEntity projectedEntity = rawType.getAnnotation(ProjectedEntity.class); if (entity != null) { initEntityMetadata(entity); } else if (projectedEntity != null) { initEntityMetadata(projectedEntity); } else { String message = String.format("Class %s must either have %s or %s annotation", - entityClass.getName(), Entity.class.getName(), ProjectedEntity.class.getName()); + rawType.getName(), Entity.class.getName(), ProjectedEntity.class.getName()); throw new EntityManagerException(message); } + processGenericParameters(); processPropertyOverrides(); processFields(); // If we did not find valid Identifier... if (entityMetadata.getIdentifierMetadata() == null) { throw new EntityManagerException( - String.format("Class %s requires a field with annotation of %s", entityClass.getName(), + String.format("Class %s requires a field with annotation of %s", rawType.getName(), Identifier.class.getName())); } - entityMetadata.setEntityListenersMetadata(EntityListenersIntrospector.introspect(entityClass)); + entityMetadata.setEntityListenersMetadata(EntityListenersIntrospector.introspect(rawType)); entityMetadata.ensureUniqueProperties(); entityMetadata.cleanup(); } + /** + * Resolve the raw class from a given type + * + * @param type + * the type to resolve + * @return the raw class of the underlying type + */ + private Class resolveRawType(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return (Class) ((ParameterizedType) type).getRawType(); + } else { + String message = String.format("Given type %s not supported." + + "Only Class and ParameterizedType are valid.", type.getTypeName()); + throw new EntityManagerException(message); + } + } + + /** + * Resolve the field type if generic + * + * @param field + * the field to resolve + * @return the actual class of the field + */ + private Class reifyFieldType(Field field) { + // Resolve the field type if it's a generic parameterizedType + Class fieldType = field.getType(); + if (field.getGenericType() instanceof TypeVariable) { + Class reifiedType = entityMetadata.reify((TypeVariable) field.getGenericType()); + if (reifiedType != null) { + fieldType = reifiedType; + } + } + + return fieldType; + } + + /** * Initializes the metadata using the given {@link Entity} annotation. - * + * * @param entity * the {@link Entity} annotation. */ private void initEntityMetadata(Entity entity) { String kind = entity.kind(); if (kind.trim().length() == 0) { - kind = entityClass.getSimpleName(); + kind = rawType.getSimpleName(); } - entityMetadata = new EntityMetadata(entityClass, kind); + entityMetadata = new EntityMetadata(rawType, kind); } /** * Initializes the metadata using the given {@link ProjectedEntity} annotation. - * + * * @param projectedEntity * the {@link ProjectedEntity} annotation. */ private void initEntityMetadata(ProjectedEntity projectedEntity) { String kind = projectedEntity.kind(); if (kind.trim().length() == 0) { - String message = String.format("Class %s requires a non-blank Kind", entityClass.getName()); + String message = String.format("Class %s requires a non-blank Kind", rawType.getName()); throw new EntityManagerException(message); } - entityMetadata = new EntityMetadata(entityClass, kind, true); + entityMetadata = new EntityMetadata(rawType, kind, true); + } + + /** + * Resolve generic parameters if a {@link ParameterizedType} was given. + */ + private void processGenericParameters() { + if (entityType instanceof ParameterizedType) { + TypeVariable>[] typeParameters = rawType.getTypeParameters(); + Type[] reified = ((ParameterizedType) entityType).getActualTypeArguments(); + if (typeParameters.length != reified.length) { + String message = String.format("ParameterizedType %s requires %d generic types", + entityType.getTypeName(), typeParameters.length); + throw new EntityManagerException(message); + } + + for (int i = 0; i < typeParameters.length; i++) { + entityMetadata.putGenericType(typeParameters[i], reified[i]); + } + } } /** * Processes the property overrides for the embedded objects, if any. */ private void processPropertyOverrides() { - PropertyOverrides propertyOverrides = entityClass.getAnnotation(PropertyOverrides.class); + PropertyOverrides propertyOverrides = rawType.getAnnotation(PropertyOverrides.class); if (propertyOverrides == null) { return; } @@ -217,16 +287,17 @@ private void processPropertyOverrides() { private void processFields() { List fields = getAllFields(); for (Field field : fields) { + Class fieldType = reifyFieldType(field); if (field.isAnnotationPresent(Identifier.class)) { - processIdentifierField(field); + processIdentifierField(field, fieldType); } else if (field.isAnnotationPresent(Key.class)) { - processKeyField(field); + processKeyField(field, fieldType); } else if (field.isAnnotationPresent(ParentKey.class)) { - processParentKeyField(field); + processParentKeyField(field, fieldType); } else if (field.isAnnotationPresent(Embedded.class)) { - processEmbeddedField(field); + processEmbeddedField(field, fieldType); } else { - processField(field); + processField(field, fieldType); } } } @@ -234,12 +305,12 @@ private void processFields() { /** * Processes the entity class and any super classes that are MappedSupperClasses and returns the * fields. - * + * * @return all fields of the entity hierarchy. */ private List getAllFields() { List allFields = new ArrayList<>(); - Class clazz = entityClass; + Class clazz = rawType; boolean stop; do { List fields = IntrospectionUtils.getPersistableFields(clazz); @@ -255,47 +326,51 @@ private List getAllFields() { * * @param field * the identifier field + * @param type + * the field Type */ - private void processIdentifierField(Field field) { + private void processIdentifierField(Field field, Class type) { Identifier identifier = field.getAnnotation(Identifier.class); boolean autoGenerated = identifier.autoGenerated(); - IdentifierMetadata identifierMetadata = new IdentifierMetadata(field, autoGenerated); + IdentifierMetadata identifierMetadata = new IdentifierMetadata(field, type, autoGenerated); entityMetadata.setIdentifierMetadata(identifierMetadata); } /** * Processes the Key field and builds the entity metadata. - * + * * @param field * the Key field + * @param type + * the field Type */ - private void processKeyField(Field field) { + private void processKeyField(Field field, Class type) { String fieldName = field.getName(); - Class type = field.getType(); if (!type.equals(DatastoreKey.class)) { String message = String.format("Invalid type, %s, for Key field %s in class %s. ", type, - fieldName, entityClass); + fieldName, rawType); throw new EntityManagerException(message); } - KeyMetadata keyMetadata = new KeyMetadata(field); + KeyMetadata keyMetadata = new KeyMetadata(field, type); entityMetadata.setKeyMetadata(keyMetadata); } /** * Processes the ParentKey field and builds the entity metadata. - * + * * @param field * the ParentKey field + * @param type + * the field type */ - private void processParentKeyField(Field field) { + private void processParentKeyField(Field field, Class type) { String fieldName = field.getName(); - Class type = field.getType(); if (!type.equals(DatastoreKey.class)) { String message = String.format("Invalid type, %s, for ParentKey field %s in class %s. ", type, - fieldName, entityClass); + fieldName, rawType); throw new EntityManagerException(message); } - ParentKeyMetadata parentKeyMetadata = new ParentKeyMetadata(field); + ParentKeyMetadata parentKeyMetadata = new ParentKeyMetadata(field, type); entityMetadata.setParentKetMetadata(parentKeyMetadata); } @@ -304,13 +379,15 @@ private void processParentKeyField(Field field) { * * @param field * the field to process + * @param type + * the field type */ - private void processField(Field field) { - PropertyMetadata propertyMetadata = IntrospectionUtils.getPropertyMetadata(field); + private void processField(Field field, Class type) { + PropertyMetadata propertyMetadata = IntrospectionUtils.getPropertyMetadata(field, type); if (propertyMetadata != null) { // If the field is from a super class, there might be some // overrides, so process those. - if (!field.getDeclaringClass().equals(entityClass)) { + if (!field.getDeclaringClass().equals(rawType)) { applyPropertyOverride(propertyMetadata); } entityMetadata.putPropertyMetadata(propertyMetadata); @@ -326,7 +403,7 @@ private void processField(Field field) { /** * Processes the Version annotation of the field with the given metadata. - * + * * @param propertyMetadata * the metadata of the field that has the Version annotation. */ @@ -335,14 +412,14 @@ private void processVersionField(PropertyMetadata propertyMetadata) { if (!long.class.equals(dataClass)) { String messageFormat = "Field %s in class %s must be of type %s"; throw new EntityManagerException(String.format(messageFormat, - propertyMetadata.getField().getName(), entityClass, long.class)); + propertyMetadata.getField().getName(), rawType, long.class)); } entityMetadata.setVersionMetadata(propertyMetadata); } /** * Processes the field that is marked with {@link CreatedTimestamp} annotation. - * + * * @param propertyMetadata * the metadata of the field that was annotated with {@link CreatedTimestamp}. */ @@ -353,7 +430,7 @@ private void processCreatedTimestampField(PropertyMetadata propertyMetadata) { /** * Processes the field that is marked with {@link UpdatedTimestamp} annotation. - * + * * @param propertyMetadata * the metadata of the field that was annotated with {@link UpdatedTimestamp}. */ @@ -364,7 +441,7 @@ private void processUpdatedTimestampField(PropertyMetadata propertyMetadata) { /** * Validates the given property metadata to ensure it is valid for an automatic timestamp field. - * + * * @param propertyMetadata * the metadata to validate */ @@ -373,13 +450,13 @@ private void validateAutoTimestampField(PropertyMetadata propertyMetadata) { if (Collections.binarySearch(VALID_TIMESTAMP_TYPES, dataClass.getName()) < 0) { String messageFormat = "Field %s in class %s must be one of the following types - %s"; throw new EntityManagerException(String.format(messageFormat, - propertyMetadata.getField().getName(), entityClass, VALID_TIMESTAMP_TYPES)); + propertyMetadata.getField().getName(), rawType, VALID_TIMESTAMP_TYPES)); } } /** * Applies any override information for the property with the given metadata. - * + * * @param propertyMetadata * the metadata of the property */ @@ -399,14 +476,16 @@ private void applyPropertyOverride(PropertyMetadata propertyMetadata) { /** * Processes and gathers the metadata for the given embedded field. - * + * * @param field * the embedded field + * @param type + * the field type */ - private void processEmbeddedField(Field field) { - // First create EmbeddedField so we can maintain the path/depth of the + private void processEmbeddedField(Field field, Class type) { + // First, create EmbeddedField so we can maintain the path/depth of the // embedded field - EmbeddedField embeddedField = new EmbeddedField(field); + EmbeddedField embeddedField = new EmbeddedField(field, type); // Introspect the embedded field. EmbeddedMetadata embeddedMetadata = EmbeddedIntrospector.introspect(embeddedField, entityMetadata); @@ -415,71 +494,71 @@ private void processEmbeddedField(Field field) { /** * Convenient method for getting the metadata of the field used for optimistic locking. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @return the metadata of the field used for optimistic locking. Returns null, if * the class does not have a field with {@link Version} annotation. */ - public static PropertyMetadata getVersionMetadata(Class entityClass) { - return introspect(entityClass).getVersionMetadata(); + public static PropertyMetadata getVersionMetadata(Type entityType) { + return introspect(entityType).getVersionMetadata(); } /** * Convenient method for getting the metadata of the field used for optimistic locking. - * + * * @param entity * the entity * @return the metadata of the field used for optimistic locking. Returns null, if * the class does not have a field with {@link Version} annotation. */ - public static PropertyMetadata getVersionMetadata(Object entity) { - return introspect(entity.getClass()).getVersionMetadata(); + public static PropertyMetadata getVersionMetadata(Object entity, Type entityType) { + return introspect(entity, entityType).getVersionMetadata(); } /** * Returns the Identifier Metadata for the given entity. - * + * * @param entity * the entity * @return the Identifier Metadata */ - public static IdentifierMetadata getIdentifierMetadata(Object entity) { - return getIdentifierMetadata(entity.getClass()); + public static IdentifierMetadata getIdentifierMetadata(Object entity, Type entityType) { + return introspect(entity, entityType).getIdentifierMetadata(); } /** * Returns the Identifier Metadata for the given entity class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @return the Identifier Metadata */ - public static IdentifierMetadata getIdentifierMetadata(Class entityClass) { - return introspect(entityClass).getIdentifierMetadata(); + public static IdentifierMetadata getIdentifierMetadata(Type entityType) { + return introspect(entityType).getIdentifierMetadata(); } /** * Returns the metadata of entity listeners associated with the given entity. - * + * * @param entity * the entity * @return the metadata of EntityListeners associated with the given entity. */ - public static EntityListenersMetadata getEntityListenersMetadata(Object entity) { - return introspect(entity.getClass()).getEntityListenersMetadata(); + public static EntityListenersMetadata getEntityListenersMetadata(Object entity, Type entityType) { + return introspect(entity, entityType).getEntityListenersMetadata(); } /** * Returns the metadata of entity listeners associated with the given entity class. - * - * @param entityClass - * the entity class + * + * @param entityType + * the entity type * @return the metadata of entity listeners associated with the given entity class. */ - public static EntityListenersMetadata getEntityListenersMetadata(Class entityClass) { - return introspect(entityClass).getEntityListenersMetadata(); + public static EntityListenersMetadata getEntityListenersMetadata(Type entityType) { + return introspect(entityType).getEntityListenersMetadata(); } } diff --git a/src/main/java/com/jmethods/catatumbo/impl/EntityMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/EntityMetadata.java index cf634ad..59c58d2 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/EntityMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/EntityMetadata.java @@ -17,7 +17,10 @@ package com.jmethods.catatumbo.impl; import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import com.jmethods.catatumbo.CreatedTimestamp; @@ -34,7 +37,7 @@ /** * Objects of this class hold metadata information about an entity. Metadata includes all the * information that is needed to map Java objects to the Cloud Datastore and vice versa. - * + * * * @author Sai Pullabhotla */ @@ -103,6 +106,11 @@ public class EntityMetadata extends MetadataBase { */ private EntityListenersMetadata entityListenersMetadata; + /** + * Mapping of potential type parameters and their concrete type + */ + private Map, Type> genericTypes; + /** * Creates a new instance of EntityMetadata. * @@ -132,6 +140,7 @@ public EntityMetadata(Class entityClass, String kind, boolean projectedEntity this.projectedEntity = projectedEntity; propertyOverrideMap = new HashMap<>(); masterPropertyMetadataMap = new HashMap<>(); + genericTypes = new LinkedHashMap<>(); } /** @@ -145,7 +154,7 @@ public Class getEntityClass() { /** * Tells whether or not this metadata belongs to a {@link ProjectedEntity}. - * + * * @return true, if this metadata belongs to a {@link ProjectedEntity}; * false, otherwise. */ @@ -202,7 +211,7 @@ public KeyMetadata getKeyMetadata() { /** * Sets the metadata of the Key field. - * + * * @param keyMetadata * the key metadata. */ @@ -220,7 +229,7 @@ public void setKeyMetadata(KeyMetadata keyMetadata) { /** * Returns the metadata of the Parent Key. - * + * * @return the metadata of the Parent Key.May return null. */ public ParentKeyMetadata getParentKeyMetadata() { @@ -229,7 +238,7 @@ public ParentKeyMetadata getParentKeyMetadata() { /** * Sets the metadata about the parent key. - * + * * @param parentKeyMetadata * the parent key metadata. */ @@ -247,7 +256,7 @@ public void setParentKetMetadata(ParentKeyMetadata parentKeyMetadata) { /** * Returns the metadata of the field that is used for optimistic locking. - * + * * @return the versionMetadata the metadata of the field that is used for optimistic locking. */ public PropertyMetadata getVersionMetadata() { @@ -256,7 +265,7 @@ public PropertyMetadata getVersionMetadata() { /** * Sets the metadata of the field that is used for optimistic locking. - * + * * @param versionMetadata * metadata of the field that is used for optimistic locking. */ @@ -270,7 +279,7 @@ public void setVersionMetadata(PropertyMetadata versionMetadata) { /** * Returns the metadata of the field that was marked with {@link CreatedTimestamp} annotation. - * + * * @return the metadata of the field that was marked with {@link CreatedTimestamp} annotation. The * returned value may be null, if the entity does not have a field with * {@link CreatedTimestamp} annotation. @@ -281,7 +290,7 @@ public PropertyMetadata getCreatedTimestampMetadata() { /** * Sets the created timestamp metadata to the given value. - * + * * @param createdTimestampMetadata * the created timestamp metadata */ @@ -295,7 +304,7 @@ public void setCreatedTimestampMetadata(PropertyMetadata createdTimestampMetadat /** * Returns the metadata of the field that was marked with {@link UpdatedTimestamp} annotation. - * + * * @return the metadata of the field that was marked with {@link UpdatedTimestamp} annotation. The * returned value may be null, if the entity does not have a field with * {@link UpdatedTimestamp} annotation. @@ -318,7 +327,7 @@ public void setUpdatedTimestampMetadata(PropertyMetadata updatedTimestampMetadat /** * Puts/adds the given property override. - * + * * @param propertyOverride * the property override */ @@ -329,7 +338,7 @@ public void putPropertyOverride(PropertyOverride propertyOverride) { /** * Returns the property override, if any for the given name. May return null if there * is no override exists for the given name. - * + * * @param name * the name of the property * @return the property override for the given property name. May return null. @@ -340,12 +349,12 @@ public Property getPropertyOverride(String name) { /** * Updates the master property metadata map with the given property metadata. - * + * * @param mappedName * the mapped name (or property name in the datastore) * @param qualifiedName * the qualified name of the field - * + * * @throws EntityManagerException * if a property with the same mapped name already exists. */ @@ -359,9 +368,17 @@ public void updateMasterPropertyMetadataMap(String mappedName, String qualifiedN } + public Class reify(TypeVariable typeParameter) { + return (Class) genericTypes.get(typeParameter); + } + + public void putGenericType(TypeVariable typeParameter, Type concreteType) { + genericTypes.put(typeParameter, concreteType); + } + /** * Returns the metadata of the entity listeners. - * + * * @return the metadata of the entity listeners. */ public EntityListenersMetadata getEntityListenersMetadata() { @@ -370,7 +387,7 @@ public EntityListenersMetadata getEntityListenersMetadata() { /** * Sets the metadata of the entity listeners. - * + * * @param entityListenersMetadata * the metadata of the entity listeners. */ @@ -404,7 +421,7 @@ public void ensureUniqueProperties() { /** * Validates the embedded field represented by the given metadata to ensure there are no duplicate * property names defined across the entity. - * + * * @param embeddedMetadata * the metadata of the embedded field * @param storageStrategy @@ -432,7 +449,7 @@ private void ensureUniqueProperties(EmbeddedMetadata embeddedMetadata, /** * Raises an exception with a detailed message reporting that the entity has more than one field * that has a specific annotation. - * + * * @param entityClass * the entity class * @param annotationClass diff --git a/src/main/java/com/jmethods/catatumbo/impl/FieldMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/FieldMetadata.java index a0099bf..857d869 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/FieldMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/FieldMetadata.java @@ -32,6 +32,11 @@ public abstract class FieldMetadata { */ protected final Field field; + /** + * Underlying field type + */ + protected final Class type; + /** * Read method (or getter method) for this field */ @@ -44,20 +49,23 @@ public abstract class FieldMetadata { /** * Creates a new instance of FieldMetadata. - * + * * @param field * the field + * @param type + * the field Type * */ - public FieldMetadata(Field field) { + public FieldMetadata(Field field, Class type) { this.field = field; - this.readMethod = IntrospectionUtils.findReadMethodHandle(this.field); - this.writeMethod = IntrospectionUtils.findWriteMethodHandle(this.field); + this.type = type; + this.readMethod = IntrospectionUtils.findReadMethodHandle(this.field, this.type); + this.writeMethod = IntrospectionUtils.findWriteMethodHandle(this.field, this.type); } /** * Returns the field. - * + * * @return the field. */ public Field getField() { @@ -93,12 +101,12 @@ public MethodHandle getWriteMethod() { /** * Returns the declared type of the field to which this metadata belongs. - * + * * @return the declared type of the field to which this metadata belongs. */ @SuppressWarnings("rawtypes") public Class getDeclaredType() { - return field.getType(); + return type; } } diff --git a/src/main/java/com/jmethods/catatumbo/impl/IdentifierMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/IdentifierMetadata.java index 06076f9..2c34445 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/IdentifierMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/IdentifierMetadata.java @@ -29,7 +29,7 @@ public class IdentifierMetadata extends FieldMetadata { /** * Valid identifier types. - * + * * @author Sai Pullabhotla * */ @@ -54,7 +54,7 @@ public enum DataType { /** * Creates a new instance of DataType. - * + * * @param dataClass * the type/class for the data type */ @@ -64,7 +64,7 @@ private DataType(Class dataClass) { /** * Returns the DataType for the given class. - * + * * @param dataClass * the class * @return the DataType for the given class. @@ -96,16 +96,18 @@ public static DataType forClass(Class dataClass) { /** * Creates a new instance of IdentifierMetadata. - * + * * @param field * the field + * @param type + * the field type * @param autoGenerated * if the identifier is to be generated automatically */ - public IdentifierMetadata(Field field, boolean autoGenerated) { - super(field); + public IdentifierMetadata(Field field, Class type, boolean autoGenerated) { + super(field, type); this.autoGenerated = autoGenerated; - DataType dataType = DataType.forClass(field.getType()); + DataType dataType = DataType.forClass(type); if (dataType == null) { idClassMetadata = new IdClassMetadata(getDeclaredType()); dataType = DataType.forClass(idClassMetadata.getIdType()); @@ -114,7 +116,7 @@ public IdentifierMetadata(Field field, boolean autoGenerated) { } if (dataType == null) { String message = String.format("Invalid identifier type %s for field %s in class %s", - field.getType().getName(), field.getName(), field.getDeclaringClass().getName()); + type.getName(), field.getName(), field.getDeclaringClass().getName()); throw new EntityManagerException(message); } this.dataType = dataType; @@ -141,7 +143,7 @@ public void setAutoGenerated(boolean autoGenerated) { /** * Returns the type of the ID. - * + * * @return the type of the ID. */ public DataType getDataType() { @@ -150,7 +152,7 @@ public DataType getDataType() { /** * Returns the metadata of the ID class, if any. - * + * * @return the idClassMetadata the metadata of the ID class, if any. Returns null, if * the Entity has a simple ID (long, Long or String). */ diff --git a/src/main/java/com/jmethods/catatumbo/impl/IntrospectionUtils.java b/src/main/java/com/jmethods/catatumbo/impl/IntrospectionUtils.java index 0da232b..c78b03f 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/IntrospectionUtils.java +++ b/src/main/java/com/jmethods/catatumbo/impl/IntrospectionUtils.java @@ -35,7 +35,7 @@ /** * Utility methods for helping with introspection/reflection. - * + * * @author Sai Pullabhotla * */ @@ -57,7 +57,7 @@ private IntrospectionUtils() { /** * Creates and returns a new instance of a persistence class for the given metadata. The returned * object will be an instance of the primary persistence class or its Builder. - * + * * @param metadata * the metadata of the class * @return a new instance of the of the Class to which the given metadata belongs. @@ -74,18 +74,20 @@ public static Object instantiate(MetadataBase metadata) { /** * Returns the metadata for the given field. - * + * * @param field * the field whose metadata has to be prepared + * @param type + * the field Type * @return metadata of the given field. */ - public static PropertyMetadata getPropertyMetadata(Field field) { + public static PropertyMetadata getPropertyMetadata(Field field, Class type) { Property property = field.getAnnotation(Property.class); // For fields that have @Property annotation, we expect both setter and // getter methods. For all other fields, we only treat them as // persistable if we find valid getter and setter methods. try { - PropertyMetadata propertyMetadata = new PropertyMetadata(field); + PropertyMetadata propertyMetadata = new PropertyMetadata(field, type); return propertyMetadata; } catch (NoAccessorMethodException | NoMutatorMethodException exp) { if (property != null) { @@ -98,18 +100,20 @@ public static PropertyMetadata getPropertyMetadata(Field field) { /** * Finds and returns a {@link MethodHandle} that can be used to read the field represented by the * given metadata. - * + * * @param field * the field - * + * @param type + * the field Type + * * @return the {@link MethodHandle} for reading the field's value * @throws EntityManagerException * if no read method exists */ - public static MethodHandle findReadMethodHandle(Field field) { + public static MethodHandle findReadMethodHandle(Field field, Class type) { String readMethodName; MethodHandle mh = null; - if (boolean.class.equals(field.getType())) { + if (boolean.class.equals(type)) { readMethodName = IntrospectionUtils.getReadMethodNameForBoolean(field); mh = findInstanceMethod(field.getDeclaringClass(), readMethodName, field.getType()); } @@ -128,15 +132,17 @@ public static MethodHandle findReadMethodHandle(Field field) { /** * Finds and returns a {@link MethodHandle} that can be used to update a field represented by the * given metadata. - * + * * @param field * the field - * + * @param type + * the field Type + * * @return the {@link MethodHandle} to update the field * @throws NoMutatorMethodException * if no matching method exists */ - public static MethodHandle findWriteMethodHandle(Field field) { + public static MethodHandle findWriteMethodHandle(Field field, Class type) { ConstructorMetadata constructorMetadata = ConstructorIntrospector .introspect(field.getDeclaringClass()); Class containerClass; @@ -145,7 +151,7 @@ public static MethodHandle findWriteMethodHandle(Field field) { containerClass = constructorMetadata.getBuilderClass(); for (String prefix : WRITE_METHOD_PREFIXES) { mh = findInstanceMethod(containerClass, getWriteMethodName(field, prefix), null, - field.getType()); + field.getType()); if (mh != null) { break; } @@ -166,7 +172,7 @@ public static MethodHandle findWriteMethodHandle(Field field) { * Returns all potentially persistable fields that were declared in the specified class. This * method filters out the static fields and any fields that have an annotation of {@link Ignore}, * and returns the rest of the declared fields. - * + * * @param clazz * the class * @return all potentially persistable fields that were declared in the specified class. @@ -248,7 +254,7 @@ public static String getCapitalizedName(String fieldName) { /** * Creates a new object of given class by invoking the class' default public constructor. - * + * * @param clazz * the class whose instance needs to be created * @return a new instance of the given class @@ -268,7 +274,7 @@ public static Object instantiateObject(Class clazz) { /** * Examines the given Collection type (List and Set) and returns the Class and Parameterized type, * if any. - * + * * @param type * the Collection type * @return an array of Class objects with two elements. The first element will contain the raw @@ -296,7 +302,7 @@ public static Class[] resolveCollectionType(Type type) { /** * Examines the given Map type and returns the raw type, type of keys, type of values in the map. - * + * * @param type * the type of map * @return an array containing three elements: @@ -332,7 +338,7 @@ public static Class[] resolveMapType(Type type) { /** * Returns a public constructor of the given class with the given parameter types. Returns * null, if there is no matching constructor. - * + * * @param clazz * the class * @param parameterTypes @@ -354,7 +360,7 @@ public static Constructor getConstructor(Class clazz, Class... para /** * Checks to see if the given field is a static field. - * + * * @param field * the field to test * @return true, if the given field is static; false, otherwise. @@ -366,7 +372,7 @@ public static boolean isStatic(Field field) { /** * Returns the value of the field represented by the given metadata. - * + * * @param fieldMetadata * the metadata of the field * @param target @@ -384,7 +390,7 @@ public static Object getFieldValue(FieldMetadata fieldMetadata, Object target) { /** * Finds and returns a MethodHandle for the default constructor of the given class, {@code clazz}. - * + * * @param clazz * the class * @return a MethodHandle for the default constructor. Returns {@code null} if the class does not @@ -403,7 +409,7 @@ public static MethodHandle findDefaultConstructor(Class clazz) { /** * Finds and returns a MethodHandle for a public static method. - * + * * @param clazz * the class to search * @param methodName @@ -422,7 +428,7 @@ public static MethodHandle findStaticMethod(Class clazz, String methodName, /** * Finds and returns a MethodHandle for a public instance method. - * + * * @param clazz * the class to search * @param methodName @@ -441,7 +447,7 @@ public static MethodHandle findInstanceMethod(Class clazz, String methodName, /** * Finds and returns a method handle for the given criteria. - * + * * @param clazz * the class to search * @param methodName diff --git a/src/main/java/com/jmethods/catatumbo/impl/KeyMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/KeyMetadata.java index 96353f8..9b6bb89 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/KeyMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/KeyMetadata.java @@ -20,19 +20,21 @@ /** * Objects of this class contain the metadata about an entity's full key. - * + * * @author Sai Pullabhotla */ public class KeyMetadata extends FieldMetadata { /** * Creates a new instance of KeyMetadata. - * + * * @param field * the field. + * @param type + * the field Type. */ - public KeyMetadata(Field field) { - super(field); + public KeyMetadata(Field field, Class type) { + super(field, type); } } diff --git a/src/main/java/com/jmethods/catatumbo/impl/Marshaller.java b/src/main/java/com/jmethods/catatumbo/impl/Marshaller.java index 3d17a60..f8b9e67 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/Marshaller.java +++ b/src/main/java/com/jmethods/catatumbo/impl/Marshaller.java @@ -16,6 +16,7 @@ package com.jmethods.catatumbo.impl; +import java.lang.reflect.Type; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; @@ -52,7 +53,7 @@ public class Marshaller { * The intent of marshalling an object. The marshalling may be different depending on the intended * purpose. For example, if marshalling an object for UPDATE operation, the marshaller does not * automatically generate a key. - * + * * @author Sai Pullabhotla * */ @@ -94,7 +95,7 @@ public enum Intent { /** * Creates a new instance of Intent. - * + * * @param keyRequired * whether or not a complete key is required. * @param validOnProjectedEntities @@ -107,7 +108,7 @@ private Intent(boolean keyRequired, boolean validOnProjectedEntities) { /** * Tells whether or not a complete key is required for this Intent. - * + * * @return true, if a complete key is required; false, otherwise. */ public boolean isKeyRequired() { @@ -116,7 +117,7 @@ public boolean isKeyRequired() { /** * Tells whether or not this intent is valid on projected entities. - * + * * @return true, if this intent is valid/supported on projected entities; * false, otherwise. */ @@ -166,17 +167,18 @@ public boolean isValidOnProjectedEntities() { * @param intent * the intent of marshalling */ - private Marshaller(DefaultEntityManager entityManager, Object entity, Intent intent) { + private Marshaller(DefaultEntityManager entityManager, Object entity, Intent intent, + Type entityType) { this.entityManager = entityManager; this.entity = entity; this.intent = intent; - entityMetadata = EntityIntrospector.introspect(entity.getClass()); + entityMetadata = EntityIntrospector.introspect(entity, entityType); validateIntent(); } /** * Validates if the Intent is legal for the entity being marshalled. - * + * * @throws EntityManagerException * if the Intent is not valid for the entity being marshalled */ @@ -190,7 +192,30 @@ private void validateIntent() { /** * Marshals the given entity (POJO) into the format needed for the low level Cloud Datastore API. - * + * + * @param entityManager + * the entity manager + * @param entity + * the entity to marshal + * @param intent + * the intent or purpose of marshalling. Marshalling process varies slightly depending on + * the purpose. For example, if the purpose if INSERT or UPSERT, the marshaller would + * auto generate any keys. Where as if the purpose is UPDATE, then then marshaller will + * NOT generate any keys. + * @param entityType + * the entity type + * @return the marshaled object + */ + @SuppressWarnings("rawtypes") + public static BaseEntity marshal(DefaultEntityManager entityManager, Object entity, Intent intent, + Type entityType) { + Marshaller marshaller = new Marshaller(entityManager, entity, intent, entityType); + return marshaller.marshal(); + } + + /** + * Marshals the given entity (POJO) into the format needed for the low level Cloud Datastore API. + * * @param entityManager * the entity manager * @param entity @@ -204,15 +229,15 @@ private void validateIntent() { */ @SuppressWarnings("rawtypes") public static BaseEntity marshal(DefaultEntityManager entityManager, Object entity, - Intent intent) { - Marshaller marshaller = new Marshaller(entityManager, entity, intent); + Intent intent) { + Marshaller marshaller = new Marshaller(entityManager, entity, intent, null); return marshaller.marshal(); } /** * Marshals the given entity and and returns the equivalent Entity needed for the underlying Cloud * Datastore API. - * + * * @return A native entity that is equivalent to the POJO being marshalled. The returned value * could either be a FullEntity or Entity. */ @@ -240,10 +265,12 @@ private BaseEntity marshal() { * the entity manager. * @param entity * the entity from which key is to be extracted + * @param entityType + * the entity type * @return extracted key. */ - public static Key marshalKey(DefaultEntityManager entityManager, Object entity) { - Marshaller marshaller = new Marshaller(entityManager, entity, Intent.DELETE); + public static Key marshalKey(DefaultEntityManager entityManager, Object entity, Type entityType) { + Marshaller marshaller = new Marshaller(entityManager, entity, Intent.DELETE, entityType); marshaller.marshalKey(); return (Key) marshaller.key; } @@ -309,7 +336,7 @@ private void marshalKey() { /** * Checks to see if the given value is a valid identifier for the given ID type. - * + * * @param idValue * the ID value * @param identifierType @@ -340,7 +367,7 @@ private static boolean isValidId(Object idValue, DataType identifierType) { /** * Creates a complete key using the given parameters. - * + * * @param parent * the parent key, may be null. * @param id @@ -357,7 +384,7 @@ private void createCompleteKey(Key parent, long id) { /** * Creates a complete key using the given parameters. - * + * * @param parent * the parent key, may be null. * @param id @@ -375,7 +402,7 @@ private void createCompleteKey(Key parent, String id) { /** * Creates a CompleteKey using the given parameters. The actual ID is generated using * UUID.randomUUID().toString(). - * + * * @param parent * the parent key, may be null. */ @@ -391,7 +418,7 @@ private void createCompleteKey(Key parent) { /** * Creates an IncompleteKey. - * + * * @param parent * the parent key, may be null. */ @@ -417,7 +444,7 @@ private void marshalFields() { /** * Marshals the field with the given property metadata. - * + * * @param propertyMetadata * the metadata of the field to be marshaled. * @param target @@ -429,7 +456,7 @@ private void marshalField(PropertyMetadata propertyMetadata, Object target) { /** * Marshals the field with the given property metadata. - * + * * @param propertyMetadata * the metadata of the field to be marshaled. * @param target @@ -488,7 +515,7 @@ private void marshalEmbeddedFields() { /** * Marshals an embedded field represented by the given metadata. - * + * * @param embeddedMetadata * the metadata of the embedded field * @param target @@ -510,7 +537,7 @@ private void marshalWithExplodedStrategy(EmbeddedMetadata embeddedMetadata, Obje /** * Marshals the embedded field represented by the given metadata. - * + * * @param embeddedMetadata * the metadata of the embedded field. * @param target @@ -596,7 +623,7 @@ private void marshalCreatedAndUpdatedTimestamp() { /** * Applies the given time, millis, to the property represented by the given metadata. - * + * * @param propertyMetadata * the property metadata of the field * @param millis @@ -638,7 +665,7 @@ private void marshalVersionField() { /** * Initializes the Embedded object represented by the given metadata. - * + * * @param embeddedMetadata * the metadata of the embedded field * @param target diff --git a/src/main/java/com/jmethods/catatumbo/impl/ParentKeyMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/ParentKeyMetadata.java index 130b371..cbd55f0 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/ParentKeyMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/ParentKeyMetadata.java @@ -20,19 +20,21 @@ /** * Objects of this class contain the metadata about parent key of an entity. - * + * * @author Sai Pullabhotla */ public class ParentKeyMetadata extends KeyMetadata { /** * Creates a new instance of ParentKeyMetadata. - * + * * @param field * the field + * @param type + * the field type */ - public ParentKeyMetadata(Field field) { - super(field); + public ParentKeyMetadata(Field field, Class type) { + super(field, type); } } diff --git a/src/main/java/com/jmethods/catatumbo/impl/PropertyMetadata.java b/src/main/java/com/jmethods/catatumbo/impl/PropertyMetadata.java index ab8d980..038d068 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/PropertyMetadata.java +++ b/src/main/java/com/jmethods/catatumbo/impl/PropertyMetadata.java @@ -78,9 +78,11 @@ public class PropertyMetadata extends FieldMetadata { * * @param field * the field + * @param type + * the field type */ - public PropertyMetadata(Field field) { - super(field); + public PropertyMetadata(Field field, Class type) { + super(field, type); String mappedName = field.getName(); boolean indexed = true; boolean optional = false; @@ -102,9 +104,11 @@ public PropertyMetadata(Field field) { /** * Creates a new instance of PropertyMetadata. - * + * * @param field * the field + * @param type + * the field type * @param mappedName * name of the property in the Datastore * @param indexed @@ -112,8 +116,9 @@ public PropertyMetadata(Field field) { * @param optional * whether or not the property is optional */ - public PropertyMetadata(Field field, String mappedName, boolean indexed, boolean optional) { - super(field); + public PropertyMetadata(Field field, Class type, String mappedName, boolean indexed, + boolean optional) { + super(field, type); this.mappedName = mappedName; this.indexed = indexed; setOptional(optional); @@ -161,7 +166,7 @@ public void setIndexed(boolean indexed) { /** * Returns the secondary indexer associated with this property, if any. - * + * * @return the secondaryIndexer the secondary indexer associated with this property. May be * null. */ @@ -171,7 +176,7 @@ public Indexer getSecondaryIndexer() { /** * Returns the secondary index name, if any. - * + * * @return the secondary index name. May be null. */ public String getSecondaryIndexName() { @@ -180,7 +185,7 @@ public String getSecondaryIndexName() { /** * Tells whether or not the field represented by this metadata is optional. - * + * * @return true, if the field represented by this metadata is optional; * false, otherwise. */ @@ -190,12 +195,12 @@ public boolean isOptional() { /** * Sets whether or not the field represented by this metadata is optional. - * + * * @param optional * whether or not the field represented by this metadata is optional. */ public void setOptional(boolean optional) { - if (field.getType().isPrimitive() || field.isAnnotationPresent(Version.class) + if (getDeclaredType().isPrimitive() || field.isAnnotationPresent(Version.class) || field.isAnnotationPresent(CreatedTimestamp.class) || field.isAnnotationPresent(UpdatedTimestamp.class)) { this.optional = false; @@ -230,7 +235,7 @@ private void initializeSecondaryIndexer() { /** * Returns the {@link Mapper} associated with the field to which this metadata belongs. - * + * * @return he {@link Mapper} associated with the field to which this metadata belongs. */ public Mapper getMapper() { @@ -239,12 +244,12 @@ public Mapper getMapper() { /** * Initializes the {@link Mapper} for this field. - * + * * @return the {@link Mapper} for the field represented by this metadata */ private Mapper initializeMapper() { try { - return MapperFactory.getInstance().getMapper(field); + return MapperFactory.getInstance().getMapper(field, getDeclaredType()); } catch (NoSuitableMapperException exp) { String message = String.format( "No suitable mapper found or error occurred creating a mapper for field %s in class %s", diff --git a/src/main/java/com/jmethods/catatumbo/impl/Unmarshaller.java b/src/main/java/com/jmethods/catatumbo/impl/Unmarshaller.java index 5243690..73bf8f6 100644 --- a/src/main/java/com/jmethods/catatumbo/impl/Unmarshaller.java +++ b/src/main/java/com/jmethods/catatumbo/impl/Unmarshaller.java @@ -17,6 +17,7 @@ package com.jmethods.catatumbo.impl; import java.lang.invoke.MethodHandle; +import java.lang.reflect.Type; import java.util.Collection; import com.google.cloud.datastore.BaseEntity; @@ -54,48 +55,48 @@ public class Unmarshaller { /** * Creates a new instance of Unmarshaller. - * + * * @param nativeEntity * the native entity to unmarshal - * @param entityClass + * @param entityType * the expected model type */ - private Unmarshaller(BaseEntity nativeEntity, Class entityClass) { + private Unmarshaller(BaseEntity nativeEntity, Type entityType) { this.nativeEntity = nativeEntity; - entityMetadata = EntityIntrospector.introspect(entityClass); + entityMetadata = EntityIntrospector.introspect(entityType); } /** * Unmarshals the given native Entity into an object of given type, entityClass. - * + * * @param * target object type * @param nativeEntity * the native Entity - * @param entityClass + * @param entityType * the target type * @return Object that is equivalent to the given native entity. If the given * datastoreEntity is null, returns null. */ - public static T unmarshal(Entity nativeEntity, Class entityClass) { - return unmarshalBaseEntity(nativeEntity, entityClass); + public static T unmarshal(Entity nativeEntity, Type entityType) { + return unmarshalBaseEntity(nativeEntity, entityType); } /** * Unmarshals the given native ProjectionEntity into an object of given type, entityClass. - * + * * @param * target object type * @param nativeEntity * the native Entity - * @param entityClass + * @param entityType * the target type * @return Object that is equivalent to the given native entity. If the given * datastoreEntity is null, returns null. */ - public static T unmarshal(ProjectionEntity nativeEntity, Class entityClass) { - return unmarshalBaseEntity(nativeEntity, entityClass); + public static T unmarshal(ProjectionEntity nativeEntity, Type entityType) { + return unmarshalBaseEntity(nativeEntity, entityType); } /** @@ -130,18 +131,18 @@ private T unmarshal() { /** * Unmarshals the given BaseEntity and returns the equivalent model object. - * + * * @param nativeEntity * the native entity to unmarshal - * @param entityClass + * @param entityType * the target type of the model class * @return the model object */ - private static T unmarshalBaseEntity(BaseEntity nativeEntity, Class entityClass) { + private static T unmarshalBaseEntity(BaseEntity nativeEntity, Type entityType) { if (nativeEntity == null) { return null; } - Unmarshaller unmarshaller = new Unmarshaller(nativeEntity, entityClass); + Unmarshaller unmarshaller = new Unmarshaller(nativeEntity, entityType); return unmarshaller.unmarshal(); } @@ -154,7 +155,7 @@ private void instantiateEntity() { /** * Unamrshals the identifier. - * + * * @throws Throwable * propagated */ @@ -174,10 +175,10 @@ private void unmarshalIdentifier() throws Throwable { /** * Unamrshals the entity's key and parent key. - * + * * @throws Throwable * propagated - * + * */ private void unmarshalKeyAndParentKey() throws Throwable { KeyMetadata keyMetadata = entityMetadata.getKeyMetadata(); @@ -199,7 +200,7 @@ private void unmarshalKeyAndParentKey() throws Throwable { /** * Unmarshal all the properties. - * + * * @throws Throwable * propagated */ @@ -213,7 +214,7 @@ private void unmarshalProperties() throws Throwable { /** * Unmarshals the embedded fields of this entity. - * + * * @throws Throwable * propagated */ @@ -229,7 +230,7 @@ private void unmarshalEmbeddedFields() throws Throwable { /** * Unmarshals the embedded field represented by the given embedded metadata. - * + * * @param embeddedMetadata * the embedded metadata * @param target @@ -255,7 +256,7 @@ private void unmarshalWithExplodedStrategy(EmbeddedMetadata embeddedMetadata, Ob /** * Unmarshals the embedded field represented by the given metadata. - * + * * @param embeddedMetadata * the metadata of the field to unmarshal * @param target @@ -298,7 +299,7 @@ private static void unmarshalWithImplodedStrategy(EmbeddedMetadata embeddedMetad /** * Unmarshals the property represented by the given property metadata and updates the target * object with the property value. - * + * * @param propertyMetadata * the property metadata * @param target @@ -314,7 +315,7 @@ private void unmarshalProperty(PropertyMetadata propertyMetadata, Object target) /** * Unmarshals the property with the given metadata and sets the unmarshalled value on the given * target object. - * + * * @param propertyMetadata * the metadata of the property * @param target @@ -341,7 +342,7 @@ private static void unmarshalProperty(PropertyMetadata propertyMetadata, Object /** * Initializes the Embedded object represented by the given metadata. - * + * * @param embeddedMetadata * the metadata of the embedded field * @param target diff --git a/src/test/java/com/jmethods/catatumbo/entities/GenericEntity.java b/src/test/java/com/jmethods/catatumbo/entities/GenericEntity.java new file mode 100644 index 0000000..0931dd6 --- /dev/null +++ b/src/test/java/com/jmethods/catatumbo/entities/GenericEntity.java @@ -0,0 +1,95 @@ +/* + * Copyright 2016 Sai Pullabhotla. + * + * 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 com.jmethods.catatumbo.entities; + +import com.jmethods.catatumbo.Embedded; +import com.jmethods.catatumbo.Entity; +import com.jmethods.catatumbo.Identifier; +import com.jmethods.catatumbo.PropertyMapper; +import com.jmethods.catatumbo.custommappers.CurrencyMapper; + +import java.math.BigDecimal; +import java.util.Objects; + +/** + * @author Sai Pullabhotla + * + */ +@Entity +public class GenericEntity { + + @Identifier + private long id; + + @Embedded + private T embeddedGeneric; + + private Z generic; + + /** + * @return the id + */ + public long getId() { + return id; + } + + /** + * @param id + * the id to set + */ + public void setId(long id) { + this.id = id; + } + + public T getEmbeddedGeneric() { + return embeddedGeneric; + } + + public void setEmbeddedGeneric(T embeddedGeneric) { + this.embeddedGeneric = embeddedGeneric; + } + + public Z getGeneric() { + return generic; + } + + public void setGeneric(Z generic) { + this.generic = generic; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GenericEntity that = (GenericEntity) o; + return id == that.id && + Objects.equals(embeddedGeneric, that.embeddedGeneric) && + Objects.equals(generic, that.generic); + } + + @Override + public int hashCode() { + return Objects.hash(id, embeddedGeneric, generic); + } + + public static GenericEntity createSampleGenericEntity1() { + GenericEntity genericEntity = new GenericEntity<>(); + genericEntity.setEmbeddedGeneric(Address.getSample1()); + genericEntity.setGeneric("Stuff"); + return genericEntity; + } +} diff --git a/src/test/java/com/jmethods/catatumbo/entities/GenericParameterizedType.java b/src/test/java/com/jmethods/catatumbo/entities/GenericParameterizedType.java new file mode 100644 index 0000000..9465d63 --- /dev/null +++ b/src/test/java/com/jmethods/catatumbo/entities/GenericParameterizedType.java @@ -0,0 +1,29 @@ +package com.jmethods.catatumbo.entities; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +public class GenericParameterizedType implements ParameterizedType { + + private Type[] types; + + public GenericParameterizedType(Type[] types) { + this.types = types; + } + + @Override + public Type[] getActualTypeArguments() { + return types; + } + + @Override + public Type getRawType() { + return GenericEntity.class; + } + + @Override + public Type getOwnerType() { + return null; + } +} diff --git a/src/test/java/com/jmethods/catatumbo/impl/EntityIntrospectorTest.java b/src/test/java/com/jmethods/catatumbo/impl/EntityIntrospectorTest.java index 0fa0988..f9c348d 100644 --- a/src/test/java/com/jmethods/catatumbo/impl/EntityIntrospectorTest.java +++ b/src/test/java/com/jmethods/catatumbo/impl/EntityIntrospectorTest.java @@ -23,9 +23,13 @@ import static org.junit.Assert.assertTrue; import java.awt.Button; +import java.lang.reflect.Type; import java.util.Map; import java.util.Objects; +import com.jmethods.catatumbo.entities.Address; +import com.jmethods.catatumbo.entities.GenericEntity; +import com.jmethods.catatumbo.entities.GenericParameterizedType; import org.junit.Test; import com.jmethods.catatumbo.EntityManagerException; @@ -66,6 +70,29 @@ */ public class EntityIntrospectorTest { + @Test + public void testIntrospect_Generic() { + Type[] types = {Address.class, String.class}; + EntityMetadata metadata = EntityIntrospector.introspect(GenericEntity.class, new GenericParameterizedType(types)); + System.out.println(metadata); + System.out.println("************"); + metadata = EntityIntrospector.introspect(GenericEntity.class, new GenericParameterizedType(types)); + System.out.println(metadata); + + assertEquals(metadata.reify(GenericEntity.class.getTypeParameters()[0]), types[0]); + assertEquals(metadata.reify(GenericEntity.class.getTypeParameters()[1]), types[1]); + } + + @Test(expected = EntityManagerException.class) + public void testIntrospect_invalid_Generic_Types() { + EntityIntrospector.introspect(GenericEntity.class, new GenericParameterizedType(new Type[] {String.class})); + } + + @Test(expected = EntityManagerException.class) + public void testIntrospect_Generic_Type_missing() { + EntityIntrospector.introspect(GenericEntity.class); + } + @Test public void testIntrospect_Embedded() { EntityMetadata metadata = EntityIntrospector.introspect(Customer.class); diff --git a/src/test/java/com/jmethods/catatumbo/impl/MarshallerTest.java b/src/test/java/com/jmethods/catatumbo/impl/MarshallerTest.java index 3de4983..6a2691d 100644 --- a/src/test/java/com/jmethods/catatumbo/impl/MarshallerTest.java +++ b/src/test/java/com/jmethods/catatumbo/impl/MarshallerTest.java @@ -21,6 +21,9 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import com.jmethods.catatumbo.entities.Address; +import com.jmethods.catatumbo.entities.GenericEntity; +import com.jmethods.catatumbo.entities.GenericParameterizedType; import org.junit.BeforeClass; import org.junit.Test; @@ -36,6 +39,8 @@ import com.jmethods.catatumbo.entities.WrappedStringIdEntity; import com.jmethods.catatumbo.impl.Marshaller.Intent; +import java.lang.reflect.Type; + /** * @author Sai Pullabhotla * @@ -49,6 +54,15 @@ public static void setUpBeforeClass() throws Exception { em = (DefaultEntityManager) TestUtils.getEntityManager(); } + @Test + public void testMarshal_Generic() { + Type[] types = {Address.class, String.class}; + GenericEntity genericEntity = GenericEntity.createSampleGenericEntity1(); + FullEntity entity = (FullEntity) Marshaller.marshal(em, genericEntity, Intent.INSERT, new GenericParameterizedType(types)); + assertEquals("Stuff", entity.getString("generic")); + assertEquals("Omaha", entity.getString("city")); + } + @Test public void testMarshal_Embedded() { Customer customer = Customer.createSampleCustomer2();