| /* |
| * Copyright (c) 2008, Edward Yakop. All Rights Reserved. |
| * Copyright (c) 2008, Michael Hunger. All Rights Reserved. |
| * Copyright (c) 2008, Niclas Hedhman. All Rights Reserved. |
| * Copyright (c) 2008, Rickard Öberg. All Rights Reserved. |
| * Copyright (c) 2013, Paul Merlin. All Rights Reserved. |
| * |
| * 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 org.apache.zest.test.entity; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.joda.time.DateTime; |
| import org.joda.time.LocalDate; |
| import org.joda.time.LocalDateTime; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.apache.zest.api.association.Association; |
| import org.apache.zest.api.association.ManyAssociation; |
| import org.apache.zest.api.association.NamedAssociation; |
| import org.apache.zest.api.common.Optional; |
| import org.apache.zest.api.common.UseDefaults; |
| import org.apache.zest.api.entity.EntityBuilder; |
| import org.apache.zest.api.entity.EntityComposite; |
| import org.apache.zest.api.injection.scope.Service; |
| import org.apache.zest.api.property.Property; |
| import org.apache.zest.api.unitofwork.ConcurrentEntityModificationException; |
| import org.apache.zest.api.unitofwork.NoSuchEntityException; |
| import org.apache.zest.api.unitofwork.UnitOfWork; |
| import org.apache.zest.api.unitofwork.UnitOfWorkCompletionException; |
| import org.apache.zest.api.value.ValueBuilder; |
| import org.apache.zest.api.value.ValueComposite; |
| import org.apache.zest.bootstrap.AssemblyException; |
| import org.apache.zest.bootstrap.ModuleAssembly; |
| import org.apache.zest.spi.entitystore.EntityStore; |
| import org.apache.zest.spi.uuid.UuidIdentityGeneratorService; |
| import org.apache.zest.test.AbstractQi4jTest; |
| |
| import static org.hamcrest.CoreMatchers.equalTo; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.joda.time.DateTimeZone.UTC; |
| import static org.junit.Assert.assertThat; |
| import static org.junit.Assert.fail; |
| |
| /** |
| * Abstract satisfiedBy with tests for the EntityStore interface. |
| */ |
| public abstract class AbstractEntityStoreTest |
| extends AbstractQi4jTest |
| { |
| |
| @Service |
| private EntityStore store; |
| |
| @Override |
| public void assemble( ModuleAssembly module ) |
| throws AssemblyException |
| { |
| module.services( UuidIdentityGeneratorService.class ); |
| module.entities( TestEntity.class ); |
| module.values( TestValue.class, TestValue2.class, TjabbaValue.class ); |
| module.objects( getClass() ); |
| } |
| |
| @Before |
| public void init() |
| { |
| module.injectTo( this ); |
| } |
| |
| @Override |
| @After |
| public void tearDown() |
| throws Exception |
| { |
| super.tearDown(); |
| } |
| |
| protected TestEntity createEntity( UnitOfWork unitOfWork ) |
| throws UnitOfWorkCompletionException |
| { |
| // Create entity |
| EntityBuilder<TestEntity> builder = unitOfWork.newEntityBuilder( TestEntity.class ); |
| builder.instance().dateValue().set( new Date() ); |
| TestEntity instance = builder.newInstance(); |
| |
| instance.name().set( "Test" ); |
| instance.intValue().set( 42 ); |
| instance.longValue().set( 42L ); |
| instance.doubleValue().set( 42D ); |
| instance.floatValue().set( 42F ); |
| instance.booleanValue().set( Boolean.TRUE ); |
| instance.bigIntegerValue().set( new BigInteger( "42" ) ); |
| instance.bigDecimalValue().set( new BigDecimal( "42" ) ); |
| instance.dateValue().set( new DateTime( "2020-03-04T13:24:35", UTC ).toDate() ); |
| instance.dateTimeValue().set( new DateTime( "2020-03-04T13:24:35", UTC ) ); |
| instance.localDateTimeValue().set( new LocalDateTime( "2020-03-04T13:23:00" ) ); |
| instance.localDateValue().set( new LocalDate( "2020-03-04" ) ); |
| instance.association().set( instance ); |
| |
| ValueBuilder<Tjabba> valueBuilder4 = module.newValueBuilder( Tjabba.class ); |
| final Tjabba prototype4 = valueBuilder4.prototype(); |
| prototype4.bling().set( "BlinkLjus" ); |
| |
| // Set value |
| ValueBuilder<TestValue2> valueBuilder2 = module.newValueBuilder( TestValue2.class ); |
| TestValue2 prototype2 = valueBuilder2.prototype(); |
| prototype2.stringValue().set( "Bar" ); |
| Tjabba newValue = valueBuilder4.newInstance(); |
| prototype2.anotherValue().set( newValue ); |
| prototype2.anotherValue().set( newValue ); |
| |
| ValueBuilder<Tjabba> valueBuilder3 = module.newValueBuilder( Tjabba.class ); |
| final Tjabba prototype3 = valueBuilder3.prototype(); |
| prototype3.bling().set( "Brakfis" ); |
| |
| ValueBuilder<TestValue> valueBuilder1 = module.newValueBuilder( TestValue.class ); |
| TestValue prototype = valueBuilder1.prototype(); |
| prototype.enumProperty().set( TestEnum.VALUE3 ); |
| prototype.listProperty().get().add( "Foo" ); |
| |
| prototype.valueProperty().set( valueBuilder2.newInstance() ); |
| prototype.tjabbaProperty().set( valueBuilder3.newInstance() ); |
| Map<String, String> mapValue = new HashMap<>( 1 ); |
| mapValue.put( "foo", "bar" ); |
| prototype.mapStringStringProperty().set( mapValue ); |
| instance.valueProperty().set( valueBuilder1.newInstance() ); |
| |
| instance.manyAssociation().add( 0, instance ); |
| |
| instance.namedAssociation().put( "foo", instance ); |
| instance.namedAssociation().put( "bar", instance ); |
| |
| return instance; |
| } |
| |
| @Test |
| public void whenNewEntityThenCanFindEntityAndCorrectValues() |
| throws Exception |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| try |
| { |
| TestEntity instance = createEntity( unitOfWork ); |
| unitOfWork.complete(); |
| |
| // Find entity |
| unitOfWork = module.newUnitOfWork(); |
| instance = unitOfWork.get( instance ); |
| |
| // Check state |
| assertThat( "property 'intValue' has correct value", |
| instance.intValue().get(), |
| equalTo( 42 ) ); |
| |
| assertThat( "property 'longValue' has correct value", |
| instance.longValue().get(), |
| equalTo( 42L ) ); |
| |
| assertThat( "property 'doubleValue' has correct value", |
| instance.doubleValue().get(), |
| equalTo( 42D ) ); |
| |
| assertThat( "property 'floatValue' has correct value", |
| instance.floatValue().get(), |
| equalTo( 42F ) ); |
| |
| assertThat( "property 'booleanValue' has correct value", |
| instance.booleanValue().get(), |
| equalTo( Boolean.TRUE ) ); |
| |
| assertThat( "property 'bigInteger' has correct value", |
| instance.bigIntegerValue().get(), |
| equalTo( new BigInteger( "42" ) ) ); |
| |
| assertThat( "property 'bigDecimal' has correct value", |
| instance.bigDecimalValue().get(), |
| equalTo( new BigDecimal( "42" ) ) ); |
| |
| assertThat( "property 'dateValue' has correct value", |
| instance.dateValue().get(), |
| equalTo( new DateTime( "2020-03-04T13:24:35", UTC ).toDate() ) ); |
| |
| assertThat( "property 'dateTimeValue' has correct value", |
| instance.dateTimeValue().get(), |
| equalTo( new DateTime( "2020-03-04T13:24:35", UTC ) ) ); |
| |
| assertThat( "property 'localDateTimeValue' has correct value", |
| instance.localDateTimeValue().get(), |
| equalTo( new LocalDateTime( "2020-03-04T13:23:00", UTC ) ) ); |
| |
| assertThat( "property 'localDateValue' has correct value", |
| instance.localDateValue().get(), |
| equalTo( new LocalDate( "2020-03-04" ) ) ); |
| |
| assertThat( "property 'name' has correct value", |
| instance.name().get(), |
| equalTo( "Test" ) ); |
| |
| assertThat( "property 'unsetName' has correct value", |
| instance.unsetName().get(), |
| equalTo( null ) ); |
| |
| assertThat( "property 'emptyName' has correct value", |
| instance.emptyName().get(), |
| equalTo( "" ) ); |
| |
| assertThat( "property 'valueProperty.stringValue' has correct value", |
| instance.valueProperty().get().valueProperty().get().stringValue().get(), |
| equalTo( "Bar" ) ); |
| |
| assertThat( "property 'valueProperty.listProperty' has correct value", |
| instance.valueProperty().get().listProperty().get().get( 0 ), |
| equalTo( "Foo" ) ); |
| |
| assertThat( "property 'valueProperty.enumProperty' has correct value", |
| instance.valueProperty().get().enumProperty().get(), |
| equalTo( TestEnum.VALUE3 ) ); |
| |
| assertThat( "property 'valueProperty.anotherValue.bling' has correct value", |
| instance.valueProperty().get().valueProperty().get().anotherValue().get().bling().get(), |
| equalTo( "BlinkLjus" ) ); |
| |
| assertThat( "property 'valueProperty.tjabbaProperty.bling' has correct value", |
| instance.valueProperty().get().tjabbaProperty().get().bling().get(), |
| equalTo( "Brakfis" ) ); |
| |
| Map<String, String> mapValue = new HashMap<>(); |
| mapValue.put( "foo", "bar" ); |
| assertThat( "property 'valueProperty.mapStringStringProperty' has correct value", |
| instance.valueProperty().get().mapStringStringProperty().get(), |
| equalTo( mapValue ) ); |
| |
| assertThat( "association has correct value", |
| instance.association().get(), |
| equalTo( instance ) ); |
| |
| assertThat( "manyAssociation has correct value", |
| instance.manyAssociation().iterator().next(), |
| equalTo( instance ) ); |
| |
| assertThat( "namedAssociation has correct 'foo' value", |
| instance.namedAssociation().get( "foo" ), |
| equalTo( instance ) ); |
| |
| assertThat( "namedAssociation has correct 'bar' value", |
| instance.namedAssociation().get( "bar" ), |
| equalTo( instance ) ); |
| |
| unitOfWork.discard(); |
| } |
| finally |
| { |
| unitOfWork.discard(); |
| } |
| } |
| |
| @Test |
| public void whenRemovedEntityThenCannotFindEntity() |
| throws Exception |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| TestEntity newInstance = createEntity( unitOfWork ); |
| String identity = newInstance.identity().get(); |
| unitOfWork.complete(); |
| |
| // Remove entity |
| unitOfWork = module.newUnitOfWork(); |
| TestEntity instance = unitOfWork.get( newInstance ); |
| unitOfWork.remove( instance ); |
| unitOfWork.complete(); |
| |
| // Find entity |
| unitOfWork = module.newUnitOfWork(); |
| try |
| { |
| unitOfWork.get( TestEntity.class, identity ); |
| fail( "Should not be able to find entity" ); |
| } |
| catch( NoSuchEntityException e ) |
| { |
| // Ok! |
| } |
| finally |
| { |
| unitOfWork.discard(); |
| } |
| } |
| |
| @Test |
| public void givenEntityIsNotModifiedWhenUnitOfWorkCompletesThenDontStoreState() |
| throws UnitOfWorkCompletionException |
| { |
| TestEntity testEntity; |
| String version; |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| EntityBuilder<TestEntity> builder = unitOfWork.newEntityBuilder( TestEntity.class ); |
| |
| testEntity = builder.newInstance(); |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| version = spi.entityStateOf( testEntity ).version(); |
| |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| String newVersion = spi.entityStateOf( testEntity ).version(); |
| assertThat( "version has not changed", newVersion, equalTo( version ) ); |
| |
| unitOfWork.complete(); |
| } |
| } |
| |
| @Test |
| public void givenPropertyIsModifiedWhenUnitOfWorkCompletesThenStoreState() |
| throws UnitOfWorkCompletionException |
| { |
| TestEntity testEntity; |
| String version; |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| EntityBuilder<TestEntity> builder = unitOfWork.newEntityBuilder( TestEntity.class ); |
| |
| testEntity = builder.newInstance(); |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| testEntity.name().set( "Rickard" ); |
| version = spi.entityStateOf( testEntity ).version(); |
| |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| String newVersion = spi.entityStateOf( testEntity ).version(); |
| assertThat( "version has changed", newVersion, not( equalTo( version ) ) ); |
| |
| unitOfWork.complete(); |
| } |
| } |
| |
| @Test |
| public void givenManyAssociationIsModifiedWhenUnitOfWorkCompletesThenStoreState() |
| throws UnitOfWorkCompletionException |
| { |
| TestEntity testEntity; |
| String version; |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| EntityBuilder<TestEntity> builder = unitOfWork.newEntityBuilder( TestEntity.class ); |
| |
| testEntity = builder.newInstance(); |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| testEntity.manyAssociation().add( 0, testEntity ); |
| version = spi.entityStateOf( testEntity ).version(); |
| |
| unitOfWork.complete(); |
| } |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| testEntity = unitOfWork.get( testEntity ); |
| String newVersion = spi.entityStateOf( testEntity ).version(); |
| assertThat( "version has changed", newVersion, not( equalTo( version ) ) ); |
| |
| unitOfWork.complete(); |
| } |
| } |
| |
| @Test |
| public void givenConcurrentUnitOfWorksWhenUoWCompletesThenCheckConcurrentModification() |
| throws UnitOfWorkCompletionException |
| { |
| TestEntity testEntity; |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| EntityBuilder<TestEntity> builder = unitOfWork.newEntityBuilder( TestEntity.class ); |
| |
| testEntity = builder.newInstance(); |
| unitOfWork.complete(); |
| } |
| |
| UnitOfWork unitOfWork1; |
| TestEntity testEntity1; |
| String version; |
| { |
| // Start working with Entity in one UoW |
| unitOfWork1 = module.newUnitOfWork(); |
| testEntity1 = unitOfWork1.get( testEntity ); |
| version = spi.entityStateOf( testEntity1 ).version(); |
| if( version.isEmpty() ) |
| { |
| unitOfWork1.discard(); |
| return; // Store doesn't track versions - no point in testing it |
| } |
| testEntity1.name().set( "A" ); |
| testEntity1.unsetName().set( "A" ); |
| } |
| { |
| // Start working with same Entity in another UoW, and complete it |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| TestEntity testEntity2 = unitOfWork.get( testEntity ); |
| assertThat( "version is correct", spi.entityStateOf( testEntity1 ).version(), equalTo( version ) ); |
| testEntity2.name().set( "B" ); |
| unitOfWork.complete(); |
| } |
| { |
| // Try to complete first UnitOfWork |
| try |
| { |
| unitOfWork1.complete(); |
| fail( "Should have thrown concurrent modification exception" ); |
| } |
| catch( ConcurrentEntityModificationException e ) |
| { |
| unitOfWork1.discard(); |
| } |
| } |
| { |
| // Check values |
| unitOfWork1 = module.newUnitOfWork(); |
| testEntity1 = unitOfWork1.get( testEntity ); |
| assertThat( "property name has not been set", testEntity1.name().get(), equalTo( "B" ) ); |
| assertThat( "version is incorrect", spi.entityStateOf( testEntity1 ).version(), |
| not( equalTo( version ) ) ); |
| unitOfWork1.discard(); |
| } |
| } |
| |
| @Test |
| public void givenEntityStoredLoadedChangedWhenUnitOfWorkDiscardsThenDontStoreState() |
| throws UnitOfWorkCompletionException |
| { |
| UnitOfWork unitOfWork = module.newUnitOfWork(); |
| try |
| { |
| String identity = createEntity( unitOfWork ).identity().get(); |
| unitOfWork.complete(); |
| |
| unitOfWork = module.newUnitOfWork(); |
| TestEntity entity = unitOfWork.get( TestEntity.class, identity ); |
| assertThat( entity.intValue().get(), is( 42 ) ); |
| entity.intValue().set( 23 ); |
| unitOfWork.discard(); |
| |
| unitOfWork = module.newUnitOfWork(); |
| entity = unitOfWork.get( TestEntity.class, identity ); |
| assertThat( entity.intValue().get(), is( 42 ) ); |
| } |
| finally |
| { |
| unitOfWork.discard(); |
| } |
| } |
| |
| public interface TestEntity |
| extends EntityComposite |
| { |
| |
| @UseDefaults |
| Property<Integer> intValue(); |
| |
| @UseDefaults |
| Property<Long> longValue(); |
| |
| @UseDefaults |
| Property<Double> doubleValue(); |
| |
| @UseDefaults |
| Property<Float> floatValue(); |
| |
| @UseDefaults |
| Property<Boolean> booleanValue(); |
| |
| @Optional |
| Property<BigInteger> bigIntegerValue(); |
| |
| @Optional |
| Property<BigDecimal> bigDecimalValue(); |
| |
| @Optional |
| Property<Date> dateValue(); |
| |
| @Optional |
| Property<DateTime> dateTimeValue(); |
| |
| @Optional |
| Property<LocalDateTime> localDateTimeValue(); |
| |
| @Optional |
| Property<LocalDate> localDateValue(); |
| |
| @Optional |
| Property<String> name(); |
| |
| @Optional |
| Property<String> unsetName(); |
| |
| @UseDefaults |
| Property<String> emptyName(); |
| |
| @Optional |
| Property<TestValue> valueProperty(); |
| |
| @Optional |
| Association<TestEntity> association(); |
| |
| @Optional |
| Association<TestEntity> unsetAssociation(); |
| |
| ManyAssociation<TestEntity> manyAssociation(); |
| |
| NamedAssociation<TestEntity> namedAssociation(); |
| } |
| |
| public interface TjabbaValue |
| extends Tjabba, ValueComposite |
| { |
| } |
| |
| public interface Tjabba |
| { |
| |
| Property<String> bling(); |
| } |
| |
| public interface TestValue |
| extends ValueComposite |
| { |
| |
| @UseDefaults |
| Property<String> stringProperty(); |
| |
| @UseDefaults |
| Property<Integer> intProperty(); |
| |
| @UseDefaults |
| Property<TestEnum> enumProperty(); |
| |
| @UseDefaults |
| Property<List<String>> listProperty(); |
| |
| @UseDefaults |
| Property<Map<String, Tjabba>> mapProperty(); |
| |
| Property<TestValue2> valueProperty(); |
| |
| Property<Tjabba> tjabbaProperty(); |
| |
| Property<Map<String, String>> mapStringStringProperty(); |
| } |
| |
| public interface TestValue2 |
| extends ValueComposite |
| { |
| |
| Property<String> stringValue(); |
| |
| Property<Tjabba> anotherValue(); |
| } |
| |
| public enum TestEnum |
| { |
| |
| VALUE1, VALUE2, VALUE3 |
| } |
| |
| } |