blob: 6f17569a7690552f4e9869a2764edb69a85d712e [file] [log] [blame]
/*
* Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
* Copyright (c) 2010, Niclas Hednman. All Rights Reserved.
* Copyright (c) 2012-2014, 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.qi4j.spi.value;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalDateTime;
import org.qi4j.api.Qi4j;
import org.qi4j.api.association.Association;
import org.qi4j.api.association.AssociationDescriptor;
import org.qi4j.api.association.AssociationStateHolder;
import org.qi4j.api.association.ManyAssociation;
import org.qi4j.api.association.NamedAssociation;
import org.qi4j.api.composite.CompositeInstance;
import org.qi4j.api.entity.EntityComposite;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.entity.Identity;
import org.qi4j.api.property.Property;
import org.qi4j.api.property.PropertyDescriptor;
import org.qi4j.api.util.Base64Encoder;
import org.qi4j.api.util.Dates;
import org.qi4j.api.value.ValueComposite;
import org.qi4j.api.value.ValueDescriptor;
import org.qi4j.api.value.ValueSerializationException;
import org.qi4j.api.value.ValueSerializer;
import org.qi4j.functional.Function;
import org.qi4j.functional.Function2;
import static org.qi4j.functional.Iterables.first;
/**
* Adapter for pull-parsing capable ValueSerializers.
*
* <p>
* Among Plain values (see {@link ValueSerializer}) some are considered primitives to underlying serialization
* mechanisms and by so handed/come without conversion to/from implementations. Primitive values can be one of:
* </p>
* <ul>
* <li>String,</li>
* <li>Character or char,</li>
* <li>Boolean or boolean,</li>
* <li>Integer or int,</li>
* <li>Long or long,</li>
* <li>Short or short,</li>
* <li>Byte or byte,</li>
* <li>Float or float,</li>
* <li>Double or double.</li>
* </ul>
* <p>
* Some other Plain values are transformed before being handed to implementations:
* </p>
* <ul>
* <li>BigInteger and BigDecimal depends on ValueSerializer.{@link org.qi4j.api.value.ValueSerializer.Options};</li>
* <li>Date as a ISO-8601 UTC String;</li>
* <li>DateTime (JodaTime) as a ISO-8601 String with timezone offset or Z for UTC;</li>
* <li>LocalDateTime (JodaTime) as a ISO-8601 String with no timezone offset;</li>
* <li>LocalDate (JodaTime) as a ISO-8601 String with no time info;</li>
* </ul>
*
* @param <OutputType> Implementor output type
*/
public abstract class ValueSerializerAdapter<OutputType>
implements ValueSerializer
{
public static interface ComplexSerializer<T, OutputType>
{
void serialize( Options options, T object, OutputType output )
throws Exception;
}
private static final String UTF_8 = "UTF-8";
private static <TO, FROM extends TO> Function2<Options, FROM, TO> identitySerializer()
{
return new Function2<Options, FROM, TO>()
{
@Override
public TO map( Options options, FROM from )
{
return from;
}
};
}
private final Map<Class<?>, Function2<Options, Object, Object>> serializers = new HashMap<>( 16 );
private final Map<Class<?>, ComplexSerializer<Object, OutputType>> complexSerializers = new HashMap<>( 2 );
/**
* Register a Plain Value type serialization Function.
*
* @param <T> Plain Value parametrized Type
* @param type Plain Value Type
* @param serializer Serialization Function
*/
@SuppressWarnings( "unchecked" )
protected final <T> void registerSerializer( Class<T> type, Function2<Options, T, Object> serializer )
{
serializers.put( type, (Function2<Options, Object, Object>) serializer );
}
/**
* Register a Complex Value type serialization Function.
*
* @param <T> Complex Value parametrized Type
* @param type Complex Value Type
* @param serializer Serialization Function
*/
@SuppressWarnings( "unchecked" )
protected final <T> void registerComplexSerializer( Class<T> type, ComplexSerializer<T, OutputType> serializer )
{
complexSerializers.put( type, (ComplexSerializer<Object, OutputType>) serializer );
}
public ValueSerializerAdapter()
{
// Primitive Value types
registerSerializer( String.class, ValueSerializerAdapter.<Object, String>identitySerializer() );
registerSerializer( Character.class, ValueSerializerAdapter.<Object, Character>identitySerializer() );
registerSerializer( Boolean.class, ValueSerializerAdapter.<Object, Boolean>identitySerializer() );
registerSerializer( Integer.class, ValueSerializerAdapter.<Object, Integer>identitySerializer() );
registerSerializer( Long.class, ValueSerializerAdapter.<Object, Long>identitySerializer() );
registerSerializer( Short.class, ValueSerializerAdapter.<Object, Short>identitySerializer() );
registerSerializer( Byte.class, ValueSerializerAdapter.<Object, Byte>identitySerializer() );
registerSerializer( Float.class, ValueSerializerAdapter.<Object, Float>identitySerializer() );
registerSerializer( Double.class, ValueSerializerAdapter.<Object, Double>identitySerializer() );
// Number types
registerSerializer( BigDecimal.class, new Function2<Options, BigDecimal, Object>()
{
@Override
public Object map( Options options, BigDecimal bigDecimal )
{
return bigDecimal.toString();
}
} );
registerSerializer( BigInteger.class, new Function2<Options, BigInteger, Object>()
{
@Override
public Object map( Options options, BigInteger bigInteger )
{
return bigInteger.toString();
}
} );
// Date types
registerSerializer( Date.class, new Function2<Options, Date, Object>()
{
@Override
public Object map( Options options, Date date )
{
return Dates.toUtcString( date );
}
} );
registerSerializer( DateTime.class, new Function2<Options, DateTime, Object>()
{
@Override
public Object map( Options options, DateTime date )
{
return date.toString();
}
} );
registerSerializer( LocalDateTime.class, new Function2<Options, LocalDateTime, Object>()
{
@Override
public Object map( Options options, LocalDateTime date )
{
return date.toString();
}
} );
registerSerializer( LocalDate.class, new Function2<Options, LocalDate, Object>()
{
@Override
public Object map( Options options, LocalDate date )
{
return date.toString();
}
} );
// Other supported types
registerSerializer( EntityReference.class, new Function2<Options, EntityReference, Object>()
{
@Override
public Object map( Options options, EntityReference ref )
{
return ref.toString();
}
} );
}
@Override
public final <T> Function<T, String> serialize()
{
return new Function<T, String>()
{
@Override
public String map( T object )
{
return serialize( object );
}
};
}
@Override
public final <T> Function<T, String> serialize( final Options options )
{
return new Function<T, String>()
{
@Override
public String map( T object )
{
return serialize( options, object );
}
};
}
@Override
@Deprecated
public final <T> Function<T, String> serialize( final boolean includeTypeInfo )
{
return new Function<T, String>()
{
@Override
public String map( T object )
{
return serialize( includeTypeInfo ? new Options().withTypeInfo() : new Options().withoutTypeInfo(),
object );
}
};
}
@Override
public final String serialize( Object object )
throws ValueSerializationException
{
return serialize( new Options(), object );
}
@Override
public final String serialize( Options options, Object object )
throws ValueSerializationException
{
try
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
serializeRoot( options, object, output );
return output.toString( UTF_8 );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not serialize value", ex );
}
}
@Override
@Deprecated
public final String serialize( Object object, boolean includeTypeInfo )
throws ValueSerializationException
{
return serialize( includeTypeInfo ? new Options().withTypeInfo() : new Options().withoutTypeInfo(),
object );
}
@Override
public final void serialize( Object object, OutputStream output )
throws ValueSerializationException
{
serialize( new Options(), object, output );
}
@Override
public final void serialize( Options options, Object object, OutputStream output )
throws ValueSerializationException
{
try
{
serializeRoot( options, object, output );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not serialize value", ex );
}
}
@Override
@Deprecated
public final void serialize( Object object, OutputStream output, boolean includeTypeInfo )
throws ValueSerializationException
{
serialize( includeTypeInfo ? new Options().withTypeInfo() : new Options().withoutTypeInfo(),
object, output );
}
private void serializeRoot( Options options, Object object, OutputStream output )
throws Exception
{
if( object != null )
{
if( serializers.get( object.getClass() ) != null )
{
// Plain Value
Object serialized = serializers.get( object.getClass() ).map( options, object );
output.write( serialized.toString().getBytes( UTF_8 ) );
}
else if( object.getClass().isEnum() )
{
// Enum Value
output.write( object.toString().getBytes( UTF_8 ) );
}
else if( object.getClass().isArray() )
{
// Array Value
output.write( serializeBase64Serializable( object ).getBytes( UTF_8 ) );
}
else
{
// Complex Value
OutputType adaptedOutput = adaptOutput( output );
onSerializationStart( object, adaptedOutput );
doSerialize( options, object, adaptedOutput, true );
onSerializationEnd( object, adaptedOutput );
}
}
}
private void doSerialize( Options options, Object object, OutputType output, boolean rootPass )
throws Exception
{
// Null
if( object == null )
{
onValue( output, null );
}
else // Registered serializer
if( serializers.get( object.getClass() ) != null )
{
onValue( output, serializers.get( object.getClass() ).map( options, object ) );
}
else if( complexSerializers.get( object.getClass() ) != null )
{
complexSerializers.get( object.getClass() ).serialize( options, object, output );
}
else // ValueComposite
if( ValueComposite.class.isAssignableFrom( object.getClass() ) )
{
serializeValueComposite( options, object, output, rootPass );
}
else // EntityComposite
if( EntityComposite.class.isAssignableFrom( object.getClass() ) )
{
serializeEntityComposite( object, output );
}
else // Collection - Iterable
if( Iterable.class.isAssignableFrom( object.getClass() ) )
{
serializeIterable( options, object, output );
}
else // Array - QUID Remove this and use java serialization for arrays?
if( object.getClass().isArray() )
{
serializeBase64Serializable( object, output );
}
else // Map
if( Map.class.isAssignableFrom( object.getClass() ) )
{
serializeMap( options, object, output );
}
else // Enum
if( object.getClass().isEnum() )
{
onValue( output, object.toString() );
}
else // Fallback to Base64 encoded Java Serialization
{
serializeBase64Serializable( object, output );
}
}
private void serializeValueComposite( Options options, Object object, OutputType output, boolean rootPass )
throws Exception
{
CompositeInstance valueInstance = Qi4j.FUNCTION_COMPOSITE_INSTANCE_OF.map( (ValueComposite) object );
ValueDescriptor descriptor = (ValueDescriptor) valueInstance.descriptor();
AssociationStateHolder state = (AssociationStateHolder) valueInstance.state();
onObjectStart( output );
//noinspection ConstantConditions
if( options.getBoolean( Options.INCLUDE_TYPE_INFO ) && !rootPass )
{
onFieldStart( output, "_type" );
onValueStart( output );
onValue( output, first( descriptor.valueType().types() ).getName() );
onValueEnd( output );
onFieldEnd( output );
}
for( PropertyDescriptor persistentProperty : descriptor.valueType().properties() )
{
Property<?> property = state.propertyFor( persistentProperty.accessor() );
onFieldStart( output, persistentProperty.qualifiedName().name() );
onValueStart( output );
doSerialize( options, property.get(), output, false );
onValueEnd( output );
onFieldEnd( output );
}
for( AssociationDescriptor associationDescriptor : descriptor.valueType().associations() )
{
Association<?> association = state.associationFor( associationDescriptor.accessor() );
onFieldStart( output, associationDescriptor.qualifiedName().name() );
onValueStart( output );
EntityReference ref = association.reference();
if( ref == null )
{
onValue( output, null );
}
else
{
onValue( output, ref.identity() );
}
onValueEnd( output );
onFieldEnd( output );
}
for( AssociationDescriptor associationDescriptor : descriptor.valueType().manyAssociations() )
{
ManyAssociation<?> manyAssociation = state.manyAssociationFor( associationDescriptor.accessor() );
onFieldStart( output, associationDescriptor.qualifiedName().name() );
onValueStart( output );
onArrayStart( output );
for( EntityReference ref : manyAssociation.references() )
{
onValueStart( output );
onValue( output, ref.identity() );
onValueEnd( output );
}
onArrayEnd( output );
onValueEnd( output );
onFieldEnd( output );
}
for( AssociationDescriptor associationDescriptor : descriptor.valueType().namedAssociations() )
{
NamedAssociation<?> namedAssociation = state.namedAssociationFor( associationDescriptor.accessor() );
onFieldStart( output, associationDescriptor.qualifiedName().name() );
onValueStart( output );
onObjectStart( output );
for( String name : namedAssociation )
{
onFieldStart( output, name );
onValueStart( output );
EntityReference ref = namedAssociation.referenceOf( name );
onValue( output, ref.identity() );
onValueEnd( output );
onFieldEnd( output );
}
onObjectEnd( output );
onValueEnd( output );
onFieldEnd( output );
}
onObjectEnd( output );
}
private void serializeEntityComposite( Object object, OutputType output )
throws Exception
{
onValue( output, EntityReference.entityReferenceFor( object ) );
}
private void serializeIterable( Options options, Object object, OutputType output )
throws Exception
{
@SuppressWarnings( "unchecked" )
Iterable<Object> collection = (Iterable<Object>) object;
onArrayStart( output );
for( Object item : collection )
{
onValueStart( output );
doSerialize( options, item, output, false );
onValueEnd( output );
}
onArrayEnd( output );
}
private void serializeMap( Options options, Object object, OutputType output )
throws Exception
{
@SuppressWarnings( "unchecked" )
Map<Object, Object> map = (Map<Object, Object>) object;
if( options.getBoolean( Options.MAP_ENTRIES_AS_OBJECTS ) )
{
onObjectStart( output );
for( Map.Entry<Object, Object> entry : map.entrySet() )
{
onFieldStart( output, entry.getKey().toString() );
onValueStart( output );
doSerialize( options, entry.getValue(), output, false );
onValueEnd( output );
onFieldEnd( output );
}
onObjectEnd( output );
}
else
{
onArrayStart( output );
for( Map.Entry<Object, Object> entry : map.entrySet() )
{
onObjectStart( output );
onFieldStart( output, "key" );
onValueStart( output );
onValue( output, entry.getKey().toString() );
onValueEnd( output );
onFieldEnd( output );
onFieldStart( output, "value" );
onValueStart( output );
doSerialize( options, entry.getValue(), output, false );
onValueEnd( output );
onFieldEnd( output );
onObjectEnd( output );
}
onArrayEnd( output );
}
}
private void serializeBase64Serializable( Object object, OutputType output )
throws Exception
{
onValue( output, serializeBase64Serializable( object ) );
}
private String serializeBase64Serializable( Object object )
throws Exception
{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream( bout ))
{
out.writeUnshared( object );
}
byte[] bytes = Base64Encoder.encode( bout.toByteArray(), true );
return new String( bytes, UTF_8 );
}
protected abstract OutputType adaptOutput( OutputStream output )
throws Exception;
protected void onSerializationStart( Object object, OutputType output )
throws Exception
{
// NOOP
}
protected void onSerializationEnd( Object object, OutputType output )
throws Exception
{
// NOOP
}
protected abstract void onArrayStart( OutputType output )
throws Exception;
protected abstract void onArrayEnd( OutputType output )
throws Exception;
protected abstract void onObjectStart( OutputType output )
throws Exception;
protected abstract void onObjectEnd( OutputType output )
throws Exception;
protected abstract void onFieldStart( OutputType output, String fieldName )
throws Exception;
protected void onFieldEnd( OutputType output )
throws Exception
{
// NOOP
}
protected void onValueStart( OutputType output )
throws Exception
{
// NOOP
}
protected abstract void onValue( OutputType output, Object value )
throws Exception;
protected void onValueEnd( OutputType output )
throws Exception
{
// NOOP
}
}