Repository: zest-java Updated Branches: refs/heads/develop bbeb6d514 -> 20771538f
ZEST-173 - Adding test cases, and fixing issue in Core which seems to be a JDK bug. Project: http://git-wip-us.apache.org/repos/asf/zest-java/repo Commit: http://git-wip-us.apache.org/repos/asf/zest-java/commit/20771538 Tree: http://git-wip-us.apache.org/repos/asf/zest-java/tree/20771538 Diff: http://git-wip-us.apache.org/repos/asf/zest-java/diff/20771538 Branch: refs/heads/develop Commit: 20771538f10d802b373b7b0250c911282e329879 Parents: bbeb6d5 Author: Niclas Hedhman <[email protected]> Authored: Sat Sep 3 11:24:47 2016 +0800 Committer: Niclas Hedhman <[email protected]> Committed: Sat Sep 3 11:24:47 2016 +0800 ---------------------------------------------------------------------- .../apache/zest/api/unitofwork/UnitOfWork.java | 25 ++- core/runtime/build.gradle | 1 + .../runtime/composite/CompositeMethodModel.java | 11 +- .../runtime/value/AssociationToValueTest.java | 215 +++++++++++++++++++ libraries.gradle | 5 + 5 files changed, 244 insertions(+), 13 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zest-java/blob/20771538/core/api/src/main/java/org/apache/zest/api/unitofwork/UnitOfWork.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/apache/zest/api/unitofwork/UnitOfWork.java b/core/api/src/main/java/org/apache/zest/api/unitofwork/UnitOfWork.java index 15b3b84..309ca58 100644 --- a/core/api/src/main/java/org/apache/zest/api/unitofwork/UnitOfWork.java +++ b/core/api/src/main/java/org/apache/zest/api/unitofwork/UnitOfWork.java @@ -404,14 +404,15 @@ public interface UnitOfWork extends MetaInfoHolder, AutoCloseable * * <p> * All the referenced entities inside the association will be fetched from the underlying entity store, - * which is potentially very expensive operation. + * which is potentially very expensive operation. Each of the fetched entities will be passed to + * {@link #toValue(Class, Identity)}, and its associations will NOT be converted into values, but remain + * {@link EntityReference} values. Hence there is no problem with circular references. * </p> * * <p> - * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and - * as seen in the method signature, also be sub-type of {@link Identity}. + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link Identity}. * </p> - * * @param association The association of entities to be converted into values. * @param <T> The primary type of the association. @@ -428,12 +429,14 @@ public interface UnitOfWork extends MetaInfoHolder, AutoCloseable * <p> * All the referenced entities inside the association will be fetched from the underlying entity store, * which is potentially very expensive operation. However, any duplicate EntityReferences in the association - * will be dropped before the fetch occurs. + * will be dropped before the fetch occurs. Each of the fetched entities will be passed to + * {@link #toValue(Class, Identity)}, and its associations will NOT be converted into values, but remain + * {@link EntityReference} values. Hence there is no problem with circular references. * </p> * * <p> - * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and - * as seen in the method signature, also be sub-type of {@link Identity}. + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link Identity}. * </p> * * @param association The association of entities to be converted into values. @@ -450,11 +453,13 @@ public interface UnitOfWork extends MetaInfoHolder, AutoCloseable * * <p> * A {@link NamedAssociation} is effectively a Map with a String key and an EntityReference as the value. The - * EntityReference is fetched from the entity store and converted into a value of the same type. + * EntityReference is fetched from the entity store and converted into a value of the same type.Each of the fetched + * entities will be passed to {@link #toValue(Class, Identity)}, and its associations will NOT be converted into + * values, but remain {@link EntityReference} values. Hence there is no problem with circular references. * </p> * <p> - * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and - * as seen in the method signature, also be sub-type of {@link Identity}. + * For this to work, the type <T> must be registered at bootstrap as both an Entity and a Value, and + * as seen in the method signature, also be sub-type of {@link Identity}. * </p> * * @param association The association of entities to be converted into values. http://git-wip-us.apache.org/repos/asf/zest-java/blob/20771538/core/runtime/build.gradle ---------------------------------------------------------------------- diff --git a/core/runtime/build.gradle b/core/runtime/build.gradle index 3cd2da4..25db8e2 100644 --- a/core/runtime/build.gradle +++ b/core/runtime/build.gradle @@ -31,4 +31,5 @@ dependencies { testCompile project( ":org.apache.zest.core:org.apache.zest.core.testsupport" ) testCompile project( ":org.apache.zest.libraries:org.apache.zest.library.constraints" ) + testCompile libraries.hamcrest } http://git-wip-us.apache.org/repos/asf/zest-java/blob/20771538/core/runtime/src/main/java/org/apache/zest/runtime/composite/CompositeMethodModel.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/zest/runtime/composite/CompositeMethodModel.java b/core/runtime/src/main/java/org/apache/zest/runtime/composite/CompositeMethodModel.java index 237b023..be8a07c 100644 --- a/core/runtime/src/main/java/org/apache/zest/runtime/composite/CompositeMethodModel.java +++ b/core/runtime/src/main/java/org/apache/zest/runtime/composite/CompositeMethodModel.java @@ -100,9 +100,14 @@ public final class CompositeMethodModel @SuppressWarnings( "unchecked" ) public Stream<DependencyModel> dependencies() { - return Stream.of( concerns, sideEffects ).filter( e -> e != null ).flatMap( Dependencies::dependencies ); -// return flattenIterables( filter( notNull(), iterable( concerns != null ? concerns.dependencies() : null, -// sideEffects != null ? sideEffects.dependencies() : null ) ) ); + // For some unknown reason, the following lines can not be put into a single expression. + // This is possibly due to a compiler or JVM bug. The problem manifests itself in + // failure inside the AssociationToValueTest and possibly others. + // java.lang.invoke.LambdaConversionException: Invalid receiver type interface org.apache.zest.functional.VisitableHierarchy; not a subtype of implementation type interface org.apache.zest.runtime.injection.Dependencies + // Since it is a runtime bug, we should not change this in Java 8, but if the problem is gone in Java 9, + // we can collapse these into a single expression. + Stream<? extends Dependencies> deps = Stream.of( this.concerns, sideEffects ); + return deps.filter( e -> e != null ).flatMap( Dependencies::dependencies ); } // Context http://git-wip-us.apache.org/repos/asf/zest-java/blob/20771538/core/runtime/src/test/java/org/apache/zest/runtime/value/AssociationToValueTest.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/test/java/org/apache/zest/runtime/value/AssociationToValueTest.java b/core/runtime/src/test/java/org/apache/zest/runtime/value/AssociationToValueTest.java new file mode 100644 index 0000000..e9dfdf3 --- /dev/null +++ b/core/runtime/src/test/java/org/apache/zest/runtime/value/AssociationToValueTest.java @@ -0,0 +1,215 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.zest.runtime.value; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import org.apache.zest.api.association.ManyAssociation; +import org.apache.zest.api.association.NamedAssociation; +import org.apache.zest.api.entity.Identity; +import org.apache.zest.api.injection.scope.Service; +import org.apache.zest.api.injection.scope.Structure; +import org.apache.zest.api.mixin.Mixins; +import org.apache.zest.api.unitofwork.UnitOfWork; +import org.apache.zest.api.unitofwork.UnitOfWorkFactory; +import org.apache.zest.api.unitofwork.concern.UnitOfWorkConcern; +import org.apache.zest.api.unitofwork.concern.UnitOfWorkPropagation; +import org.apache.zest.api.value.ValueSerialization; +import org.apache.zest.bootstrap.AssemblyException; +import org.apache.zest.bootstrap.ModuleAssembly; +import org.apache.zest.entitystore.memory.MemoryEntityStoreService; +import org.apache.zest.spi.uuid.UuidIdentityGeneratorService; +import org.apache.zest.test.AbstractZestTest; +import org.apache.zest.valueserialization.orgjson.OrgJsonValueSerializationService; +import org.junit.Test; + +import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; +import static org.junit.Assert.assertThat; + +public class AssociationToValueTest extends AbstractZestTest +{ + @Service + PersonRepository repo; + + @Test + public void givenAdamWhenRequestingChildrenListExpectAbelAndKain() + { + Person adam = repo.findPersonByName( "Adam" ); + Person abel = repo.findPersonByName( "Abel" ); + Person kain = repo.findPersonByName( "Kain" ); + List<Person> children = repo.transact( adam, ( p, uow ) -> uow.toValueList( p.children() ) ); + assertThat( children, containsInAnyOrder( kain, abel ) ); + } + + @Test + public void givenAbelWhenRequestingChildrenSetExpectAdamAndEve() + { + Person abel = repo.findPersonByName( "Abel" ); + Person adam = repo.findPersonByName( "Adam" ); + Person eve = repo.findPersonByName( "Eve" ); + Set<Person> children = repo.transact( abel, ( p, uow ) -> uow.toValueSet( p.children() ) ); + assertThat( children, containsInAnyOrder( adam, eve ) ); + } + + @Test + public void givenBobWhenRequestingRolesExpectAllRolesWithCorrectPerson() + { + Person bob = repo.findPersonByName( "Bob" ); + Person alice = repo.findPersonByName( "Alice" ); + Person john = repo.findPersonByName( "John" ); + Person jane = repo.findPersonByName( "Jane" ); + Person kim = repo.findPersonByName( "Kim" ); + Person robin = repo.findPersonByName( "Robin" ); + Map<String, Person> roles = repo.transact( bob, ( p, uow ) -> uow.toValueMap( p.roles() ) ); + assertThat( roles.keySet(), containsInAnyOrder( "spouse", "mechanic", "maid", "plumber", "electrician" ) ); + assertThat( roles.values(), containsInAnyOrder( alice, john, jane, kim, robin ) ); + } + + @Test + public void givenLouisWhenRequestingRolesExpectAllRolesOfMarie() + { + Person louis = repo.findPersonByName( "Louis" ); + Person marie = repo.findPersonByName( "Marie" ); + Map<String, Person> roles = repo.transact( louis, ( p, uow ) -> uow.toValueMap( p.roles() ) ); + assertThat( roles.keySet(), containsInAnyOrder( "spouse", "lover", "death-mate" ) ); + assertThat( roles.values(), containsInAnyOrder( marie, marie, marie ) ); + } + + @Override + public void assemble( ModuleAssembly module ) + throws AssemblyException + { + module.entities( Person.class ); + module.values( Person.class ); + module.services( PersonRepository.class ).withConcerns( UnitOfWorkConcern.class ); + module.services( OrgJsonValueSerializationService.class ).taggedWith( ValueSerialization.Formats.JSON ); + module.services( UuidIdentityGeneratorService.class ); + module.services( MemoryEntityStoreService.class ); + + module.services( Runnable.class ) + .withMixins( LoadData.class ) + .withConcerns( UnitOfWorkConcern.class ) + .instantiateOnStartup(); + } + + @Override + public void setUp() + throws Exception + { + super.setUp(); + serviceFinder.findService( Runnable.class ).get().run(); + } + + public interface Person extends Identity + { + ManyAssociation<Person> children(); + + NamedAssociation<Person> roles(); + } + + @Mixins( PersonRepositoryMixin.class ) + public interface PersonRepository + { + @UnitOfWorkPropagation + <T, R> R transact( T arg, BiFunction<T, UnitOfWork, R> closure ); + + @UnitOfWorkPropagation + Person findPersonByName( String name ); + } + + protected static class PersonRepositoryMixin + implements PersonRepository + { + @Structure + UnitOfWorkFactory unitOfWorkFactory; + + @Override + public <T, R> R transact( T arg, BiFunction<T, UnitOfWork, R> closure ) + { + UnitOfWork uow = unitOfWorkFactory.currentUnitOfWork(); + return closure.apply( arg, uow ); + } + + @Override + public Person findPersonByName( String name ) + { + UnitOfWork uow = unitOfWorkFactory.currentUnitOfWork(); + return uow.toValue( Person.class, uow.get( Person.class, name ) ); + } + } + + public static class LoadData + implements Runnable + { + @Structure + UnitOfWorkFactory uowf; + + @Override + @UnitOfWorkPropagation + public void run() + { + Person bob = createPerson( "Bob" ); + Person alice = createPerson( "Alice" ); + Person john = createPerson( "John" ); + Person jane = createPerson( "Jane" ); + Person kim = createPerson( "Kim" ); + Person robin = createPerson( "Robin" ); + Person william = createPerson( "William" ); + Person adam = createPerson( "Adam" ); + Person eve = createPerson( "Eve" ); + Person abel = createPerson( "Abel" ); + Person kain = createPerson( "Kain" ); + Person louis = createPerson( "Louis" ); + Person marie = createPerson( "Marie" ); + Person romeo = createPerson( "Romeo" ); + Person juliette = createPerson( "Juliette" ); + adam.children().add( abel ); + adam.children().add( kain ); + eve.children().add( abel ); + eve.children().add( kain ); + abel.children().add( adam ); + abel.children().add( eve ); + abel.children().add( adam ); + abel.children().add( eve ); + abel.children().add( eve ); + bob.roles().put( "spouse", alice ); + bob.roles().put( "mechanic", john ); + bob.roles().put( "maid", jane ); + bob.roles().put( "plumber", kim ); + bob.roles().put( "electrician", robin ); + louis.roles().put( "spouse", marie ); + louis.roles().put( "lover", marie ); + louis.roles().put( "death-mate", marie ); + juliette.roles().put( "lover", romeo ); + juliette.roles().put( "author", william ); + romeo.roles().put( "lover", juliette ); + romeo.roles().put( "author", william ); + } + + private Person createPerson( String name ) + { + UnitOfWork uow = uowf.currentUnitOfWork(); + return uow.newEntity( Person.class, name ); + } + } +} http://git-wip-us.apache.org/repos/asf/zest-java/blob/20771538/libraries.gradle ---------------------------------------------------------------------- diff --git a/libraries.gradle b/libraries.gradle index 31930f5..5f8fd5e 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -33,6 +33,7 @@ def geodeVersion = '1.0.0-incubating.M2' def groovyVersion = '2.4.7' def h2Version = '1.4.192' def hazelcastVersion = '3.6.3' +def hamcrestVersion = '1.3' def httpClientVersion = '4.5.2' def jacksonVersion = '2.7.5' def javascriptVersion = '1.7.7.1' @@ -227,6 +228,10 @@ rootProject.ext { // Testing junit: "junit:junit:$junitVersion", + hamcrest: [ + "org.hamcrest:hamcrest-core:$hamcrestVersion", + "org.hamcrest:hamcrest-library:$hamcrestVersion" + ], awaitility: "com.jayway.awaitility:awaitility:$awaitilityVersion", easymock: "org.easymock:easymock:$easyMockVersion", mockito: "org.mockito:mockito-core:$mockitoVersion",
