/*
* Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
* Copyright (c) 2010, Niclas Hehdman. 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.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.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.Functions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.qi4j.functional.Iterables.*;
/**
* 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>
*
* @param <OutputType> Implementor output type
*/
public abstract class ValueSerializerAdapter<OutputType>
implements ValueSerializer
{
private static final Logger LOG = LoggerFactory.getLogger( ValueSerializerAdapter.class );
private static final String UTF_8 = "UTF-8";
private final Map<Class<?>, Function<Object, Object>> serializers = new HashMap<Class<?>, Function<Object, Object>>();
/**
* Register a Plain Value type serialization Function.
*
* @param <T> Plain Value parametrized Type
* @param type Plain Value Type
* @param deserializer Serialization Function
*/
@SuppressWarnings( "unchecked" )
protected final <T> void registerSerializer( Class<T> type, Function<T, Object> serializer )
{
serializers.put( type, (Function<Object, Object>) serializer );
}
public ValueSerializerAdapter()
{
// Primitive Value types
registerSerializer( String.class, Functions.<Object, String>identity() );
registerSerializer( Character.class, Functions.<Object, Character>identity() );
registerSerializer( Boolean.class, Functions.<Object, Boolean>identity() );
registerSerializer( Integer.class, Functions.<Object, Integer>identity() );
registerSerializer( Long.class, Functions.<Object, Long>identity() );
registerSerializer( Short.class, Functions.<Object, Short>identity() );
registerSerializer( Byte.class, Functions.<Object, Byte>identity() );
registerSerializer( Float.class, Functions.<Object, Float>identity() );
registerSerializer( Double.class, Functions.<Object, Double>identity() );
// Number types
registerSerializer( BigDecimal.class, new Function<BigDecimal, Object>()
{
@Override
public Object map( BigDecimal bigDecimal )
{
return bigDecimal.toString();
}
} );
registerSerializer( BigInteger.class, new Function<BigInteger, Object>()
{
@Override
public Object map( BigInteger bigInteger )
{
return bigInteger.toString();
}
} );
// Date types
registerSerializer( Date.class, new Function<Date, Object>()
{
@Override
public Object map( Date date )
{
return Dates.toUtcString( date );
}
} );
registerSerializer( DateTime.class, new Function<DateTime, Object>()
{
@Override
public Object map( DateTime date )
{
return date.toString();
}
} );
registerSerializer( LocalDateTime.class, new Function<LocalDateTime, Object>()
{
@Override
public Object map( LocalDateTime date )
{
return date.toString();
}
} );
registerSerializer( LocalDate.class, new Function<LocalDate, Object>()
{
@Override
public Object map( LocalDate date )
{
return date.toString();
}
} );
// Other supported types
registerSerializer( EntityReference.class, new Function<EntityReference, Object>()
{
@Override
public Object map( 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 boolean includeTypeInfo )
{
return new Function<T, String>()
{
@Override
public String map( T object )
{
return serialize( object, includeTypeInfo );
}
};
}
@Override
public final String serialize( Object object )
throws ValueSerializationException
{
return serialize( object, true );
}
@Override
public final String serialize( Object object, boolean includeTypeInfo )
throws ValueSerializationException
{
try
{
ByteArrayOutputStream output = new ByteArrayOutputStream();
serializeRoot( object, output, includeTypeInfo );
return output.toString( UTF_8 );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not serialize value", ex );
}
}
@Override
public final void serialize( Object object, OutputStream output )
throws ValueSerializationException
{
serialize( object, output, true );
}
@Override
public final void serialize( Object object, OutputStream output, boolean includeTypeInfo )
throws ValueSerializationException
{
try
{
serializeRoot( object, output, includeTypeInfo );
}
catch( ValueSerializationException ex )
{
throw ex;
}
catch( Exception ex )
{
throw new ValueSerializationException( "Could not serialize value", ex );
}
}
private void serializeRoot( Object object, OutputStream output, boolean includeTypeInfo )
throws Exception
{
if( object != null )
{
// System.out.println( ">>>>>>>>>>>> " + ( object == null ? "null" : object.getClass() ) );
if( serializers.get( object.getClass() ) != null )
{
// Plain Value
Object serialized = serializers.get( object.getClass() ).map( 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( object, adaptedOutput, includeTypeInfo, true );
onSerializationEnd( object, adaptedOutput );
}
}
}
private void doSerialize( Object object, OutputType output, boolean includeTypeInfo, boolean rootPass )
throws Exception
{
// Null
if( object == null )
{
LOG.trace( "Null object -> onValue( null )" );
onValue( output, null );
}
else // Registered serializer
if( serializers.get( object.getClass() ) != null )
{
LOG.trace( "Registered serializer matches -> onValue( serialized )" );
onValue( output, serializers.get( object.getClass() ).map( object ) );
}
else // ValueComposite
if( ValueComposite.class.isAssignableFrom( object.getClass() ) )
{
LOG.trace( "ValueComposite assignable -> serializeValueComposite( object )" );
serializeValueComposite( object, output, includeTypeInfo, rootPass );
}
else // EntityComposite
if( EntityComposite.class.isAssignableFrom( object.getClass() ) )
{
LOG.trace( "EntityComposite assignable -> serializeEntityComposite( object )" );
serializeEntityComposite( object, output );
}
else // Collection - Iterable
if( Iterable.class.isAssignableFrom( object.getClass() ) )
{
LOG.trace( "Iterable assignable -> serializeIterable( object )" );
serializeIterable( object, output, includeTypeInfo );
}
else // Array - QUID Remove this and use java serialization for arrays?
if( object.getClass().isArray() )
{
LOG.trace( "Object isArray -> serializeBase64Serializable( object )" );
serializeBase64Serializable( object, output );
}
else // Map
if( Map.class.isAssignableFrom( object.getClass() ) )
{
LOG.trace( "Map assignable -> serializeMap( object )" );
serializeMap( object, output, includeTypeInfo );
}
else // Enum
if( object.getClass().isEnum() )
{
LOG.trace( "Object is an enum -> onValue( object.toString() )" );
onValue( output, object.toString() );
}
else // Fallback to Base64 encoded Java Serialization
{
LOG.trace( "Unknown object type -> serializeBase64Serializable( object )" );
serializeBase64Serializable( object, output );
}
}
private void serializeValueComposite( Object object, OutputType output, boolean includeTypeInfo, 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 );
if( includeTypeInfo && !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( property.get(), output, includeTypeInfo, false );
onValueEnd( output );
onFieldEnd( output );
}
for( AssociationDescriptor associationDescriptor : descriptor.valueType().associations() )
{
Association<?> association = state.associationFor( associationDescriptor.accessor() );
Object instance = association.get();
onFieldStart( output, associationDescriptor.qualifiedName().name() );
onValueStart( output );
if( instance == null )
{
onValue( output, null );
}
else
{
onValue( output, ( (Identity) instance ).identity().get() );
}
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( Object instance : manyAssociation )
{
onValueStart( output );
onValue( output, ( (Identity) instance ).identity().get() );
onValueEnd( output );
}
onArrayEnd( output );
onValueEnd( output );
onFieldEnd( output );
}
onObjectEnd( output );
}
private void serializeEntityComposite( Object object, OutputType output )
throws Exception
{
onValue( output, EntityReference.entityReferenceFor( object ) );
}
private void serializeIterable( Object object, OutputType output, boolean includeTypeInfo )
throws Exception
{
@SuppressWarnings( "unchecked" )
Iterable<Object> collection = (Iterable<Object>) object;
onArrayStart( output );
for( Object item : collection )
{
onValueStart( output );
doSerialize( item, output, includeTypeInfo, false );
onValueEnd( output );
}
onArrayEnd( output );
}
private void serializeMap( Object object, OutputType output, boolean includeTypeInfo )
throws Exception
{
@SuppressWarnings( "unchecked" )
Map<Object, Object> map = (Map<Object, Object>) object;
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( entry.getValue(), output, includeTypeInfo, 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();
ObjectOutputStream out = new ObjectOutputStream( bout );
out.writeUnshared( object );
out.close();
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
}
}