| /* |
| * 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 |
| } |
| } |