blob: ddea6d212a7393263afdc5ff0e67cd75fc57f492 [file] [log] [blame]
/*
* 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.polygene.test.serialization;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.polygene.api.association.Association;
import org.apache.polygene.api.association.ManyAssociation;
import org.apache.polygene.api.association.NamedAssociation;
import org.apache.polygene.api.common.Optional;
import org.apache.polygene.api.common.UseDefaults;
import org.apache.polygene.api.common.Visibility;
import org.apache.polygene.api.entity.EntityBuilder;
import org.apache.polygene.api.entity.EntityComposite;
import org.apache.polygene.api.entity.EntityReference;
import org.apache.polygene.api.identity.HasIdentity;
import org.apache.polygene.api.identity.Identity;
import org.apache.polygene.api.identity.StringIdentity;
import org.apache.polygene.api.injection.scope.Service;
import org.apache.polygene.api.injection.scope.Structure;
import org.apache.polygene.api.injection.scope.This;
import org.apache.polygene.api.mixin.Mixins;
import org.apache.polygene.api.property.Property;
import org.apache.polygene.api.serialization.ConvertedBy;
import org.apache.polygene.api.serialization.JavaSerializationConverter;
import org.apache.polygene.api.serialization.Serialization;
import org.apache.polygene.api.structure.Module;
import org.apache.polygene.api.unitofwork.UnitOfWork;
import org.apache.polygene.api.value.ValueBuilder;
import org.apache.polygene.api.value.ValueComposite;
import org.apache.polygene.bootstrap.ModuleAssembly;
import org.apache.polygene.test.AbstractPolygeneTest;
import org.apache.polygene.test.EntityTestAssembler;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.polygene.api.usecase.UsecaseBuilder.newUsecase;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Assert that Serialization behaviour on ValueComposites is correct.
*/
// TODO Assert Generics behaviour!
public abstract class AbstractValueCompositeSerializationTest
extends AbstractPolygeneTest
{
@Rule
public TestName testName = new TestName();
@Before
public void before()
{
System.out.println( "# BEGIN " + testName.getMethodName() );
}
@After
public void after()
{
System.out.println( "# END " + testName.getMethodName() );
}
@Override
public void assemble( ModuleAssembly module )
{
module.objects( JavaSerializationConverter.class );
module.values( Some.class, SomeExtended.class, SomeShuffled.class,
AnotherValue.class, FooValue.class, CustomFooValue.class,
SpecificCollection.class /*, SpecificValue.class, GenericValue.class */ );
module.entities( Some.class, BarEntity.class );
new EntityTestAssembler().visibleIn( Visibility.layer ).assemble( module.layer().module( "persistence" ) );
}
@Structure
protected Module moduleInstance;
@Service
protected Serialization serialization;
@Test
public void givenValueCompositeWhenSerializingAndDeserializingExpectEquals()
throws Exception
{
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() )
{
Some some = buildSomeValue( moduleInstance, uow, "23" );
// Serialize using injected service
String stateString = serialization.serialize( some );
System.out.println( stateString );
// Deserialize using Module API
Some some2 = moduleInstance.newValueFromSerializedState( Some.class, stateString );
assertThat( "Map<String, Integer>",
some2.stringIntMap().get().get( "foo" ),
equalTo( 42 ) );
assertThat( "Map<String, Value>",
some2.stringValueMap().get().get( "foo" ).internalVal(),
equalTo( "Bar" ) );
assertThat( "Nested Entities",
some2.barAssociation().get().cathedral().get(),
equalTo( "bazar in barAssociation" ) );
assertThat( "Polymorphic deserialization of value type NOT extending ValueComposite",
some.customFoo().get() instanceof CustomFooValue,
is( true ) );
assertThat( "Polymorphic deserialization of value type extending ValueComposite",
some.customFooValue().get() instanceof CustomFooValue,
is( true ) );
assertThat( "Value equality", some, equalTo( some2 ) );
uow.complete();
}
}
@Test
public void givenEntityCompositeWhenSerializingAndDeserializingExpectEquals()
throws Exception
{
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() )
{
Some some = buildSomeEntity( moduleInstance, uow, "23" );
// Serialize using injected service
String stateString = serialization.serialize( some );
System.out.println( stateString );
// Deserialize using Module API
Some some2 = moduleInstance.newValueFromSerializedState( Some.class, stateString );
assertThat( "Map<String, Integer>",
some2.stringIntMap().get().get( "foo" ),
equalTo( 42 ) );
assertThat( "Map<String, Value>",
some2.stringValueMap().get().get( "foo" ).internalVal(),
equalTo( "Bar" ) );
assertThat( "Nested Entities",
some2.barAssociation().get().cathedral().get(),
equalTo( "bazar in barAssociation" ) );
assertThat( "Polymorphic deserialization of value type NOT extending ValueComposite",
some.customFoo().get() instanceof CustomFooValue,
is( true ) );
assertThat( "Polymorphic deserialization of value type extending ValueComposite",
some.customFooValue().get() instanceof CustomFooValue,
is( true ) );
assertThat( "Value equality", some, equalTo( some2 ) );
uow.complete();
}
}
@Test
public void canDeserializeUsingSuperTypeWithLessState()
{
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() )
{
SomeExtended someExtended = buildSomeExtendedValue( moduleInstance, uow, "42" );
String serialized = serialization.serialize( someExtended );
System.out.println( serialized );
Some deserialized = serialization.deserialize( module, Some.class, serialized );
System.out.println( deserialized );
uow.complete();
}
}
@Test
public void canDeserializeUsingChildTypeWithSupplementaryOptionalState()
{
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() )
{
Some some = buildSomeValue( moduleInstance, uow, "42" );
String serialized = serialization.serialize( some );
System.out.println( serialized );
SomeExtended deserialized = serialization.deserialize( module, SomeExtended.class, serialized );
System.out.println( deserialized );
uow.complete();
}
}
/**
* State model order depend on declaration order, this test ensures that moving a state method up/down into a type
* does not break deserialization.
*/
@Test
public void canDeserializeFromShuffledState()
{
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork() )
{
SomeExtended someExtended = buildSomeExtendedValue( moduleInstance, uow, "42" );
String serialized = serialization.serialize( someExtended );
System.out.println( serialized );
SomeShuffled deserialized = serialization.deserialize( module, SomeShuffled.class, serialized );
System.out.println( deserialized );
serialized = serialization.serialize( deserialized );
System.out.println( serialized );
serialization.deserialize( module, SomeExtended.class, serialized );
System.out.println( deserialized );
uow.complete();
}
}
@Test
@Ignore( "JSONEntityState cannot handle polymorphic deserialization" )
// TODO Entity == Identity + Value
// JSONEntityState does not allow for polymorphic serialization
public void valueAndEntityTypeEquality()
{
Identity identity = StringIdentity.identity( "42" );
Some createdValue, loadedValue;
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork( newUsecase( "create" ) ) )
{
Some entity = buildSomeEntity( moduleInstance, uow, identity );
createdValue = uow.toValue( Some.class, entity );
System.out.println( "Created Entity\n\t" + entity + "\nCreated Value\n\t" + createdValue );
uow.complete();
}
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork( newUsecase( "load" ) ) )
{
Some entity = uow.get( Some.class, identity );
loadedValue = uow.toValue( Some.class, entity );
System.out.println( "Loaded Entity\n\t" + entity + "\nLoaded Value\n\t" + loadedValue );
}
assertThat( "Create/Read equality",
createdValue, equalTo( loadedValue ) );
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork( newUsecase( "remove" ) ) )
{
uow.remove( uow.get( Some.class, identity ) );
uow.complete();
}
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork( newUsecase( "create from value" ) ) )
{
Some entity = uow.toEntity( Some.class, loadedValue );
createdValue = uow.toValue( Some.class, entity );
System.out.println( "Created Entity from Value\n\t" + entity + "\nCreated Value\n\t" + createdValue );
uow.complete();
}
try( UnitOfWork uow = unitOfWorkFactory.newUnitOfWork( newUsecase( "read again" ) ) )
{
Some entity = uow.get( Some.class, identity );
loadedValue = uow.toValue( Some.class, entity );
System.out.println( "Loaded Entity\n\t" + entity + "\nLoaded Value\n\t" + loadedValue );
}
assertThat( "Create from Value/Read equality",
createdValue, equalTo( loadedValue ) );
}
protected static Some buildSomeEntity( Module module, UnitOfWork uow, String identity )
{
EntityBuilder<Some> builder = uow.newEntityBuilder( Some.class );
Some proto = builder.instance();
proto.identity().set( StringIdentity.identity( identity ) );
setSomeValueState( module, uow, proto );
return builder.newInstance();
}
/**
* @return a Some ValueComposite whose state is populated with test data.
*/
protected static Some buildSomeValue( Module module, UnitOfWork uow, String identity )
{
ValueBuilder<Some> builder = module.newValueBuilder( Some.class );
Some proto = builder.prototype();
proto.identity().set( StringIdentity.identity( identity ) );
setSomeValueState( module, uow, proto );
return builder.newInstance();
}
protected static SomeExtended buildSomeExtendedValue( Module module, UnitOfWork uow, String identity )
{
ValueBuilder<SomeExtended> builder = module.newValueBuilder( SomeExtended.class );
SomeExtended proto = builder.prototype();
proto.identity().set( StringIdentity.identity( identity ) );
setSomeValueState( module, uow, proto );
proto.extraProperty().set( "extra property" );
proto.extraAssociation().set( buildBarEntity( module, uow, "extra association" ) );
proto.extraManyAssociation().add( buildBarEntity( module, uow, "extra many association" ) );
proto.extraNamedAssociation().put( "extra", buildBarEntity( module, uow, "extra named association" ) );
return builder.newInstance();
}
/**
* @return a Some EntityComposite whose state is populated with test data.
*/
protected static Some buildSomeEntity( Module module, UnitOfWork uow, Identity identity )
{
EntityBuilder<Some> builder = uow.newEntityBuilder( Some.class, identity );
setSomeValueState( module, uow, builder.instance() );
return builder.newInstance();
}
private static void setSomeValueState( Module module, UnitOfWork uow, Some some )
{
some.anotherList().get().add( module.newValue( AnotherValue.class ) );
ValueBuilder<SpecificCollection> specificColBuilder = module.newValueBuilder( SpecificCollection.class );
SpecificCollection specificColProto = specificColBuilder.prototype();
List<String> genericList = new ArrayList<>( 2 );
genericList.add( "Some" );
genericList.add( "String" );
specificColProto.genericList().set( genericList );
some.specificCollection().set( specificColBuilder.newInstance() );
AnotherValue anotherValue1 = createAnotherValue( module, "Foo", "Bar" );
AnotherValue anotherValue2 = createAnotherValue( module, "Habba", "ZoutZout" );
AnotherValue anotherValue3 = createAnotherValue( module, "Niclas", "Hedhman" );
some.string().set( "Foo\"Bar\"\nTest\f\t\b\r" );
some.string2().set( "/Foo/bar" );
some.number().set( 43L );
some.localTime().set( LocalTime.now() );
some.dateTime().set( OffsetDateTime.of( 2020, 3, 4, 13, 24, 35, 0, ZoneOffset.ofHours( 1 ) ) );
some.localDate().set( LocalDate.now() );
some.localDateTime().set( LocalDateTime.now() );
some.entityReference().set( EntityReference.parseEntityReference( "12345" ) );
some.stringIntMap().get().put( "foo", 42 );
// Can't put more than one entry in Map because this test rely on the fact that the underlying implementations
// maintain a certain order but it's not the case on some JVMs. On OpenJDK 8 they are reversed for example.
// This should not be enforced tough as both the Map API and the JSON specification state that name-value pairs
// are unordered.
// As a consequence this test should be enhanced to be Map order independent.
//
// proto.stringIntMap().get().put( "bar", 67 );
some.stringValueMap().get().put( "foo", anotherValue1 );
some.another().set( anotherValue1 );
some.arrayOfValues().set( new AnotherValue[] { anotherValue1, anotherValue2, anotherValue3 } );
some.primitiveByteArray().set( "foo".getBytes( UTF_8 ) );
some.byteArray().set( new Byte[] { 23, null, 42 } );
some.serializable().set( new SerializableObject() );
some.foo().set( module.newValue( FooValue.class ) );
some.fooValue().set( module.newValue( FooValue.class ) );
some.customFoo().set( module.newValue( CustomFooValue.class ) );
some.customFooValue().set( module.newValue( CustomFooValue.class ) );
// NestedEntities
some.barAssociation().set( buildBarEntity( module, uow, "bazar in barAssociation" ) );
some.barEntityAssociation().set( buildBarEntity( module, uow, "bazar in barEntityAssociation" ) );
some.barManyAssociation().add( buildBarEntity( module, uow, "bazar ONE in barManyAssociation" ) );
some.barManyAssociation().add( buildBarEntity( module, uow, "bazar TWO in barManyAssociation" ) );
some.barEntityManyAssociation().add( buildBarEntity( module, uow, "bazar ONE in barEntityManyAssociation" ) );
some.barEntityManyAssociation().add( buildBarEntity( module, uow, "bazar TWO in barEntityManyAssociation" ) );
some.barNamedAssociation().put( "bazar", buildBarEntity( module, uow, "bazar in barNamedAssociation" ) );
some.barNamedAssociation().put( "cathedral",
buildBarEntity( module, uow, "cathedral in barNamedAssociation" ) );
some.barEntityNamedAssociation().put( "bazar",
buildBarEntity( module, uow, "bazar in barEntityNamedAssociation" ) );
some.barEntityNamedAssociation().put( "cathedral",
buildBarEntity( module, uow, "cathedral in barEntityNamedAssociation" ) );
}
private static AnotherValue createAnotherValue( Module module, String val1, String val2 )
{
ValueBuilder<AnotherValue> valueBuilder = module.newValueBuilder( AnotherValue.class );
valueBuilder.prototype().val1().set( val1 );
valueBuilder.prototypeFor( AnotherValueInternalState.class ).val2().set( val2 );
return valueBuilder.newInstance();
}
private static BarEntity buildBarEntity( Module module, UnitOfWork uow, String cathedral )
{
EntityBuilder<BarEntity> barBuilder = uow.newEntityBuilder( BarEntity.class );
barBuilder.instance().cathedral().set( cathedral );
barBuilder.instance().another().set( createAnotherValue( module, "nested", "value" ) );
return barBuilder.newInstance();
}
public enum TestEnum
{
somevalue,
anothervalue
}
public interface Some extends HasIdentity
{
Property<String> string();
Property<String> string2();
@Optional
Property<String> nullString();
@UseDefaults
Property<String> emptyString();
@UseDefaults
Property<Long> number();
Property<LocalTime> localTime();
Property<OffsetDateTime> dateTime();
Property<LocalDate> localDate();
Property<LocalDateTime> localDateTime();
Property<EntityReference> entityReference();
@UseDefaults
Property<List<String>> stringList();
@UseDefaults
Property<Map<String, Integer>> stringIntMap();
@UseDefaults
Property<Map<String, AnotherValue>> stringValueMap();
Property<AnotherValue> another();
Property<AnotherValue[]> arrayOfValues();
@Optional
Property<AnotherValue> anotherNull();
@UseDefaults
Property<List<AnotherValue>> anotherList();
@Optional
Property<List<AnotherValue>> anotherListNull();
@UseDefaults
Property<List<AnotherValue>> anotherListEmpty();
@UseDefaults
Property<TestEnum> testEnum();
Property<byte[]> primitiveByteArray();
@Optional
Property<byte[]> primitiveByteArrayNull();
Property<Byte[]> byteArray();
@Optional
Property<Byte[]> byteArrayNull();
Property<SerializableObject> serializable();
Property<Foo> foo();
Property<FooValue> fooValue();
Property<Foo> customFoo();
Property<FooValue> customFooValue();
Property<SpecificCollection> specificCollection();
/* Too complicated to do generics here for now
Property<SpecificValue> specificValue();
*/
@Optional
Association<Bar> barAssociationOptional();
Association<Bar> barAssociation();
Association<BarEntity> barEntityAssociation();
ManyAssociation<Bar> barManyAssociationEmpty();
ManyAssociation<Bar> barManyAssociation();
ManyAssociation<BarEntity> barEntityManyAssociation();
NamedAssociation<Bar> barNamedAssociationEmpty();
NamedAssociation<Bar> barNamedAssociation();
NamedAssociation<BarEntity> barEntityNamedAssociation();
}
interface SomeExtended extends Some
{
@Optional
Property<String> extraProperty();
@Optional
Association<Bar> extraAssociation();
ManyAssociation<Bar> extraManyAssociation();
NamedAssociation<Bar> extraNamedAssociation();
}
interface SomeShuffled extends SomeExtended
{
NamedAssociation<Bar> extraNamedAssociation();
@Override
NamedAssociation<Bar> barNamedAssociation();
ManyAssociation<Bar> extraManyAssociation();
@Override
ManyAssociation<Bar> barManyAssociation();
Association<Bar> extraAssociation();
@Override
Association<Bar> barAssociation();
Property<String> extraProperty();
@Override
Property<String> string();
}
public interface SpecificCollection
extends GenericCollection<String>
{
}
public interface GenericCollection<TYPE>
extends ValueComposite
{
@UseDefaults
Property<List<TYPE>> genericList();
}
public interface SpecificValue
extends GenericValue<String>
{
}
public interface GenericValue<TYPE>
extends ValueComposite
{
@Optional
Property<TYPE> item();
}
@Mixins( AnotherValueMixin.class )
public interface AnotherValue
extends ValueComposite
{
@UseDefaults
Property<String> val1();
String internalVal();
}
public interface AnotherValueInternalState
{
@UseDefaults
Property<String> val2();
}
public static abstract class AnotherValueMixin
implements AnotherValue
{
@This
private AnotherValueInternalState internalState;
@Override
public String internalVal()
{
return internalState.val2().get();
}
}
public interface Foo
{
@UseDefaults
Property<String> bar();
}
public interface FooValue
extends Foo, ValueComposite
{
}
public interface CustomFooValue
extends FooValue
{
@UseDefaults
Property<String> custom();
}
public interface Bar
{
@UseDefaults
Property<String> cathedral();
Property<AnotherValue> another();
}
public interface BarEntity
extends Bar, EntityComposite
{
}
@ConvertedBy( JavaSerializationConverter.class )
public static class SerializableObject
implements Serializable
{
private static final long serialVersionUID = 1L;
private final String foo = "Foo";
private final int val = 35;
@Override
public boolean equals( Object o )
{
if( this == o )
{
return true;
}
if( o == null || getClass() != o.getClass() )
{
return false;
}
SerializableObject that = (SerializableObject) o;
return val == that.val && foo.equals( that.foo );
}
@Override
public int hashCode()
{
int result = foo.hashCode();
result = 31 * result + val;
return result;
}
}
}