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();