From 418b7f2ca592ef54f6b96aef6b0ffea411f36468 Mon Sep 17 00:00:00 2001 From: William Cekan Date: Sun, 7 Apr 2019 16:38:18 -0500 Subject: [PATCH] Implement JPA DataStore (#747) * Elide JPA datastore initial commit * Fix CheckStyle errors * Passing the bulk of the integration tests for the jpa data store. Fixing a number of bugs with tests and also a few with the data store * Passing the bulk of the integration tests for the jpa data store. Fixing a number of bugs with tests and also a few with the data store * Fixed GraphQL Bug * Resolve conflicts * Change build order * Enable UserType test * Cleanup warnings * merge master * Bind entity model beans * Include missing tests --- changelog.md | 6 +- .../elide-datastore-hibernate3/pom.xml | 1 - .../elide-datastore-hibernate5/pom.xml | 4 - ...bernateEntityManagerDataStoreSupplier.java | 2 + .../elide-datastore-jpa/.gitignore | 1 + elide-datastore/elide-datastore-jpa/pom.xml | 191 ++++++++++++ .../elide/datastores/jpa/JpaDataStore.java | 71 +++++ .../jpa/porting/EntityManagerWrapper.java | 36 +++ .../datastores/jpa/porting/QueryWrapper.java | 49 +++ .../transaction/AbstractJpaTransaction.java | 291 ++++++++++++++++++ .../jpa/transaction/JpaTransaction.java | 19 ++ .../jpa/transaction/JtaTransaction.java | 83 +++++ .../jpa/transaction/NonJtaTransaction.java | 58 ++++ .../checker/PersistentCollectionChecker.java | 33 ++ .../EclipseLinkPersistentCollections.java | 18 ++ .../HibernatePersistentCollections.java | 23 ++ .../datastores/jpa/JpaDataStoreSupplier.java | 105 +++++++ .../jpa/usertypes/JsonConverter.java | 49 +++ .../test/java/example/AddressFragment.java | 25 ++ .../src/test/java/example/Person.java | 44 +++ elide-datastore/pom.xml | 1 + elide-datastore/testng.xml | 2 + .../yahoo/elide/graphql/GraphQLScalars.java | 2 +- .../com/yahoo/elide/graphql/GraphQLTest.java | 1 - elide-integration-tests/pom.xml | 10 +- .../com/yahoo/elide/tests/ShareableIT.groovy | 1 + .../com/yahoo/elide/tests/UserTypeIT.groovy | 40 +-- .../com/yahoo/elide/audit/InMemoryLogger.java | 5 +- .../elide/inheritance/InheritanceIT.java | 1 + .../com/yahoo/elide/tests/ResourceIT.java | 12 +- .../com/yahoo/elide/utils/ClassScanner.java | 3 - .../src/test/java/example/Author.java | 4 + .../src/test/java/example/Book.java | 4 + .../src/test/java/example/NoReadEntity.java | 6 +- .../src/test/java/example/NoUpdateEntity.java | 4 +- .../src/test/resources/hibernate.cfg.xml | 5 +- pom.xml | 2 + 37 files changed, 1164 insertions(+), 48 deletions(-) create mode 100644 elide-datastore/elide-datastore-jpa/.gitignore create mode 100644 elide-datastore/elide-datastore-jpa/pom.xml create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/JpaDataStore.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/EntityManagerWrapper.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/QueryWrapper.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/AbstractJpaTransaction.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JpaTransaction.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JtaTransaction.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/NonJtaTransaction.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/PersistentCollectionChecker.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/EclipseLinkPersistentCollections.java create mode 100644 elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/HibernatePersistentCollections.java create mode 100644 elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/JpaDataStoreSupplier.java create mode 100644 elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/usertypes/JsonConverter.java create mode 100644 elide-datastore/elide-datastore-jpa/src/test/java/example/AddressFragment.java create mode 100644 elide-datastore/elide-datastore-jpa/src/test/java/example/Person.java diff --git a/changelog.md b/changelog.md index 4aafbe7edb..9d3349f7c0 100644 --- a/changelog.md +++ b/changelog.md @@ -7,15 +7,15 @@ * Throw proper exception on invalid PersistentResource where id=null * Issue#744 Elide returns wrong date parsing format in 400 error for non-default DateFormats +**Features** + * Added [JPA Data Store](https://github.com/yahoo/elide/pull/747) + ## 4.3.3 **Fixes** * Issue#744 Better error handling for mismatched method in Lifecycle and additional test * Upgraded puppycrawl.tools (checkstyle) dependency to address CVE-2019-9658 * Issue#766 Outdated MySQL driver in elide-standalone and examples -**Features** - * Let custom Elide wrapper process JsonPatchExtensionException - ## 4.3.2 **Fixes** * Issue#754 diff --git a/elide-datastore/elide-datastore-hibernate3/pom.xml b/elide-datastore/elide-datastore-hibernate3/pom.xml index 52266270d5..afa2704ad9 100644 --- a/elide-datastore/elide-datastore-hibernate3/pom.xml +++ b/elide-datastore/elide-datastore-hibernate3/pom.xml @@ -40,7 +40,6 @@ com.yahoo.elide.datastores.hibernate3.HibernateDataStoreSupplier - 3.6.10.Final diff --git a/elide-datastore/elide-datastore-hibernate5/pom.xml b/elide-datastore/elide-datastore-hibernate5/pom.xml index b56d3afc70..f37ebee73d 100644 --- a/elide-datastore/elide-datastore-hibernate5/pom.xml +++ b/elide-datastore/elide-datastore-hibernate5/pom.xml @@ -38,10 +38,6 @@ HEAD - - 5.2.15.Final - - diff --git a/elide-datastore/elide-datastore-hibernate5/src/test/java/com/yahoo/elide/datastores/hibernate5/HibernateEntityManagerDataStoreSupplier.java b/elide-datastore/elide-datastore-hibernate5/src/test/java/com/yahoo/elide/datastores/hibernate5/HibernateEntityManagerDataStoreSupplier.java index e854bf7f9f..3e8cf98fdd 100644 --- a/elide-datastore/elide-datastore-hibernate5/src/test/java/com/yahoo/elide/datastores/hibernate5/HibernateEntityManagerDataStoreSupplier.java +++ b/elide-datastore/elide-datastore-hibernate5/src/test/java/com/yahoo/elide/datastores/hibernate5/HibernateEntityManagerDataStoreSupplier.java @@ -6,6 +6,7 @@ package com.yahoo.elide.datastores.hibernate5; import com.yahoo.elide.core.DataStore; +import com.yahoo.elide.models.generics.Manager; import com.yahoo.elide.models.triggers.Invoice; import com.yahoo.elide.utils.ClassScanner; @@ -53,6 +54,7 @@ public DataStore get() { try { bindClasses.addAll(ClassScanner.getAnnotatedClasses(Parent.class.getPackage(), Entity.class)); + bindClasses.addAll(ClassScanner.getAnnotatedClasses(Manager.class.getPackage(), Entity.class)); bindClasses.addAll(ClassScanner.getAnnotatedClasses(Invoice.class.getPackage(), Entity.class)); } catch (MappingException e) { throw new IllegalStateException(e); diff --git a/elide-datastore/elide-datastore-jpa/.gitignore b/elide-datastore/elide-datastore-jpa/.gitignore new file mode 100644 index 0000000000..2f7896d1d1 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/elide-datastore/elide-datastore-jpa/pom.xml b/elide-datastore/elide-datastore-jpa/pom.xml new file mode 100644 index 0000000000..77bb3157aa --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/pom.xml @@ -0,0 +1,191 @@ + + + + 4.0.0 + elide-datastore-jpa + jar + Elide Data Store: JPA + Elide Data Store for JPA support + https://github.com/yahoo/elide + + com.yahoo.elide + elide-datastore-parent-pom + 4.3.4-SNAPSHOT + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + Belov Aleksandr + https://github.com/belovaf + + + + + scm:git:ssh://git@github.com/yahoo/elide.git + https://github.com/yahoo/elide.git + HEAD + + + + com.yahoo.elide.datastores.jpa.JpaDataStoreSupplier + + + + + + com.yahoo.elide + elide-core + + + com.yahoo.elide + elide-datastore-hibernate + 4.3.4-SNAPSHOT + + + + + org.eclipse.persistence + javax.persistence + 2.1.0 + provided + + + + + javax.transaction + javax.transaction-api + 1.2 + provided + true + + + + com.yahoo.elide + elide-integration-tests + 4.3.4-SNAPSHOT + test-jar + test + + + + org.mockito + mockito-core + test + + + + + + + + ch.qos.logback + logback-classic + test + + + ch.qos.logback + logback-core + test + + + org.testng + testng + test + + + com.jayway.restassured + rest-assured + test + + + org.glassfish.jersey.containers + jersey-container-servlet + test + + + org.hibernate + hibernate-validator + test + + + org.eclipse.jetty + jetty-webapp + test + + + org.hibernate + hibernate-core + ${hibernate5.version} + + + slf4j-api + org.slf4j + + + xml-apis + xml-apis + + + jasper-runtime + tomcat + + + xerces + xerces + + + jasper-compiler + tomcat + + + test + + + + + org.hibernate + hibernate-envers + ${hibernate5.version} + test + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/JpaDataStore.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/JpaDataStore.java new file mode 100644 index 0000000000..7edf4c8abb --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/JpaDataStore.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa; + +import com.yahoo.elide.core.DataStore; +import com.yahoo.elide.core.DataStoreTransaction; +import com.yahoo.elide.core.EntityDictionary; +import com.yahoo.elide.datastores.jpa.transaction.JpaTransaction; + +import javax.persistence.EntityManager; +import javax.persistence.metamodel.EntityType; + +/** + * Implementation for JPA EntityManager data store. + */ +public class JpaDataStore implements DataStore { + protected final EntityManagerSupplier entityManagerSupplier; + protected final JpaTransactionSupplier transactionSupplier; + + public JpaDataStore(EntityManagerSupplier entityManagerSupplier, + JpaTransactionSupplier transactionSupplier) { + this.entityManagerSupplier = entityManagerSupplier; + this.transactionSupplier = transactionSupplier; + } + + @Override + public void populateEntityDictionary(EntityDictionary dictionary) { + for (EntityType type : entityManagerSupplier.get().getMetamodel().getEntities()) { + try { + Class mappedClass = type.getJavaType(); + // Ignore this result. We are just checking to see if it throws an exception meaning that + // provided class was _not_ an entity. + dictionary.lookupEntityClass(mappedClass); + + // Bind if successful + dictionary.bindEntity(mappedClass); + } catch (IllegalArgumentException e) { + // Ignore this entity. + // Turns out that JPA may include non-entity types in this list when using things like envers. + // Since they are not entities, we do not want to bind them into the entity dictionary. + } + } + } + + @Override + public DataStoreTransaction beginTransaction() { + EntityManager entityManager = entityManagerSupplier.get(); + JpaTransaction transaction = transactionSupplier.get(entityManager); + transaction.begin(); + return transaction; + } + + /** + * Functional interface for describing a method to supply EntityManager. + */ + @FunctionalInterface + public interface EntityManagerSupplier { + EntityManager get(); + } + + /** + * Functional interface for describing a method to supply JpaTransaction. + */ + @FunctionalInterface + public interface JpaTransactionSupplier { + JpaTransaction get(EntityManager entityManager); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/EntityManagerWrapper.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/EntityManagerWrapper.java new file mode 100644 index 0000000000..f2b79ec359 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/EntityManagerWrapper.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.porting; + +import com.yahoo.elide.core.hibernate.Query; +import com.yahoo.elide.core.hibernate.Session; + +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.EntityManager; + +/** + * Wraps a JPA EntityManager allowing most data store logic + * to not directly depend on a specific version of JPA. + */ +@Slf4j +public class EntityManagerWrapper implements Session { + private EntityManager entityManager; + + public EntityManagerWrapper(EntityManager entityManager) { + this.entityManager = entityManager; + } + + private static void logQuery(String queryText) { + log.debug("HQL Query: {}", queryText); + } + + @Override + public Query createQuery(String queryText) { + logQuery(queryText); + return new QueryWrapper(entityManager.createQuery(queryText)); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/QueryWrapper.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/QueryWrapper.java new file mode 100644 index 0000000000..e9c04ff7c4 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/porting/QueryWrapper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.porting; + +import com.yahoo.elide.core.hibernate.Query; + +import lombok.Getter; + +import java.util.Collection; + +/** + * Wraps a JPA Query allowing most data store logic + * to not directly depend on a specific version of JPA. + */ +public class QueryWrapper implements Query { + @Getter + private javax.persistence.Query query; + + public QueryWrapper(javax.persistence.Query query) { + this.query = query; + } + + @Override + public Query setFirstResult(int num) { + this.query = query.setFirstResult(num); + return this; + } + + @Override + public Query setMaxResults(int num) { + this.query = query.setMaxResults(num); + return this; + } + + @Override + public Query setParameter(String name, Object value) { + this.query = query.setParameter(name, value); + return this; + } + + @Override + public Query setParameterList(String name, Collection values) { + this.query = query.setParameter(name, values); + return this; + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/AbstractJpaTransaction.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/AbstractJpaTransaction.java new file mode 100644 index 0000000000..2a638832b9 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/AbstractJpaTransaction.java @@ -0,0 +1,291 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction; + +import com.yahoo.elide.core.DataStoreTransaction; +import com.yahoo.elide.core.EntityDictionary; +import com.yahoo.elide.core.Path; +import com.yahoo.elide.core.RequestScope; +import com.yahoo.elide.core.exceptions.TransactionException; +import com.yahoo.elide.core.filter.FilterPredicate; +import com.yahoo.elide.core.filter.Operator; +import com.yahoo.elide.core.filter.expression.AndFilterExpression; +import com.yahoo.elide.core.filter.expression.FilterExpression; +import com.yahoo.elide.core.hibernate.hql.AbstractHQLQueryBuilder; +import com.yahoo.elide.core.hibernate.hql.RelationshipImpl; +import com.yahoo.elide.core.hibernate.hql.RootCollectionFetchQueryBuilder; +import com.yahoo.elide.core.hibernate.hql.RootCollectionPageTotalsQueryBuilder; +import com.yahoo.elide.core.hibernate.hql.SubCollectionFetchQueryBuilder; +import com.yahoo.elide.core.hibernate.hql.SubCollectionPageTotalsQueryBuilder; +import com.yahoo.elide.core.pagination.Pagination; +import com.yahoo.elide.core.sort.Sorting; +import com.yahoo.elide.datastores.jpa.porting.EntityManagerWrapper; +import com.yahoo.elide.datastores.jpa.porting.QueryWrapper; +import com.yahoo.elide.datastores.jpa.transaction.checker.PersistentCollectionChecker; +import com.yahoo.elide.security.User; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.function.Predicate; + +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.persistence.NoResultException; + +/** + * Base JPA transaction implementation class. + */ +@Slf4j +public abstract class AbstractJpaTransaction implements JpaTransaction { + private static final Predicate> IS_PERSISTENT_COLLECTION = + new PersistentCollectionChecker(); + + protected final EntityManager em; + private final EntityManagerWrapper emWrapper; + private final LinkedHashSet deferredTasks = new LinkedHashSet<>(); + + protected AbstractJpaTransaction(EntityManager em) { + this.em = em; + this.emWrapper = new EntityManagerWrapper(em); + } + + @Override + public void delete(Object object, RequestScope scope) { + deferredTasks.add(() -> em.remove(object)); + } + + @Override + public void save(Object object, RequestScope scope) { + deferredTasks.add(() -> { + if (!em.contains(object)) { + em.merge(object); + } + }); + } + + @Override + public void flush(RequestScope requestScope) { + if (!isOpen()) { + return; + } + try { + deferredTasks.forEach(Runnable::run); + deferredTasks.clear(); + FlushModeType flushMode = em.getFlushMode(); + if (flushMode == FlushModeType.AUTO && isOpen()) { + em.flush(); + } + } catch (Exception e) { + try { + rollback(); + } catch (RuntimeException e2) { + e.addSuppressed(e2); + } finally { + log.error("Caught entity manager exception during flush", e); + } + throw new TransactionException(e); + } + } + + @Override + public abstract boolean isOpen(); + + @Override + public void commit(RequestScope scope) { + flush(scope); + } + + @Override + public void rollback() { + deferredTasks.clear(); + } + + @Override + public void close() throws IOException { + if (isOpen()) { + rollback(); + } + if (deferredTasks.size() > 0) { + throw new IOException("Transaction not closed"); + } + } + + @Override + public void createObject(Object entity, RequestScope scope) { + + deferredTasks.add(() -> { + if (!em.contains(entity)) { + em.persist(entity); + } + }); + } + + /** + * load a single record with id and filter. + * + * @param entityClass class of query object + * @param id id of the query object + * @param filterExpression FilterExpression contains the predicates + * @param scope Request scope associated with specific request + */ + @Override + public Object loadObject(Class entityClass, + Serializable id, + Optional filterExpression, + RequestScope scope) { + + try { + EntityDictionary dictionary = scope.getDictionary(); + Class idType = dictionary.getIdType(entityClass); + String idField = dictionary.getIdFieldName(entityClass); + + //Construct a predicate that selects an individual element of the relationship's parent. + FilterPredicate idExpression; + Path.PathElement idPath = new Path.PathElement(entityClass, idType, idField); + if (id != null) { + idExpression = new FilterPredicate(idPath, Operator.IN, Collections.singletonList(id)); + } else { + idExpression = new FilterPredicate(idPath, Operator.FALSE, Collections.emptyList()); + } + + FilterExpression joinedExpression = filterExpression + .map(fe -> (FilterExpression) new AndFilterExpression(fe, idExpression)) + .orElse(idExpression); + + QueryWrapper query = + (QueryWrapper) new RootCollectionFetchQueryBuilder(entityClass, dictionary, emWrapper) + .withPossibleFilterExpression(Optional.of(joinedExpression)) + .build(); + + return query.getQuery().getSingleResult(); + } catch (NoResultException e) { + return null; + } + } + + @Override + public Iterable loadObjects( + Class entityClass, + Optional filterExpression, + Optional sorting, + Optional pagination, + RequestScope scope) { + + pagination.ifPresent(p -> { + if (p.isGenerateTotals()) { + p.setPageTotals(getTotalRecords(entityClass, filterExpression, scope.getDictionary())); + } + }); + + QueryWrapper query = + (QueryWrapper) new RootCollectionFetchQueryBuilder(entityClass, scope.getDictionary(), emWrapper) + .withPossibleFilterExpression(filterExpression) + .withPossibleSorting(sorting) + .withPossiblePagination(pagination) + .build(); + + return query.getQuery().getResultList(); + } + + @Override + public Object getRelation( + DataStoreTransaction relationTx, + Object entity, + String relationName, + Optional filterExpression, + Optional sorting, + Optional pagination, + RequestScope scope) { + + EntityDictionary dictionary = scope.getDictionary(); + Object val = com.yahoo.elide.core.PersistentResource.getValue(entity, relationName, scope); + if (val instanceof Collection) { + Collection filteredVal = (Collection) val; + if (IS_PERSISTENT_COLLECTION.test(filteredVal)) { + Class relationClass = dictionary.getParameterizedType(entity, relationName); + + RelationshipImpl relationship = new RelationshipImpl( + dictionary.lookupEntityClass(entity.getClass()), + relationClass, + relationName, + entity, + filteredVal); + + pagination.ifPresent(p -> { + if (p.isGenerateTotals()) { + p.setPageTotals(getTotalRecords(relationship, filterExpression, dictionary)); + } + }); + + QueryWrapper query = (QueryWrapper) + new SubCollectionFetchQueryBuilder(relationship, dictionary, emWrapper) + .withPossibleFilterExpression(filterExpression) + .withPossibleSorting(sorting) + .withPossiblePagination(pagination) + .build(); + + if (query != null) { + return query.getQuery().getResultList(); + } + } + } + return val; + } + + /** + * Returns the total record count for a root entity and an optional filter expression. + * + * @param entityClass The entity type to count + * @param filterExpression optional security and request filters + * @param dictionary the entity dictionary + * @param The type of entity + * @return The total row count. + */ + private Long getTotalRecords(Class entityClass, + Optional filterExpression, + EntityDictionary dictionary) { + + + QueryWrapper query = (QueryWrapper) + new RootCollectionPageTotalsQueryBuilder(entityClass, dictionary, emWrapper) + .withPossibleFilterExpression(filterExpression) + .build(); + + return (Long) query.getQuery().getSingleResult(); + } + + /** + * Returns the total record count for a entity relationship + * + * @param relationship The relationship + * @param filterExpression optional security and request filters + * @param dictionary the entity dictionary + * @param The type of entity + * @return The total row count. + */ + private Long getTotalRecords(AbstractHQLQueryBuilder.Relationship relationship, + Optional filterExpression, + EntityDictionary dictionary) { + + QueryWrapper query = (QueryWrapper) + new SubCollectionPageTotalsQueryBuilder(relationship, dictionary, emWrapper) + .withPossibleFilterExpression(filterExpression) + .build(); + + return (Long) query.getQuery().getSingleResult(); + } + + @Override + public User accessUser(Object opaqueUser) { + return new User(opaqueUser); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JpaTransaction.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JpaTransaction.java new file mode 100644 index 0000000000..aca2089bf2 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JpaTransaction.java @@ -0,0 +1,19 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction; + +import com.yahoo.elide.core.DataStoreTransaction; + +/** + * Extended for JPA DataStoreTransaction. + */ +public interface JpaTransaction extends DataStoreTransaction { + void begin(); + + void rollback(); + + boolean isOpen(); +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JtaTransaction.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JtaTransaction.java new file mode 100644 index 0000000000..68ea8edb09 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/JtaTransaction.java @@ -0,0 +1,83 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction; + +import com.yahoo.elide.core.RequestScope; +import com.yahoo.elide.core.exceptions.TransactionException; + +import lombok.extern.slf4j.Slf4j; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.EntityManager; +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +/** + * JTA transaction implementation. + */ +@Slf4j +public class JtaTransaction extends AbstractJpaTransaction { + private final UserTransaction transaction; + + public JtaTransaction(EntityManager entityManager) { + this(entityManager, lookupUserTransaction()); + } + + public JtaTransaction(EntityManager entityManager, UserTransaction transaction) { + super(entityManager); + this.transaction = transaction; + } + + private static UserTransaction lookupUserTransaction() { + try { + return (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); + } catch (NamingException e) { + log.error("Fail lookup UserTransaction from InitialContext", e); + throw new TransactionException(e); + } + } + + @Override + public void begin() { + try { + transaction.begin(); + } catch (Exception e) { + log.error("Fail UserTransaction#begin()", e); + throw new TransactionException(e); + } + } + + @Override + public void commit(RequestScope scope) { + super.commit(scope); + try { + transaction.commit(); + } catch (Exception e) { + log.error("Fail UserTransaction#commit()", e); + throw new TransactionException(e); + } + } + + @Override + public void rollback() { + super.rollback(); + try { + transaction.rollback(); + } catch (Exception e) { + log.error("Fail UserTransaction#rollback()", e); + } + } + + @Override + public boolean isOpen() { + try { + return (transaction.getStatus() == Status.STATUS_ACTIVE); + } catch (Exception e) { + return false; + } + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/NonJtaTransaction.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/NonJtaTransaction.java new file mode 100644 index 0000000000..bbb47aeb5b --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/NonJtaTransaction.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction; + +import com.yahoo.elide.core.RequestScope; + +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +/** + * Non-JTA transaction implementation. + */ +@Slf4j +public class NonJtaTransaction extends AbstractJpaTransaction { + private final EntityTransaction transaction; + + public NonJtaTransaction(EntityManager entityManager) { + super(entityManager); + this.transaction = entityManager.getTransaction(); + entityManager.clear(); + } + + @Override + public void begin() { + if (!transaction.isActive()) { + transaction.begin(); + } + } + + @Override + public void commit(RequestScope scope) { + if (transaction.isActive()) { + super.commit(scope); + transaction.commit(); + } + } + + @Override + public void rollback() { + if (transaction.isActive()) { + try { + super.rollback(); + } finally { + transaction.rollback(); + } + } + } + + @Override + public boolean isOpen() { + return transaction.isActive(); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/PersistentCollectionChecker.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/PersistentCollectionChecker.java new file mode 100644 index 0000000000..12b433c297 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/PersistentCollectionChecker.java @@ -0,0 +1,33 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction.checker; + +import com.yahoo.elide.datastores.jpa.transaction.checker.classes.EclipseLinkPersistentCollections; +import com.yahoo.elide.datastores.jpa.transaction.checker.classes.HibernatePersistentCollections; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +/** + * Check is collection subclass of JPA provider special persistent collection. + * Now supported EclipseLink and Hibernate. + */ +public class PersistentCollectionChecker implements Predicate> { + private final Set classNames = new HashSet<>(); + + public PersistentCollectionChecker() { + classNames.addAll(Arrays.asList(HibernatePersistentCollections.CLASSES)); + classNames.addAll(Arrays.asList(EclipseLinkPersistentCollections.CLASSES)); + } + + @Override + public boolean test(Collection collection) { + return classNames.contains(collection.getClass().getName()); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/EclipseLinkPersistentCollections.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/EclipseLinkPersistentCollections.java new file mode 100644 index 0000000000..df07f74fde --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/EclipseLinkPersistentCollections.java @@ -0,0 +1,18 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction.checker.classes; + +/** + * All EclipseLink IndirectCollection subclasses. + * Needed to exclude EclipseLink dependency. + */ +public class EclipseLinkPersistentCollections { + public static final String[] CLASSES = { + "org.eclipse.persistence.indirection.IndirectList", + "org.eclipse.persistence.indirection.IndirectMap", + "org.eclipse.persistence.indirection.IndirectSet" + }; +} diff --git a/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/HibernatePersistentCollections.java b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/HibernatePersistentCollections.java new file mode 100644 index 0000000000..75b631db6a --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/main/java/com/yahoo/elide/datastores/jpa/transaction/checker/classes/HibernatePersistentCollections.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018, Oath Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa.transaction.checker.classes; + +/** + * All Hibernate PersistentCollection subclasses. + * Needed to exclude Hibernate dependency. + */ +public class HibernatePersistentCollections { + public static final String[] CLASSES = { + "org.hibernate.collection.internal.PersistentArrayHolder", + "org.hibernate.collection.internal.PersistentBag", + "org.hibernate.collection.internal.PersistentIdentifierBag", + "org.hibernate.collection.internal.PersistentList", + "org.hibernate.collection.internal.PersistentMap", + "org.hibernate.collection.internal.PersistentSet", + "org.hibernate.collection.internal.PersistentSortedMap", + "org.hibernate.collection.internal.PersistentSortedSet" + }; +} diff --git a/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/JpaDataStoreSupplier.java b/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/JpaDataStoreSupplier.java new file mode 100644 index 0000000000..5ea16b996b --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/JpaDataStoreSupplier.java @@ -0,0 +1,105 @@ +/* + * Copyright 2017, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.datastores.jpa; + +import com.yahoo.elide.core.DataStore; +import com.yahoo.elide.datastores.jpa.transaction.NonJtaTransaction; +import com.yahoo.elide.models.generics.Manager; +import com.yahoo.elide.models.triggers.Invoice; +import com.yahoo.elide.utils.ClassScanner; + +import example.Parent; + +import org.hibernate.MappingException; +import org.hibernate.boot.MetadataSources; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cfg.Environment; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.schema.TargetType; + +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +/** + * Supplier of Hibernate 5 Data Store. + */ +public class JpaDataStoreSupplier implements Supplier { + private static final String JDBC = "jdbc:h2:mem:root;IGNORECASE=TRUE"; + private static final String ROOT = "root"; + + @Override + public DataStore get() { + Map options = new HashMap<>(); + ArrayList> bindClasses = new ArrayList<>(); + + try { + bindClasses.addAll(ClassScanner.getAnnotatedClasses(Parent.class.getPackage(), Entity.class)); + bindClasses.addAll(ClassScanner.getAnnotatedClasses(Manager.class.getPackage(), Entity.class)); + bindClasses.addAll(ClassScanner.getAnnotatedClasses(Invoice.class.getPackage(), Entity.class)); + } catch (MappingException e) { + throw new IllegalStateException(e); + } + + options.put("javax.persistence.jdbc.driver", "org.h2.Driver"); + options.put("javax.persistence.jdbc.url", JDBC); + options.put("javax.persistence.jdbc.user", ROOT); + options.put("javax.persistence.jdbc.password", ROOT); + options.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + options.put(AvailableSettings.LOADED_CLASSES, bindClasses); + + EntityManagerFactory emf = Persistence.createEntityManagerFactory("elide-tests", options); + EntityManager em = emf.createEntityManager(); + + // method to force class initialization + MetadataSources metadataSources = new MetadataSources( + new StandardServiceRegistryBuilder() + .configure("hibernate.cfg.xml") + .applySetting(Environment.CURRENT_SESSION_CONTEXT_CLASS, "thread") + .applySetting(Environment.URL, JDBC) + .applySetting(Environment.USER, ROOT) + .applySetting(Environment.PASS, ROOT) + .applySetting(Environment.DIALECT, "org.hibernate.dialect.H2Dialect") + .build()); + + try { + ClassScanner.getAnnotatedClasses(Parent.class.getPackage(), Entity.class) + .forEach(metadataSources::addAnnotatedClass); + ClassScanner.getAnnotatedClasses(Manager.class.getPackage(), Entity.class) + .forEach(metadataSources::addAnnotatedClass); + ClassScanner.getAnnotatedClasses(Invoice.class.getPackage(), Entity.class) + .forEach(metadataSources::addAnnotatedClass); + } catch (MappingException e) { + throw new IllegalStateException(e); + } + + MetadataImplementor metadataImplementor = (MetadataImplementor) metadataSources.buildMetadata(); + + EnumSet type = EnumSet.of(TargetType.DATABASE); + // create example tables from beans + SchemaExport schemaExport = new SchemaExport(); + schemaExport.drop(type, metadataImplementor); + schemaExport.execute(type, SchemaExport.Action.CREATE, metadataImplementor); + + if (!schemaExport.getExceptions().isEmpty()) { + throw new IllegalStateException(schemaExport.getExceptions().toString()); + } + + return new JpaDataStore( + () -> { return em; }, + (entityManager) -> { return new NonJtaTransaction(entityManager); } + ); + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/usertypes/JsonConverter.java b/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/usertypes/JsonConverter.java new file mode 100644 index 0000000000..e48fd81bfc --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/test/java/com/yahoo/elide/datastores/jpa/usertypes/JsonConverter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ + +package com.yahoo.elide.datastores.jpa.usertypes; + +import com.yahoo.elide.core.exceptions.InvalidValueException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * JsonType serializes an object to json string and vice versa. + */ +@Converter +public class JsonConverter implements AttributeConverter { + private final static ObjectMapper MAPPER = new ObjectMapper(); + + private final Class objectClass; + + public JsonConverter(Class objectClass) { + this.objectClass = objectClass; + } + + @Override + public String convertToDatabaseColumn(T value) { + try { + return MAPPER.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new InvalidValueException("Unable to serialize", e); + } + } + + @Override + public T convertToEntityAttribute(String rawJson) { + try { + return MAPPER.readValue(rawJson, objectClass); + } catch (IOException e) { + throw new InvalidValueException("Unable to deserialize", e); + } + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/test/java/example/AddressFragment.java b/elide-datastore/elide-datastore-jpa/src/test/java/example/AddressFragment.java new file mode 100644 index 0000000000..6708b5bb25 --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/test/java/example/AddressFragment.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package example; + +import com.yahoo.elide.datastores.jpa.usertypes.JsonConverter; + +public class AddressFragment { + public String street; + public String state; + public ZipCode zip; + + public class ZipCode { + public String zip; + public String plusFour; + } + + public static class Converter extends JsonConverter { + public Converter() { + super(AddressFragment.class); + } + } +} diff --git a/elide-datastore/elide-datastore-jpa/src/test/java/example/Person.java b/elide-datastore/elide-datastore-jpa/src/test/java/example/Person.java new file mode 100644 index 0000000000..6ecd3de19e --- /dev/null +++ b/elide-datastore/elide-datastore-jpa/src/test/java/example/Person.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package example; + +import com.yahoo.elide.annotation.Include; + +import org.hibernate.envers.Audited; + +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +@Include(rootLevel = true) +@Audited // Ensure envers does not cause any issues +public class Person { + @Setter + private long id; + + @Setter + @Getter + private String name; + + @Setter + private AddressFragment address; + + @Id + public long getId() { + return id; + } + + @Column(name = "address", columnDefinition = "TEXT") + @Convert(converter = AddressFragment.Converter.class) + public AddressFragment getAddress() { + return address; + } +} diff --git a/elide-datastore/pom.xml b/elide-datastore/pom.xml index 9f8a6226ee..4065dbd1f6 100644 --- a/elide-datastore/pom.xml +++ b/elide-datastore/pom.xml @@ -47,6 +47,7 @@ elide-datastore-hibernate elide-datastore-hibernate5 elide-datastore-hibernate3 + elide-datastore-jpa elide-datastore-inmemorydb elide-datastore-multiplex elide-datastore-noop diff --git a/elide-datastore/testng.xml b/elide-datastore/testng.xml index 929d5030a6..fd67a4175a 100644 --- a/elide-datastore/testng.xml +++ b/elide-datastore/testng.xml @@ -8,6 +8,8 @@ + + diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLScalars.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLScalars.java index b2aa1a7bc9..5310f211d3 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLScalars.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLScalars.java @@ -42,7 +42,7 @@ public Object serialize(Object o) { public Date parseValue(Object o) { Serde dateSerde = CoerceUtil.lookup(Date.class); - return dateSerde.deserialize((Object) o); + return dateSerde.deserialize(o); } @Override diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLTest.java index 9b9db318d3..8f2dbf584c 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLTest.java @@ -14,7 +14,6 @@ import example.Book; import example.Pseudonym; import example.Publisher; - import org.testng.annotations.BeforeClass; import java.util.HashMap; diff --git a/elide-integration-tests/pom.xml b/elide-integration-tests/pom.xml index 5d902d403e..7222252eb4 100644 --- a/elide-integration-tests/pom.xml +++ b/elide-integration-tests/pom.xml @@ -90,9 +90,15 @@ org.hibernate - hibernate-annotations - 3.5.6-Final + hibernate-core + ${hibernate3.version} provided + + + * + * + + org.hibernate diff --git a/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/ShareableIT.groovy b/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/ShareableIT.groovy index 726fb6ccbf..238b6a2172 100644 --- a/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/ShareableIT.groovy +++ b/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/ShareableIT.groovy @@ -29,6 +29,7 @@ class ShareableIT extends AbstractIntegrationTestInitializer { Left left = new Left(); tx.createObject(left, null); tx.commit(null); + tx.close(); } @Test diff --git a/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/UserTypeIT.groovy b/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/UserTypeIT.groovy index 4a1d935de7..85379fac58 100644 --- a/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/UserTypeIT.groovy +++ b/elide-integration-tests/src/test/groovy/com/yahoo/elide/tests/UserTypeIT.groovy @@ -15,7 +15,7 @@ import org.testng.annotations.Test */ class UserTypeIT extends AbstractIntegrationTestInitializer { - @Test + @Test(priority = 1) public void testUserTypePost() { String person = """ @@ -36,7 +36,7 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { } } } - """; + """ given() .contentType("application/vnd.api+json") @@ -44,18 +44,18 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { .body(person) .post("/person") .then() - .statusCode(HttpStatus.SC_CREATED); + .statusCode(HttpStatus.SC_CREATED) String resp = given() .contentType("application/vnd.api+json") .accept("application/vnd.api+json") .get("/person/1") .then() - .statusCode(HttpStatus.SC_OK).extract().body().asString(); - assertEqualDocuments(resp, person); + .statusCode(HttpStatus.SC_OK).extract().body().asString() + assertEqualDocuments(resp, person) } - @Test + @Test(priority = 2) public void testUserTypePatch() { String originalPerson = """ @@ -76,7 +76,7 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { } } } - """; + """ String updatedPerson = """ { @@ -96,7 +96,7 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { } } } - """; + """ given() .contentType("application/vnd.api+json") @@ -104,7 +104,7 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { .body(originalPerson) .post("/person") .then() - .statusCode(HttpStatus.SC_CREATED); + .statusCode(HttpStatus.SC_CREATED) given() .contentType("application/vnd.api+json") @@ -112,18 +112,18 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { .body(updatedPerson) .patch("/person/2") .then() - .statusCode(HttpStatus.SC_NO_CONTENT); + .statusCode(HttpStatus.SC_NO_CONTENT) String resp = given() .contentType("application/vnd.api+json") .accept("application/vnd.api+json") .get("/person/2") .then() - .statusCode(HttpStatus.SC_OK).extract().body().asString(); - assertEqualDocuments(resp, updatedPerson); + .statusCode(HttpStatus.SC_OK).extract().body().asString() + assertEqualDocuments(resp, updatedPerson) } - @Test + @Test(priority = 3) public void testUserTypeMissingUserTypeField() { given() @@ -142,14 +142,14 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { """) .post("/person") .then() - .statusCode(HttpStatus.SC_CREATED); + .statusCode(HttpStatus.SC_CREATED) String resp = given() .contentType("application/vnd.api+json") .accept("application/vnd.api+json") .get("/person/3") .then() - .statusCode(HttpStatus.SC_OK).extract().body().asString(); + .statusCode(HttpStatus.SC_OK).extract().body().asString() assertEqualDocuments(resp, """ { "data": { @@ -161,10 +161,10 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { } } } - """); + """) } - @Test + @Test(priority = 4) public void testUserTypeMissingUserTypeProperties() { given() @@ -189,14 +189,14 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { """) .post("/person") .then() - .statusCode(HttpStatus.SC_CREATED); + .statusCode(HttpStatus.SC_CREATED) String resp = given() .contentType("application/vnd.api+json") .accept("application/vnd.api+json") .get("/person/4") .then() - .statusCode(HttpStatus.SC_OK).extract().body().asString(); + .statusCode(HttpStatus.SC_OK).extract().body().asString() assertEqualDocuments(resp, """ { @@ -216,6 +216,6 @@ class UserTypeIT extends AbstractIntegrationTestInitializer { } } } - """); + """) } } diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/audit/InMemoryLogger.java b/elide-integration-tests/src/test/java/com/yahoo/elide/audit/InMemoryLogger.java index c6b5282656..b8fc89d712 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/audit/InMemoryLogger.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/audit/InMemoryLogger.java @@ -8,15 +8,16 @@ import com.yahoo.elide.core.RequestScope; import com.yahoo.elide.security.ChangeSpec; -import org.eclipse.jetty.util.ConcurrentHashSet; +import com.google.common.collect.Sets; import java.io.IOException; +import java.util.Set; /** * Audit logger that stores messages in memory. */ public class InMemoryLogger extends AuditLogger { - public final ConcurrentHashSet logMessages = new ConcurrentHashSet<>(); + public final Set logMessages = Sets.newConcurrentHashSet(); @Override public void commit(RequestScope requestScope) throws IOException { diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/inheritance/InheritanceIT.java b/elide-integration-tests/src/test/java/com/yahoo/elide/inheritance/InheritanceIT.java index edebf2854d..57da83b243 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/inheritance/InheritanceIT.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/inheritance/InheritanceIT.java @@ -20,6 +20,7 @@ import com.yahoo.elide.core.HttpStatus; import com.yahoo.elide.initialization.AbstractIntegrationTestInitializer; + import org.testng.annotations.Test; import lombok.extern.slf4j.Slf4j; diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/tests/ResourceIT.java b/elide-integration-tests/src/test/java/com/yahoo/elide/tests/ResourceIT.java index d296f23535..395aa72f37 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/tests/ResourceIT.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/tests/ResourceIT.java @@ -112,10 +112,6 @@ public static void setup() throws IOException { p1.setChildren(childrenSet1); - tx.createObject(p1, null); - tx.createObject(c1, null); - tx.createObject(c2, null); - // List tests Parent p2 = new Parent(); // id 3 Parent p3 = new Parent(); // id 4 @@ -136,11 +132,15 @@ public static void setup() throws IOException { p3.setSpouses(Sets.newHashSet()); p3.setChildren(Sets.newHashSet()); - tx.createObject(p2, null); - tx.createObject(p3, null); + tx.createObject(c1, null); + tx.createObject(c2, null); tx.createObject(c3, null); tx.createObject(c4, null); + tx.createObject(p1, null); + tx.createObject(p2, null); + tx.createObject(p3, null); + Book bookWithPercentage = new Book(); bookWithPercentage.setTitle("titlewith%percentage"); Book bookWithoutPercentage = new Book(); diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/utils/ClassScanner.java b/elide-integration-tests/src/test/java/com/yahoo/elide/utils/ClassScanner.java index 7d6fb24e7c..8a138d9df5 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/utils/ClassScanner.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/utils/ClassScanner.java @@ -8,8 +8,6 @@ import org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener; import org.glassfish.jersey.server.internal.scanning.PackageNamesScanner; -import lombok.extern.slf4j.Slf4j; - import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; @@ -20,7 +18,6 @@ /** * Scans a package for classes by looking at files in the classpath. */ -@Slf4j public class ClassScanner { /** * Scans all classes accessible from the context class loader which belong to the given package and subpackages. diff --git a/elide-integration-tests/src/test/java/example/Author.java b/elide-integration-tests/src/test/java/example/Author.java index 420116779e..ff6b2dc3ae 100644 --- a/elide-integration-tests/src/test/java/example/Author.java +++ b/elide-integration-tests/src/test/java/example/Author.java @@ -68,4 +68,8 @@ public boolean equals(Object obj) { @ManyToMany(mappedBy = "authors") @Getter @Setter private Collection books = new ArrayList<>(); + @Override + public String toString() { + return "Author: " + id; + } } diff --git a/elide-integration-tests/src/test/java/example/Book.java b/elide-integration-tests/src/test/java/example/Book.java index 63fec2ac2a..ec91e52538 100644 --- a/elide-integration-tests/src/test/java/example/Book.java +++ b/elide-integration-tests/src/test/java/example/Book.java @@ -151,4 +151,8 @@ public Editor getEditor() { return null; } + @Override + public String toString() { + return "Book: " + id; + } } diff --git a/elide-integration-tests/src/test/java/example/NoReadEntity.java b/elide-integration-tests/src/test/java/example/NoReadEntity.java index 110b5b7e25..b8be12a445 100644 --- a/elide-integration-tests/src/test/java/example/NoReadEntity.java +++ b/elide-integration-tests/src/test/java/example/NoReadEntity.java @@ -8,6 +8,7 @@ import com.yahoo.elide.annotation.Include; import com.yahoo.elide.annotation.ReadPermission; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.OneToOne; @@ -22,8 +23,9 @@ @Entity @Table(name = "noread") public class NoReadEntity extends BaseId { - public String field; + @Column + protected String field; @OneToOne(fetch = FetchType.LAZY) - public Child child; + protected Child child; } diff --git a/elide-integration-tests/src/test/java/example/NoUpdateEntity.java b/elide-integration-tests/src/test/java/example/NoUpdateEntity.java index d48fbf50ac..80b4c509a8 100644 --- a/elide-integration-tests/src/test/java/example/NoUpdateEntity.java +++ b/elide-integration-tests/src/test/java/example/NoUpdateEntity.java @@ -23,6 +23,6 @@ @Entity @Table(name = "noupdate") public class NoUpdateEntity extends BaseId { - @OneToMany() - public Set children; + @OneToMany + protected Set children; } diff --git a/elide-integration-tests/src/test/resources/hibernate.cfg.xml b/elide-integration-tests/src/test/resources/hibernate.cfg.xml index 6ef23f52c5..b6f10963d2 100644 --- a/elide-integration-tests/src/test/resources/hibernate.cfg.xml +++ b/elide-integration-tests/src/test/resources/hibernate.cfg.xml @@ -9,7 +9,7 @@ "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> - + @@ -29,5 +29,8 @@ false thread + + 60 + true diff --git a/pom.xml b/pom.xml index ec114931dd..62d8f3e735 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,8 @@ 9.4.12.v20180830 2.9.0 2.9.8 + 3.6.10.Final + 5.2.15.Final 8.0.15