blob: 859d98c395b3f5a9f926b14092f3ad22782c6394 [file] [log] [blame]
/*
* Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
* Copyright (c) 2012, 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.value;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
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.common.Visibility;
import org.apache.zest.api.entity.EntityBuilder;
import org.apache.zest.api.entity.EntityComposite;
import org.apache.zest.api.entity.EntityReference;
import org.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.This;
import org.apache.zest.api.mixin.Mixins;
import org.apache.zest.api.property.Property;
import org.apache.zest.api.unitofwork.UnitOfWork;
import org.apache.zest.api.value.ValueBuilder;
import org.apache.zest.api.value.ValueComposite;
import org.apache.zest.api.value.ValueSerialization;
import org.apache.zest.bootstrap.AssemblyException;
import org.apache.zest.bootstrap.ModuleAssembly;
import org.apache.zest.test.AbstractQi4jTest;
import org.apache.zest.test.EntityTestAssembler;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Assert that ValueSerialization behaviour on ValueComposites is correct.
*/
// TODO Assert Arrays behaviour!
// TODO Assert Generics behaviour!
public abstract class AbstractValueCompositeSerializationTest
extends AbstractQi4jTest
{
@Rule
public TestName testName = new TestName();
@Override
public void assemble( ModuleAssembly module )
throws AssemblyException
{
module.values( SomeValue.class, AnotherValue.class, FooValue.class, CustomFooValue.class,
SpecificCollection.class /*, SpecificValue.class, GenericValue.class */ );
new EntityTestAssembler().visibleIn( Visibility.layer ).assemble( module.layer().module( "persistence" ) );
module.entities( BarEntity.class );
}
@Before
public void before()
{
module.injectTo( this );
}
@Service
protected ValueSerialization valueSerialization;
@Test
public void givenValueCompositeWhenSerializingAndDeserializingExpectEquals()
throws Exception
{
UnitOfWork uow = module.newUnitOfWork();
try
{
SomeValue some = buildSomeValue();
// Serialize using injected service
ByteArrayOutputStream output = new ByteArrayOutputStream();
valueSerialization.serialize( some, output );
String stateString = output.toString( "UTF-8" );
// Deserialize using Module API
SomeValue some2 = module.newValueFromSerializedState( SomeValue.class, stateString );
assertThat( "Same value toString", some.toString(), equalTo( some2.toString() ) );
assertThat( "Same value", some, equalTo( some2 ) );
assertThat( "Same JSON value toString", stateString, equalTo( some2.toString() ) );
assertThat( "Same JSON value", some.customFoo().get() instanceof CustomFooValue, is( true ) );
assertThat( "Same JSON value explicit", some.customFooValue().get() instanceof CustomFooValue, is( true ) );
assertThat( "String Integer Map", some2.stringIntMap().get().get( "foo" ), equalTo( 42 ) );
assertThat( "String Value Map", some2.stringValueMap().get().get( "foo" ).internalVal(), equalTo( "Bar" ) );
assertThat( "Nested Entities", some2.barAssociation().get().cathedral().get(), equalTo( "bazar in barAssociation" ) );
}
catch( Exception ex )
{
throw ex;
}
finally
{
uow.discard();
}
}
/**
* @return a SomeValue ValueComposite whose state is populated with test data.
*/
private SomeValue buildSomeValue()
{
ValueBuilder<SomeValue> builder = module.newValueBuilder( SomeValue.class );
SomeValue proto = builder.prototype();
proto.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 );
proto.specificCollection().set( specificColBuilder.newInstance() );
AnotherValue anotherValue1 = createAnotherValue( "Foo", "Bar" );
AnotherValue anotherValue2 = createAnotherValue( "Habba", "ZoutZout" );
AnotherValue anotherValue3 = createAnotherValue( "Niclas", "Hedhman" );
// FIXME Some Control Chars are not supported in JSON nor in XML, what should we do about it?
// Should Zest Core ensure the chars used in strings are supported by the whole stack?
// proto.string().set( "Foo\"Bar\"\nTest\f\t\b" );
proto.string().set( "Foo\"Bar\"\nTest\t" );
proto.string2().set( "/Foo/bar" );
proto.number().set( 42L );
proto.date().set( new Date() );
// We specify the TimeZone explicitely here so that serialized/deserialized is equals
// See https://github.com/JodaOrg/joda-time/issues/106
proto.dateTime().set( new DateTime( "2020-03-04T13:24:35", DateTimeZone.forOffsetHours( 1 ) ) );
proto.localDate().set( new LocalDate() );
proto.localDateTime().set( new LocalDateTime() );
proto.entityReference().set( EntityReference.parseEntityReference( "12345" ) );
proto.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 independant.
//
// proto.stringIntMap().get().put( "bar", 67 );
proto.stringValueMap().get().put( "foo", anotherValue1 );
proto.another().set( anotherValue1 );
// proto.arrayOfValues().set( new AnotherValue[] { anotherValue1, anotherValue2, anotherValue3 } );
proto.serializable().set( new SerializableObject() );
proto.foo().set( module.newValue( FooValue.class ) );
proto.fooValue().set( module.newValue( FooValue.class ) );
proto.customFoo().set( module.newValue( CustomFooValue.class ) );
proto.customFooValue().set( module.newValue( CustomFooValue.class ) );
// Arrays
// TODO FIXME Disabled as ValueComposite equality fails here
//proto.primitiveByteArray().set( new byte[]
// {
// 9, -12, 42, -12, 127, 23, -128, 73
// } );
//proto.byteArray().set( new Byte[]
// {
// 9, null, -12, 23, -12, 127, -128, 73
// } );
// NestedEntities
proto.barAssociation().set( buildBarEntity( "bazar in barAssociation" ) );
proto.barEntityAssociation().set( buildBarEntity( "bazar in barEntityAssociation" ) );
proto.barManyAssociation().add( buildBarEntity( "bazar ONE in barManyAssociation" ) );
proto.barManyAssociation().add( buildBarEntity( "bazar TWO in barManyAssociation" ) );
proto.barEntityManyAssociation().add( buildBarEntity( "bazar ONE in barEntityManyAssociation" ) );
proto.barEntityManyAssociation().add( buildBarEntity( "bazar TWO in barEntityManyAssociation" ) );
proto.barNamedAssociation().put( "bazar", buildBarEntity( "bazar in barNamedAssociation" ) );
proto.barNamedAssociation().put( "cathedral", buildBarEntity( "cathedral in barNamedAssociation" ) );
proto.barEntityNamedAssociation().put( "bazar", buildBarEntity( "bazar in barEntityNamedAssociation" ) );
proto.barEntityNamedAssociation().put( "cathedral", buildBarEntity( "cathedral in barEntityNamedAssociation" ) );
return builder.newInstance();
}
private AnotherValue createAnotherValue( 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 BarEntity buildBarEntity( String cathedral )
{
EntityBuilder<BarEntity> barBuilder = module.currentUnitOfWork().newEntityBuilder( BarEntity.class );
barBuilder.instance().cathedral().set( cathedral );
return barBuilder.newInstance();
}
public enum TestEnum
{
somevalue, anothervalue
}
public interface SomeValue
extends ValueComposite
{
Property<String> string();
Property<String> string2();
@Optional
Property<String> nullString();
@UseDefaults
Property<String> emptyString();
@UseDefaults
Property<Long> number();
Property<Date> date();
Property<DateTime> 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();
// TODO FIXME Disabled as ValueComposite equality fails here
//Property<byte[]> primitiveByteArray();
//
//@Optional
//Property<byte[]> primitiveByteArrayNull();
//
//Property<Byte[]> byteArray();
//
//@Optional
//Property<Byte[]> byteArrayNull();
Property<Object> 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();
}
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();
}
public interface BarEntity
extends Bar, EntityComposite
{
}
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;
}
}
}