polygene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From paulmer...@apache.org
Subject [44/50] [abbrv] polygene-java git commit: New (de)serialization API and SPI & new implementations
Date Sun, 26 Feb 2017 22:48:06 GMT
http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java
new file mode 100644
index 0000000..5450ec9
--- /dev/null
+++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSerializer.java
@@ -0,0 +1,216 @@
+/*
+ *  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.serialization.javaxjson;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.UncheckedIOException;
+import java.util.Base64;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import org.apache.polygene.api.PolygeneAPI;
+import org.apache.polygene.api.association.AssociationStateHolder;
+import org.apache.polygene.api.composite.CompositeInstance;
+import org.apache.polygene.api.injection.scope.This;
+import org.apache.polygene.api.injection.scope.Uses;
+import org.apache.polygene.api.service.ServiceDescriptor;
+import org.apache.polygene.api.type.EnumType;
+import org.apache.polygene.api.type.MapType;
+import org.apache.polygene.api.type.ValueCompositeType;
+import org.apache.polygene.api.type.ValueType;
+import org.apache.polygene.api.value.ValueComposite;
+import org.apache.polygene.api.value.ValueDescriptor;
+import org.apache.polygene.spi.serialization.AbstractTextSerializer;
+import org.apache.polygene.spi.serialization.JsonSerializer;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
+import static org.apache.polygene.api.util.Collectors.toMap;
+
+public class JavaxJsonSerializer extends AbstractTextSerializer implements JsonSerializer
+{
+    @This
+    private JavaxJsonAdapters adapters;
+
+    @Uses
+    private ServiceDescriptor descriptor;
+
+    @Override
+    public <T> Function<T, JsonValue> toJsonFunction( Options options )
+    {
+        return object -> doSerialize( options, object, true );
+    }
+
+    private JsonValue doSerialize( Options options, Object object, boolean root )
+    {
+        if( object == null )
+        {
+            return JsonValue.NULL;
+        }
+        Class<?> objectClass = object.getClass();
+        JavaxJsonAdapter<?> adapter = adapters.adapterFor( objectClass );
+        if( adapter != null )
+        {
+            return adapter.serialize( object, obj -> doSerialize( options, obj, false ) );
+        }
+        if( EnumType.isEnum( objectClass ) )
+        {
+            return JavaxJson.toJsonString( object.toString() );
+        }
+        if( ValueCompositeType.isValueComposite( objectClass ) )
+        {
+            return serializeValueComposite( options, object, root );
+        }
+        if( MapType.isMap( objectClass ) )
+        {
+            return serializeMap( options, (Map<?, ?>) object );
+        }
+        if( Iterable.class.isAssignableFrom( objectClass ) )
+        {
+            return serializeIterable( options, (Iterable<?>) object );
+        }
+        if( Stream.class.isAssignableFrom( objectClass ) )
+        {
+            return serializeStream( options, (Stream<?>) object );
+        }
+        // Fallback to Java Serialization in Base 64
+        // Include all arrays!
+        return serializeBase64( object );
+    }
+
+    private JsonObject serializeValueComposite( Options options, Object composite, boolean root )
+    {
+        CompositeInstance instance = PolygeneAPI.FUNCTION_COMPOSITE_INSTANCE_OF.apply( (ValueComposite) composite );
+        ValueDescriptor descriptor = (ValueDescriptor) instance.descriptor();
+        AssociationStateHolder state = (AssociationStateHolder) instance.state();
+        ValueCompositeType valueType = descriptor.valueType();
+
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        valueType.properties().forEach(
+            property -> builder.add(
+                property.qualifiedName().name(),
+                doSerialize( options, state.propertyFor( property.accessor() ).get(), false ) ) );
+        valueType.associations().forEach(
+            association -> builder.add(
+                association.qualifiedName().name(),
+                doSerialize( options, state.associationFor( association.accessor() ).reference(), false ) ) );
+        valueType.manyAssociations().forEach(
+            association -> builder.add(
+                association.qualifiedName().name(),
+                doSerialize( options, state.manyAssociationFor( association.accessor() ).references()
+                                           .collect( toList() ),
+                             false ) ) );
+        valueType.namedAssociations().forEach(
+            association -> builder.add(
+                association.qualifiedName().name(),
+                doSerialize( options,
+                             state.namedAssociationFor( association.accessor() ).references()
+                                  .collect( toMap() ),
+                             false ) ) );
+        if( !root && options.includeTypeInfo() )
+        {
+            withTypeInfo( builder, valueType );
+        }
+        return builder.build();
+    }
+
+    private JsonObjectBuilder withTypeInfo( JsonObjectBuilder builder, ValueType valueType )
+    {
+        return builder.add( getTypeInfoPropertyName(), valueType.primaryType().getName() );
+    }
+
+    /**
+     * Map serialization.
+     *
+     * {@literal Map<String, ?>} are serialized to a {@literal JsonObject}.
+     * {@literal Map<?, ?>} are serialized to a {@literal JsonArray} or key/value {@literal JsonObject}s.
+     * Empty maps are serialized to an empty {@literal JsonObject}.
+     */
+    private JsonValue serializeMap( Options options, Map<?, ?> map )
+    {
+        if( map.isEmpty() )
+        {
+            // Defaults to {}
+            return Json.createObjectBuilder().build();
+        }
+        Predicate<Object> characterKeyPredicate = key ->
+            key != null && ( key instanceof CharSequence || key instanceof Character );
+        if( map.keySet().stream().allMatch( characterKeyPredicate ) )
+        {
+            JsonObjectBuilder builder = Json.createObjectBuilder();
+            map.entrySet().forEach( entry -> builder.add( entry.getKey().toString(),
+                                                          doSerialize( options, entry.getValue(), false ) ) );
+            return builder.build();
+        }
+        else
+        {
+            JsonArrayBuilder builder = Json.createArrayBuilder();
+            map.entrySet().forEach(
+                entry -> builder.add(
+                    Json.createObjectBuilder()
+                        .add( "key", doSerialize( options, entry.getKey(), false ) )
+                        .add( "value", doSerialize( options, entry.getValue(), false ) )
+                        .build() ) );
+            return builder.build();
+        }
+    }
+
+    private JsonArray serializeIterable( Options options, Iterable<?> iterable )
+    {
+        return serializeStream( options, StreamSupport.stream( iterable.spliterator(), false ) );
+    }
+
+    private <T> JsonArray serializeStream( Options options, Stream<?> stream )
+    {
+        JsonArrayBuilder builder = Json.createArrayBuilder();
+        stream.forEach( element -> builder.add( doSerialize( options, element, false ) ) );
+        return builder.build();
+    }
+
+    private JsonString serializeBase64( Object object )
+    {
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        try( ObjectOutputStream out = new ObjectOutputStream( bout ) )
+        {
+            out.writeUnshared( object );
+            byte[] bytes = Base64.getEncoder().encode( bout.toByteArray() );
+            return JavaxJson.toJsonString( new String( bytes, UTF_8 ) );
+        }
+        catch( IOException ex )
+        {
+            throw new UncheckedIOException( ex );
+        }
+    }
+
+    private String getTypeInfoPropertyName()
+    {
+        return JavaxJsonSettings.orDefault( descriptor.metaInfo( JavaxJsonSettings.class ) )
+                                .getTypeInfoPropertyName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java
new file mode 100644
index 0000000..266bd99
--- /dev/null
+++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/JavaxJsonSettings.java
@@ -0,0 +1,73 @@
+/*
+ *  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.serialization.javaxjson;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.polygene.api.type.ValueType;
+
+public class JavaxJsonSettings
+{
+    public static final JavaxJsonSettings DEFAULT = new JavaxJsonSettings();
+
+    public static JavaxJsonSettings orDefault( JavaxJsonSettings settings )
+    {
+        return settings != null ? settings : DEFAULT;
+    }
+
+    private String typeInfoPropertyName;
+    private Map<ValueType, JavaxJsonAdapter<?>> adapters;
+
+    public JavaxJsonSettings()
+    {
+        typeInfoPropertyName = "_type";
+        adapters = new LinkedHashMap<>();
+    }
+
+    public String getTypeInfoPropertyName()
+    {
+        return typeInfoPropertyName;
+    }
+
+    public void setTypeInfoPropertyName( String typeInfoPropertyName )
+    {
+        this.typeInfoPropertyName = typeInfoPropertyName;
+    }
+
+    public Map<ValueType, JavaxJsonAdapter<?>> getAdapters()
+    {
+        return adapters;
+    }
+
+    public JavaxJsonSettings withTypeInfoPropertyName( String typeInfoPropertyName )
+    {
+        setTypeInfoPropertyName( typeInfoPropertyName );
+        return this;
+    }
+
+    public JavaxJsonSettings withJsonAdapter( ValueType valueType, JavaxJsonAdapter<?> adapter )
+    {
+        getAdapters().put( valueType, adapter );
+        return this;
+    }
+
+    public JavaxJsonSettings withJsonAdapter( JavaxJsonAdapter<?> adapter )
+    {
+        return withJsonAdapter( ValueType.of( adapter.type() ), adapter );
+    }
+}

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html
new file mode 100644
index 0000000..43db1d9
--- /dev/null
+++ b/core/spi/src/main/java/org/apache/polygene/serialization/javaxjson/package.html
@@ -0,0 +1,24 @@
+<!--
+  ~  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.
+  ~
+  ~
+  -->
+<html>
+    <body>
+        <h2>javax.json Serialization.</h2>
+    </body>
+</html>

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java
index 6bbef04..e4b5c05 100644
--- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java
+++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONEntityState.java
@@ -14,28 +14,32 @@
  *  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.spi.entitystore.helpers;
 
 import java.time.Instant;
+import java.util.Map;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
 import org.apache.polygene.api.common.QualifiedName;
 import org.apache.polygene.api.entity.EntityDescriptor;
 import org.apache.polygene.api.entity.EntityReference;
 import org.apache.polygene.api.property.PropertyDescriptor;
+import org.apache.polygene.api.serialization.SerializationException;
 import org.apache.polygene.api.structure.ModuleDescriptor;
 import org.apache.polygene.api.type.ValueType;
-import org.apache.polygene.api.value.ValueSerialization;
-import org.apache.polygene.api.value.ValueSerializationException;
+import org.apache.polygene.api.value.ValueDescriptor;
+import org.apache.polygene.serialization.javaxjson.JavaxJson;
 import org.apache.polygene.spi.entity.EntityState;
 import org.apache.polygene.spi.entity.EntityStatus;
 import org.apache.polygene.spi.entity.ManyAssociationState;
 import org.apache.polygene.spi.entity.NamedAssociationState;
 import org.apache.polygene.spi.entitystore.EntityStoreException;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import org.apache.polygene.spi.serialization.JsonSerialization;
 
 /**
  * Standard JSON implementation of EntityState.
@@ -43,7 +47,6 @@ import org.json.JSONObject;
 public final class JSONEntityState
     implements EntityState
 {
-    private static final String[] EMPTY_NAMES = new String[ 0 ];
     private static final String[] CLONE_NAMES =
         {
             JSONKeys.IDENTITY,
@@ -54,27 +57,27 @@ public final class JSONEntityState
         };
 
     private final ModuleDescriptor module;
-    private final ValueSerialization valueSerialization;
     private final String version;
     private final EntityReference reference;
     private final EntityDescriptor entityDescriptor;
+    private final JsonSerialization serialization;
 
     private EntityStatus status;
     private Instant lastModified;
-    private JSONObject state;
+    private JsonObject state;
 
     /* package */ JSONEntityState( ModuleDescriptor module,
-                                   ValueSerialization valueSerialization,
+                                   JsonSerialization serialization,
                                    String version,
                                    Instant lastModified,
                                    EntityReference reference,
                                    EntityStatus status,
                                    EntityDescriptor entityDescriptor,
-                                   JSONObject state
+                                   JsonObject state
     )
     {
         this.module = module;
-        this.valueSerialization = valueSerialization;
+        this.serialization = serialization;
         this.version = version;
         this.lastModified = lastModified;
         this.reference = reference;
@@ -107,22 +110,45 @@ public final class JSONEntityState
     {
         try
         {
-            Object json = state.getJSONObject( JSONKeys.PROPERTIES ).opt( stateName.name() );
-            if( JSONObject.NULL.equals( json ) )
+            JsonValue json = state.getJsonObject( JSONKeys.PROPERTIES ).get( stateName.name() );
+            if( json == null || JsonValue.NULL.equals( json ) )
             {
                 return null;
             }
             else
             {
-                PropertyDescriptor descriptor = entityDescriptor.state().findPropertyModelByQualifiedName( stateName );
-                if( descriptor == null )
+                // TODO This rely on _type explicitely :(
+                // Needed because of this mess that is JsonEntityState
+                ValueType propertyValueType = null;
+                if( json.getValueType() == JsonValue.ValueType.OBJECT )
+                {
+                    String typeInfo = ( (JsonObject) json ).getString( "_type", null );
+                    if( typeInfo != null )
+                    {
+                        ValueDescriptor valueDescriptor = module.valueDescriptor( typeInfo );
+                        if( valueDescriptor != null )
+                        {
+                            propertyValueType = valueDescriptor.valueType();
+                        }
+                    }
+                }
+                if( propertyValueType == null )
+                {
+                    PropertyDescriptor descriptor = entityDescriptor.state()
+                                                                    .findPropertyModelByQualifiedName( stateName );
+                    if( descriptor != null )
+                    {
+                        propertyValueType = descriptor.valueType();
+                    }
+                }
+                if( propertyValueType == null )
                 {
                     return null;
                 }
-                return valueSerialization.deserialize( module, descriptor.valueType(), json.toString() );
+                return serialization.fromJson( module, propertyValueType, json );
             }
         }
-        catch( ValueSerializationException | JSONException e )
+        catch( SerializationException e )
         {
             throw new EntityStoreException( e );
         }
@@ -133,129 +159,44 @@ public final class JSONEntityState
     {
         try
         {
-            Object jsonValue;
-            if( newValue == null || ValueType.isPrimitiveValue( newValue ) )
-            {
-                jsonValue = newValue;
-            }
-            else if( ValueType.isIdentity( newValue ) )
-            {
-                jsonValue = newValue.toString();
-            }
-            else
-            {
-                String serialized = valueSerialization.serialize( newValue );
-                if( serialized.startsWith( "{" ) )
-                {
-                    jsonValue = new JSONObject( serialized );
-                }
-                else if( serialized.startsWith( "[" ) )
-                {
-                    jsonValue = new JSONArray( serialized );
-                }
-                else
-                {
-                    jsonValue = serialized;
-                }
-            }
-            cloneStateIfGlobalStateLoaded();
-            state.getJSONObject( JSONKeys.PROPERTIES ).put( stateName.name(), jsonValue );
+            JsonValue jsonValue = serialization.toJson( newValue );
+            stateCloneWithProperty( stateName.name(), jsonValue );
             markUpdated();
         }
-        catch( ValueSerializationException | JSONException e )
+        catch( SerializationException e )
         {
             throw new EntityStoreException( "Unable to set property " + stateName + " value " + newValue, e );
         }
     }
 
-    private JSONObject cloneJSON( JSONObject jsonObject )
-        throws JSONException
-    {
-        String[] names = JSONObject.getNames( jsonObject );
-        if( names == null )
-        {
-            names = EMPTY_NAMES;
-        }
-        return new JSONObject( jsonObject, names );
-    }
-
     @Override
     public EntityReference associationValueOf( QualifiedName stateName )
     {
-        try
-        {
-            Object jsonValue = state.getJSONObject( JSONKeys.ASSOCIATIONS ).opt( stateName.name() );
-            if( jsonValue == null )
-            {
-                return null;
-            }
-
-            EntityReference value = jsonValue == JSONObject.NULL
-                                    ? null
-                                    : EntityReference.parseEntityReference( (String) jsonValue );
-            return value;
-        }
-        catch( JSONException e )
+        String jsonValue = state.getJsonObject( JSONKeys.ASSOCIATIONS ).getString( stateName.name(), null );
+        if( jsonValue == null )
         {
-            throw new EntityStoreException( e );
+            return null;
         }
+        return EntityReference.parseEntityReference( jsonValue );
     }
 
     @Override
     public void setAssociationValue( QualifiedName stateName, EntityReference newEntity )
     {
-        try
-        {
-            cloneStateIfGlobalStateLoaded();
-            state.getJSONObject( JSONKeys.ASSOCIATIONS ).put( stateName.name(), newEntity == null
-                                                                                ? null
-                                                                                : newEntity.identity().toString() );
-            markUpdated();
-        }
-        catch( JSONException e )
-        {
-            throw new EntityStoreException( e );
-        }
+        stateCloneWithAssociation( stateName.name(), newEntity );
+        markUpdated();
     }
 
     @Override
     public ManyAssociationState manyAssociationValueOf( QualifiedName stateName )
     {
-        try
-        {
-            JSONObject manyAssociations = state.getJSONObject( JSONKeys.MANY_ASSOCIATIONS );
-            JSONArray jsonValues = manyAssociations.optJSONArray( stateName.name() );
-            if( jsonValues == null )
-            {
-                jsonValues = new JSONArray();
-                manyAssociations.put( stateName.name(), jsonValues );
-            }
-            return new JSONManyAssociationState( this, jsonValues );
-        }
-        catch( JSONException e )
-        {
-            throw new EntityStoreException( e );
-        }
+        return new JSONManyAssociationState( this, stateName.name() );
     }
 
     @Override
     public NamedAssociationState namedAssociationValueOf( QualifiedName stateName )
     {
-        try
-        {
-            JSONObject namedAssociations = state.getJSONObject( JSONKeys.NAMED_ASSOCIATIONS );
-            JSONObject jsonValues = namedAssociations.optJSONObject( stateName.name() );
-            if( jsonValues == null )
-            {
-                jsonValues = new JSONObject();
-                namedAssociations.put( stateName.name(), jsonValues );
-            }
-            return new JSONNamedAssociationState( this, jsonValues );
-        }
-        catch( JSONException e )
-        {
-            throw new EntityStoreException( e );
-        }
+        return new JSONNamedAssociationState( this, stateName.name() );
     }
 
     @Override
@@ -282,7 +223,7 @@ public final class JSONEntityState
         return entityDescriptor;
     }
 
-    public JSONObject state()
+    public JsonObject state()
     {
         return state;
     }
@@ -293,7 +234,7 @@ public final class JSONEntityState
         return reference + "(" + state + ")";
     }
 
-    public void markUpdated()
+    void markUpdated()
     {
         if( status == EntityStatus.LOADED )
         {
@@ -301,29 +242,217 @@ public final class JSONEntityState
         }
     }
 
-    void cloneStateIfGlobalStateLoaded()
+    void stateCloneWithVersionAndModified( String version, Instant lastModified )
+    {
+        JsonObjectBuilder builder = JavaxJson.toBuilder( state );
+        builder.add( JSONKeys.VERSION, version );
+        builder.add( JSONKeys.MODIFIED, lastModified.toEpochMilli() );
+        state = builder.build();
+    }
+
+    void stateCloneWithProperty( String stateName, JsonValue value )
     {
-        if( status != EntityStatus.LOADED )
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObjectBuilder propertiesBuilder = JavaxJson.toBuilder( state.getJsonObject( JSONKeys.PROPERTIES ) );
+        if( value == null )
+        {
+            propertiesBuilder.add( stateName, JsonValue.NULL );
+        }
+        else
         {
-            return;
+            propertiesBuilder.add( stateName, value );
         }
+        builder.add( JSONKeys.PROPERTIES, propertiesBuilder.build() );
+        builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) );
+        state = builder.build();
+    }
 
-        try
+    void stateCloneWithAssociation( String stateName, EntityReference ref )
+    {
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObjectBuilder assocBuilder = JavaxJson.toBuilder( state.getJsonObject( JSONKeys.ASSOCIATIONS ) );
+        if( ref == null )
         {
-            JSONObject newProperties = cloneJSON( state.getJSONObject( JSONKeys.PROPERTIES ) );
-            JSONObject newAssoc = cloneJSON( state.getJSONObject( JSONKeys.ASSOCIATIONS ) );
-            JSONObject newManyAssoc = cloneJSON( state.getJSONObject( JSONKeys.MANY_ASSOCIATIONS ) );
-            JSONObject newNamedAssoc = cloneJSON( state.getJSONObject( JSONKeys.NAMED_ASSOCIATIONS ) );
-            JSONObject stateClone = new JSONObject( state, CLONE_NAMES );
-            stateClone.put( JSONKeys.PROPERTIES, newProperties );
-            stateClone.put( JSONKeys.ASSOCIATIONS, newAssoc );
-            stateClone.put( JSONKeys.MANY_ASSOCIATIONS, newManyAssoc );
-            stateClone.put( JSONKeys.NAMED_ASSOCIATIONS, newNamedAssoc );
-            state = stateClone;
+            assocBuilder.add( stateName, JsonValue.NULL );
         }
-        catch( JSONException e )
+        else
         {
-            throw new EntityStoreException( e );
+            assocBuilder.add( stateName, ref.identity().toString() );
+        }
+        builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) );
+        builder.add( JSONKeys.ASSOCIATIONS, assocBuilder.build() );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) );
+        state = builder.build();
+    }
+
+    void stateCloneAddManyAssociation( int idx, String stateName, EntityReference ref )
+    {
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObjectBuilder manyAssociations = Json.createObjectBuilder();
+        JsonObject previousManyAssociations = state.getJsonObject( JSONKeys.MANY_ASSOCIATIONS );
+        for( Map.Entry<String, JsonValue> previousManyAssociation : previousManyAssociations.entrySet() )
+        {
+            String key = previousManyAssociation.getKey();
+            if( !key.equals( stateName ) )
+            {
+                manyAssociations.add( key, previousManyAssociation.getValue() );
+            }
+        }
+        JsonValue previousReferences = previousManyAssociations.get( stateName );
+        JsonArrayBuilder references = Json.createArrayBuilder();
+        String newRef = ref.identity().toString();
+        if( previousReferences == null || previousReferences.getValueType() != JsonValue.ValueType.ARRAY )
+        {
+            references.add( newRef );
+        }
+        else
+        {
+            JsonArray previousReferencesArray = (JsonArray) previousReferences;
+            boolean insert = !previousReferencesArray.contains( newRef );
+            for( int i = 0; i < previousReferencesArray.size(); i++ )
+            {
+                if( insert && i == idx )
+                {
+                    references.add( newRef );
+                }
+                references.add( previousReferencesArray.getString( i ) );
+            }
+            if( insert && idx >= previousReferencesArray.size() )
+            {
+                references.add( newRef );
+            }
+        }
+        manyAssociations.add( stateName, references.build() );
+        builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) );
+        builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, manyAssociations.build() );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) );
+        state = builder.build();
+    }
+
+    void stateCloneRemoveManyAssociation( String stateName, EntityReference ref )
+    {
+        String stringRef = ref.identity().toString();
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObjectBuilder manyAssociations = Json.createObjectBuilder();
+        JsonObject previousManyAssociations = state.getJsonObject( JSONKeys.MANY_ASSOCIATIONS );
+        for( Map.Entry<String, JsonValue> previousManyAssociation : previousManyAssociations.entrySet() )
+        {
+            String key = previousManyAssociation.getKey();
+            if( !key.equals( stateName ) )
+            {
+                manyAssociations.add( key, previousManyAssociation.getValue() );
+            }
+        }
+        JsonValue previousReferences = previousManyAssociations.get( stateName );
+        JsonArrayBuilder references = Json.createArrayBuilder();
+        if( previousReferences != null && previousReferences.getValueType() == JsonValue.ValueType.ARRAY )
+        {
+            JsonArray previousReferencesArray = (JsonArray) previousReferences;
+            for( int idx = 0; idx < previousReferencesArray.size(); idx++ )
+            {
+                String previousRef = previousReferencesArray.getString( idx );
+                if( !stringRef.equals( previousRef ) )
+                {
+                    references.add( previousRef );
+                }
+            }
+        }
+        manyAssociations.add( stateName, references.build() );
+        builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) );
+        builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, manyAssociations.build() );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, state.get( JSONKeys.NAMED_ASSOCIATIONS ) );
+        state = builder.build();
+    }
+
+    void stateCloneAddNamedAssociation( String stateName, String name, EntityReference ref )
+    {
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObject previousNamedAssociations = state.getJsonObject( JSONKeys.NAMED_ASSOCIATIONS );
+        JsonObjectBuilder namedAssociations = Json.createObjectBuilder();
+        for( Map.Entry<String, JsonValue> previousNamedAssociation : previousNamedAssociations.entrySet() )
+        {
+            String key = previousNamedAssociation.getKey();
+            if( !key.equals( stateName ) )
+            {
+                namedAssociations.add( key, previousNamedAssociation.getValue() );
+            }
+        }
+        JsonValue previousReferences = previousNamedAssociations.get( stateName );
+        JsonObjectBuilder references = Json.createObjectBuilder();
+        String newRef = ref.identity().toString();
+        if( previousReferences == null || !( previousReferences instanceof JsonObject ) )
+        {
+            references.add( name, newRef );
+        }
+        else
+        {
+            JsonObject previousReferencesObject = (JsonObject) previousReferences;
+            for( Map.Entry<String, JsonValue> previousNamedReference : previousReferencesObject.entrySet() )
+            {
+                String key = previousNamedReference.getKey();
+                if( !key.equals( name ) )
+                {
+                    references.add( key, previousNamedReference.getValue() );
+                }
+            }
+            references.add( name, ref.identity().toString() );
+        }
+        namedAssociations.add( stateName, references.build() );
+        builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) );
+        builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, namedAssociations.build() );
+        state = builder.build();
+    }
+
+    void stateCloneRemoveNamedAssociation( String stateName, String name )
+    {
+        JsonObjectBuilder builder = stateShallowClone();
+        JsonObjectBuilder namedAssociations = Json.createObjectBuilder();
+        JsonObject previousNamedAssociations = state.getJsonObject( JSONKeys.NAMED_ASSOCIATIONS );
+        for( Map.Entry<String, JsonValue> previousNamedAssociation : previousNamedAssociations.entrySet() )
+        {
+            String key = previousNamedAssociation.getKey();
+            if( !key.equals( stateName ) )
+            {
+                namedAssociations.add( key, previousNamedAssociation.getValue() );
+            }
+        }
+        JsonValue previousReferences = previousNamedAssociations.get( stateName );
+        JsonObjectBuilder references = Json.createObjectBuilder();
+        if( previousReferences != null && previousReferences.getValueType() == JsonValue.ValueType.OBJECT )
+        {
+            JsonObject previousReferencesObject = (JsonObject) previousReferences;
+            for( Map.Entry<String, JsonValue> previousNamedRef : previousReferencesObject.entrySet() )
+            {
+                String previousName = previousNamedRef.getKey();
+                if( !name.equals( previousName ) )
+                {
+                    references.add( previousName, previousNamedRef.getValue() );
+                }
+            }
+        }
+        namedAssociations.add( stateName, references.build() );
+        builder.add( JSONKeys.PROPERTIES, state.get( JSONKeys.PROPERTIES ) );
+        builder.add( JSONKeys.ASSOCIATIONS, state.get( JSONKeys.ASSOCIATIONS ) );
+        builder.add( JSONKeys.MANY_ASSOCIATIONS, state.get( JSONKeys.MANY_ASSOCIATIONS ) );
+        builder.add( JSONKeys.NAMED_ASSOCIATIONS, namedAssociations.build() );
+        state = builder.build();
+    }
+
+    private JsonObjectBuilder stateShallowClone()
+    {
+        JsonObjectBuilder builder = Json.createObjectBuilder();
+        for( String cloneName : CLONE_NAMES )
+        {
+            JsonValue cloneValue = state.get( cloneName );
+            builder.add( cloneName, cloneValue == null ? JsonValue.NULL : cloneValue );
         }
+        return builder;
     }
 }

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java
index b1efbc1..e9b99c4 100644
--- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java
+++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONManyAssociationState.java
@@ -21,39 +21,52 @@ package org.apache.polygene.spi.entitystore.helpers;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
-import org.json.JSONArray;
-import org.json.JSONException;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
 import org.apache.polygene.api.entity.EntityReference;
 import org.apache.polygene.spi.entity.ManyAssociationState;
 import org.apache.polygene.spi.entitystore.EntityStoreException;
 
 /**
  * JSON implementation of ManyAssociationState.
- * <p>Backed by a JSONArray.</p>
+ * <p>Backed by a JsonArray.</p>
  */
 public final class JSONManyAssociationState
     implements ManyAssociationState
 {
-
     private final JSONEntityState entityState;
-    private final JSONArray references;
+    private final String stateName;
 
-    public JSONManyAssociationState( JSONEntityState entityState, JSONArray references )
+    /* package */ JSONManyAssociationState( JSONEntityState entityState, String stateName )
     {
         this.entityState = entityState;
-        this.references = references;
+        this.stateName = stateName;
+    }
+
+    private JsonArray getReferences()
+    {
+        JsonObject manyAssociations = entityState.state().getJsonObject( JSONKeys.MANY_ASSOCIATIONS );
+        JsonValue references = manyAssociations.get( stateName );
+        if( references != null && references.getValueType() == JsonValue.ValueType.ARRAY )
+        {
+            return (JsonArray) references;
+        }
+        return Json.createArrayBuilder().build();
     }
 
     @Override
     public int count()
     {
-        return references.length();
+        return getReferences().size();
     }
 
     @Override
     public boolean contains( EntityReference entityReference )
     {
-        return indexOfReference( entityReference.toString() ) != -1;
+        return indexOfReference( entityReference.identity().toString() ) != -1;
     }
 
     @Override
@@ -65,12 +78,11 @@ public final class JSONManyAssociationState
             {
                 return false;
             }
-            entityState.cloneStateIfGlobalStateLoaded();
-            insertReference( idx, entityReference.identity().toString() );
+            entityState.stateCloneAddManyAssociation( idx, stateName, entityReference );
             entityState.markUpdated();
             return true;
         }
-        catch( JSONException e )
+        catch( JsonException e )
         {
             throw new EntityStoreException( e );
         }
@@ -82,8 +94,7 @@ public final class JSONManyAssociationState
         int refIndex = indexOfReference( entityReference.identity().toString() );
         if( refIndex != -1 )
         {
-            entityState.cloneStateIfGlobalStateLoaded();
-            references.remove( refIndex );
+            entityState.stateCloneRemoveManyAssociation( stateName, entityReference );
             entityState.markUpdated();
             return true;
         }
@@ -93,14 +104,7 @@ public final class JSONManyAssociationState
     @Override
     public EntityReference get( int i )
     {
-        try
-        {
-            return EntityReference.parseEntityReference( references.getString( i ) );
-        }
-        catch( JSONException e )
-        {
-            throw new EntityStoreException( e );
-        }
+        return EntityReference.parseEntityReference( getReferences().getString( i ) );
     }
 
     @Override
@@ -113,7 +117,7 @@ public final class JSONManyAssociationState
             @Override
             public boolean hasNext()
             {
-                return idx < references.length();
+                return idx < getReferences().size();
             }
 
             @Override
@@ -121,11 +125,11 @@ public final class JSONManyAssociationState
             {
                 try
                 {
-                    EntityReference ref = EntityReference.parseEntityReference( references.getString( idx ) );
+                    EntityReference ref = EntityReference.parseEntityReference( getReferences().getString( idx ) );
                     idx++;
                     return ref;
                 }
-                catch( JSONException e )
+                catch( JsonException e )
                 {
                     throw new NoSuchElementException();
                 }
@@ -142,49 +146,19 @@ public final class JSONManyAssociationState
     @Override
     public String toString()
     {
-        return references.toString();
+        return getReferences().toString();
     }
 
-    private int indexOfReference( String enityIdentityAsString )
+    private int indexOfReference( String entityIdentityAsString )
     {
-        for( int idx = 0; idx < references.length(); idx++ )
+        JsonArray references = getReferences();
+        for( int idx = 0; idx < references.size(); idx++ )
         {
-            if( enityIdentityAsString.equals( references.opt( idx ) ) )
+            if( entityIdentityAsString.equals( references.getString( idx, null ) ) )
             {
                 return idx;
             }
         }
         return -1;
     }
-
-    private void insertReference( int insert, Object item )
-        throws JSONException
-    {
-        if( insert < 0 || insert > references.length() )
-        {
-            throw new JSONException( "JSONArray[" + insert + "] is out of bounds." );
-        }
-        if( insert == references.length() )
-        {
-            // append
-            references.put( item );
-        }
-        else
-        {
-            // insert (copy/insert/apply)
-            JSONArray output = new JSONArray();
-            for( int idx = 0; idx < references.length(); idx++ )
-            {
-                if( idx == insert )
-                {
-                    output.put( item );
-                }
-                output.put( references.opt( idx ) );
-            }
-            for( int idx = 0; idx < output.length(); idx++ )
-            {
-                references.put( idx, output.opt( idx ) );
-            }
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONMapEntityStoreMixin.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONMapEntityStoreMixin.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONMapEntityStoreMixin.java
index 82d4c05..a7f7690 100644
--- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONMapEntityStoreMixin.java
+++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONMapEntityStoreMixin.java
@@ -14,8 +14,6 @@
  *  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.spi.entitystore.helpers;
 
@@ -24,12 +22,18 @@ import java.io.IOException;
 import java.io.ObjectInput;
 import java.io.ObjectOutput;
 import java.io.Reader;
+import java.io.StringReader;
 import java.io.Writer;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
+import java.util.function.Function;
 import java.util.stream.Stream;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonReader;
 import org.apache.polygene.api.cache.CacheOptions;
 import org.apache.polygene.api.common.Optional;
 import org.apache.polygene.api.entity.EntityDescriptor;
@@ -42,13 +46,11 @@ import org.apache.polygene.api.injection.scope.Structure;
 import org.apache.polygene.api.injection.scope.This;
 import org.apache.polygene.api.injection.scope.Uses;
 import org.apache.polygene.api.service.ServiceDescriptor;
-import org.apache.polygene.api.service.qualifier.Tagged;
 import org.apache.polygene.api.structure.Application;
 import org.apache.polygene.api.structure.ModuleDescriptor;
 import org.apache.polygene.api.unitofwork.NoSuchEntityTypeException;
 import org.apache.polygene.api.usecase.Usecase;
-import org.apache.polygene.api.value.ValueSerialization;
-import org.apache.polygene.spi.PolygeneSPI;
+import org.apache.polygene.serialization.javaxjson.JavaxJson;
 import org.apache.polygene.spi.cache.Cache;
 import org.apache.polygene.spi.cache.CachePool;
 import org.apache.polygene.spi.cache.NullCache;
@@ -60,9 +62,7 @@ import org.apache.polygene.spi.entitystore.EntityStoreException;
 import org.apache.polygene.spi.entitystore.EntityStoreSPI;
 import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork;
 import org.apache.polygene.spi.entitystore.StateCommitter;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONTokener;
+import org.apache.polygene.spi.serialization.JsonSerialization;
 
 /**
  * Implementation of EntityStore that works with an implementation of MapEntityStore.
@@ -82,14 +82,10 @@ public class JSONMapEntityStoreMixin
     private EntityStoreSPI entityStoreSpi;
 
     @Structure
-    private PolygeneSPI spi;
-
-    @Structure
     private Application application;
 
     @Service
-    @Tagged( ValueSerialization.Formats.JSON )
-    private ValueSerialization valueSerialization;
+    private JsonSerialization serialization;
 
     @Service
     IdentityGenerator identityGenerator;
@@ -107,7 +103,6 @@ public class JSONMapEntityStoreMixin
     private Cache<CacheState> cache;
 
     protected String uuid;
-    private int count;
 
     public JSONMapEntityStoreMixin()
     {
@@ -142,102 +137,109 @@ public class JSONMapEntityStoreMixin
     // EntityStore
 
     @Override
-    public EntityStoreUnitOfWork newUnitOfWork( ModuleDescriptor module, Usecase usecaseMetaInfo, Instant currentTime )
+    public EntityStoreUnitOfWork newUnitOfWork( ModuleDescriptor module, Usecase usecase, Instant currentTime )
     {
-        return new DefaultEntityStoreUnitOfWork( module, entityStoreSpi, newUnitOfWorkId(), usecaseMetaInfo, currentTime );
+        return new DefaultEntityStoreUnitOfWork( module, entityStoreSpi, newUnitOfWorkId(), usecase, currentTime );
     }
 
     // EntityStoreSPI
 
     @Override
-    public EntityState newEntityState( EntityStoreUnitOfWork unitOfWork,
+    public EntityState newEntityState( EntityStoreUnitOfWork uow,
                                        EntityReference reference,
                                        EntityDescriptor entityDescriptor
     )
     {
         try
         {
-            JSONObject state = new JSONObject();
-            state.put( JSONKeys.IDENTITY, reference.identity() );
-            state.put( JSONKeys.APPLICATION_VERSION, application.version() );
-            state.put( JSONKeys.TYPE, entityDescriptor.types().findFirst().get().getName() );
-            state.put( JSONKeys.VERSION, unitOfWork.identity() );
-            state.put( JSONKeys.MODIFIED, unitOfWork.currentTime().toEpochMilli() );
-            state.put( JSONKeys.PROPERTIES, new JSONObject() );
-            state.put( JSONKeys.ASSOCIATIONS, new JSONObject() );
-            state.put( JSONKeys.MANY_ASSOCIATIONS, new JSONObject() );
-            state.put( JSONKeys.NAMED_ASSOCIATIONS, new JSONObject() );
-            return new JSONEntityState( entityDescriptor.module(),
-                                        valueSerialization,
-                                        "",
-                                        unitOfWork.currentTime(),
+            JsonObjectBuilder builder = Json.createObjectBuilder();
+            builder.add( JSONKeys.IDENTITY, reference.identity().toString() );
+            builder.add( JSONKeys.APPLICATION_VERSION, application.version() );
+            builder.add( JSONKeys.TYPE, entityDescriptor.types().findFirst().get().getName() );
+            builder.add( JSONKeys.VERSION, uow.identity().toString() );
+            builder.add( JSONKeys.MODIFIED, uow.currentTime().toEpochMilli() );
+            builder.add( JSONKeys.PROPERTIES, Json.createObjectBuilder().build() );
+            builder.add( JSONKeys.ASSOCIATIONS, Json.createObjectBuilder().build() );
+            builder.add( JSONKeys.MANY_ASSOCIATIONS, Json.createObjectBuilder().build() );
+            builder.add( JSONKeys.NAMED_ASSOCIATIONS, Json.createObjectBuilder().build() );
+            JsonObject state = builder.build();
+            return new JSONEntityState( entityDescriptor.module(), serialization,
+                                        "", uow.currentTime(),
                                         reference,
-                                        EntityStatus.NEW,
-                                        entityDescriptor,
+                                        EntityStatus.NEW, entityDescriptor,
                                         state );
         }
-        catch( JSONException e )
+        catch( Exception e )
         {
             throw new EntityStoreException( e );
         }
     }
 
     @Override
-    public synchronized EntityState entityStateOf( EntityStoreUnitOfWork unitOfWork,
+    public synchronized EntityState entityStateOf( EntityStoreUnitOfWork uow,
                                                    ModuleDescriptor module,
-                                                   EntityReference reference
-    )
+                                                   EntityReference reference )
     {
-        EntityState state = fetchCachedState( reference, module, unitOfWork.currentTime() );
-        if( state != null )
+        try
         {
-            return state;
+            EntityState state = fetchCachedState( reference, module, uow.currentTime() );
+            if( state != null )
+            {
+                return state;
+            }
+            // Get state
+            try( Reader in = mapEntityStore.get( reference ) )
+            {
+                JSONEntityState loadedState = readEntityState( module, in );
+                if( loadedState.status() == EntityStatus.UPDATED )
+                {
+                    List<JSONEntityState> migrated = new ArrayList<>( 1 );
+                    migrated.add( loadedState );
+                    synchMigratedEntities( migrated );
+                }
+                if( doCacheOnRead( uow ) )
+                {
+                    cache.put( reference.identity().toString(), new CacheState( loadedState.state() ) );
+                }
+                return loadedState;
+            }
         }
-        // Get state
-        Reader in = mapEntityStore.get( reference );
-        JSONEntityState loadedState = readEntityState( module, in );
-        if( doCacheOnRead( unitOfWork ) )
+        catch( EntityStoreException ex )
         {
-            cache.put( reference.identity().toString(), new CacheState( loadedState.state() ) );
+            throw ex;
+        }
+        catch( Exception ex )
+        {
+            throw new EntityStoreException( ex );
         }
-        return loadedState;
     }
 
     @Override
-    public synchronized String versionOf( EntityStoreUnitOfWork unitOfWork,
-                                          EntityReference reference
-    )
+    public synchronized String versionOf( EntityStoreUnitOfWork uow, EntityReference reference )
     {
         CacheState cacheState = cache.get( reference.identity().toString() );
         if( cacheState != null )
         {
-            try
-            {
-                return cacheState.json.getString( JSONKeys.VERSION );
-            }
-            catch( JSONException e )
-            {
-                // Should not be able to happen, unless internal error in the cache system.
-                throw new EntityStoreException( e );
-            }
+            return cacheState.json.getString( JSONKeys.VERSION );
         }
-        // Get state
-        Reader entityState = mapEntityStore.get( reference );
         try
         {
-            JSONObject jsonObject = new JSONObject( new JSONTokener( entityState ) );
-            return jsonObject.getString( JSONKeys.VERSION );
+            // Get state
+            Reader entityState = mapEntityStore.get( reference );
+            return Json.createReader( entityState ).readObject().getString( JSONKeys.VERSION );
         }
-        catch( JSONException e )
+        catch( EntityStoreException ex )
         {
-            throw new EntityStoreException( e );
+            throw ex;
+        }
+        catch( Exception ex )
+        {
+            throw new EntityStoreException( ex );
         }
     }
 
     @Override
-    public StateCommitter applyChanges( final EntityStoreUnitOfWork unitOfWork,
-                                        final Iterable<EntityState> state
-    )
+    public StateCommitter applyChanges( EntityStoreUnitOfWork uow, Iterable<EntityState> state )
         throws EntityStoreException
     {
         return new StateCommitter()
@@ -250,10 +252,9 @@ public class JSONMapEntityStoreMixin
                     mapEntityStore.applyChanges( new MapEntityStore.MapChanges()
                     {
                         @Override
-                        public void visitMap( MapEntityStore.MapChanger changer )
-                            throws IOException
+                        public void visitMap( MapEntityStore.MapChanger changer ) throws Exception
                         {
-                            CacheOptions options = unitOfWork.usecase().metaInfo( CacheOptions.class );
+                            CacheOptions options = uow.usecase().metaInfo( CacheOptions.class );
                             if( options == null )
                             {
                                 options = CacheOptions.ALWAYS;
@@ -262,26 +263,35 @@ public class JSONMapEntityStoreMixin
                             for( EntityState entityState : state )
                             {
                                 JSONEntityState state = (JSONEntityState) entityState;
+                                String newVersion = uow.identity().toString();
+                                Instant lastModified = uow.currentTime();
                                 if( state.status().equals( EntityStatus.NEW ) )
                                 {
-                                    try (Writer writer = changer.newEntity( state.entityReference(), state.entityDescriptor() ))
+                                    try( Writer writer = changer.newEntity( state.entityReference(),
+                                                                            state.entityDescriptor() ) )
                                     {
-                                        writeEntityState( state, writer, unitOfWork.identity().toString(), unitOfWork.currentTime() );
+                                        writeEntityState( state, writer, newVersion, lastModified );
                                     }
                                     if( options.cacheOnNew() )
                                     {
-                                        cache.put( state.entityReference().identity().toString(), new CacheState( state.state() ) );
+                                        cache.put( state.entityReference().identity().toString(),
+                                                   new CacheState( state.state() ) );
                                     }
                                 }
                                 else if( state.status().equals( EntityStatus.UPDATED ) )
                                 {
-                                    try (Writer writer = changer.updateEntity( state.entityReference(), state.entityDescriptor() ))
+                                    MapEntityStore.MapChange mapChange = new MapEntityStore.MapChange(
+                                        state.entityReference(), state.entityDescriptor(),
+                                        state.version(), newVersion, lastModified
+                                    );
+                                    try( Writer writer = changer.updateEntity( mapChange ) )
                                     {
-                                        writeEntityState( state, writer, unitOfWork.identity().toString(), unitOfWork.currentTime() );
+                                        writeEntityState( state, writer, newVersion, lastModified );
                                     }
                                     if( options.cacheOnWrite() )
                                     {
-                                        cache.put( state.entityReference().identity().toString(), new CacheState( state.state() ) );
+                                        cache.put( state.entityReference().identity().toString(),
+                                                   new CacheState( state.state() ) );
                                     }
                                 }
                                 else if( state.status().equals( EntityStatus.REMOVED ) )
@@ -293,7 +303,7 @@ public class JSONMapEntityStoreMixin
                         }
                     } );
                 }
-                catch( IOException e )
+                catch( Exception e )
                 {
                     throw new EntityStoreException( e );
                 }
@@ -309,11 +319,13 @@ public class JSONMapEntityStoreMixin
     @Override
     public Stream<EntityState> entityStates( ModuleDescriptor module )
     {
-        List<EntityState> migrated = new ArrayList<>();
-        return mapEntityStore.entityStates().map(
-            reader ->
+        try
+        {
+            Stream<Reader> stateStream = mapEntityStore.entityStates();
+            List<JSONEntityState> migrated = new ArrayList<>();
+            Function<Reader, EntityState> function = reader ->
             {
-                EntityState entity = readEntityState( module, reader );
+                JSONEntityState entity = readEntityState( module, reader );
                 if( entity.status() == EntityStatus.UPDATED )
                 {
                     migrated.add( entity );
@@ -324,42 +336,55 @@ public class JSONMapEntityStoreMixin
                     }
                 }
                 return entity;
-            }
-        ).onClose(
-            () ->
+            };
+            Runnable closer = () ->
             {
                 // Synch any remaining migrated entities
                 if( !migrated.isEmpty() )
                 {
                     synchMigratedEntities( migrated );
                 }
-            }
-        );
+            };
+            return stateStream.map( function ).onClose( closer );
+        }
+        catch( EntityStoreException ex )
+        {
+            throw ex;
+        }
+        catch( Exception ex )
+        {
+            throw new EntityStoreException( ex );
+        }
     }
 
-    private void synchMigratedEntities( final List<EntityState> migratedEntities )
+    private void synchMigratedEntities( List<JSONEntityState> migratedEntities )
     {
         try
         {
-            mapEntityStore.applyChanges( new MapEntityStore.MapChanges()
-            {
-                @Override
-                public void visitMap( MapEntityStore.MapChanger changer )
-                    throws IOException
+            mapEntityStore.applyChanges(
+                changer ->
                 {
-                    for( EntityState migratedEntity : migratedEntities )
+                    for( JSONEntityState state : migratedEntities )
                     {
-                        JSONEntityState state = (JSONEntityState) migratedEntity;
-                        try( Writer writer = changer.updateEntity( state.entityReference(), state.entityDescriptor() ) )
+                        Instant lastModified = state.lastModified();
+                        String version = state.version();
+                        MapEntityStore.MapChange changeInfo = new MapEntityStore.MapChange(
+                            state.entityReference(), state.entityDescriptor(),
+                            version, version, lastModified
+                        );
+                        try( Writer writer = changer.updateEntity( changeInfo ) )
                         {
-                            writeEntityState( state, writer, state.version(), state.lastModified() );
+                            writeEntityState( state, writer, version, lastModified );
                         }
                     }
-                }
-            } );
+                } );
             migratedEntities.clear();
         }
-        catch( IOException ex )
+        catch( EntityStoreException ex )
+        {
+            throw ex;
+        }
+        catch( Exception ex )
         {
             throw new EntityStoreException( "Synchronization of Migrated Entities failed.", ex );
         }
@@ -367,20 +392,18 @@ public class JSONMapEntityStoreMixin
 
     protected Identity newUnitOfWorkId()
     {
-        return identityGenerator.generate(EntityStore.class);
+        return identityGenerator.generate( EntityStore.class );
     }
 
-    protected void writeEntityState(JSONEntityState state, Writer writer, String version, Instant lastModified )
+    protected void writeEntityState( JSONEntityState state, Writer writer, String version, Instant lastModified )
         throws EntityStoreException
     {
         try
         {
-            JSONObject jsonState = state.state();
-            jsonState.put( JSONKeys.VERSION, version );
-            jsonState.put( JSONKeys.MODIFIED, lastModified.toEpochMilli() );
-            writer.append( jsonState.toString() );
+            state.stateCloneWithVersionAndModified( version, lastModified );
+            writer.append( state.state().toString() );
         }
-        catch( JSONException | IOException e )
+        catch( IOException e )
         {
             throw new EntityStoreException( "Could not store EntityState", e );
         }
@@ -391,37 +414,38 @@ public class JSONMapEntityStoreMixin
     {
         try
         {
-            JSONObject jsonObject = new JSONObject( new JSONTokener( entityState ) );
+            JsonObject parsedState = Json.createReader( entityState ).readObject();
+            JsonObjectBuilder jsonStateBuilder = JavaxJson.toBuilder( parsedState );
             EntityStatus status = EntityStatus.LOADED;
 
-            String version = jsonObject.getString( JSONKeys.VERSION );
-            Instant modified = Instant.ofEpochMilli(jsonObject.getLong( JSONKeys.MODIFIED ));
-            Identity identity = new StringIdentity(jsonObject.getString( JSONKeys.IDENTITY ));
+            String version = parsedState.getString( JSONKeys.VERSION );
+            Instant modified = Instant.ofEpochMilli( parsedState.getJsonNumber( JSONKeys.MODIFIED ).longValueExact() );
+            Identity identity = new StringIdentity( parsedState.getString( JSONKeys.IDENTITY ) );
 
-            // Check if NamedAssociation is supported
-            if( !jsonObject.has( JSONKeys.NAMED_ASSOCIATIONS ) )
+            // Check if version is correct
+            JsonObject state;
+            String currentAppVersion = parsedState.getString( JSONKeys.APPLICATION_VERSION, "0.0" );
+            if( currentAppVersion.equals( application.version() ) )
             {
-                jsonObject.put( JSONKeys.NAMED_ASSOCIATIONS, new JSONObject() );
+                state = jsonStateBuilder.build();
             }
-
-            // Check if version is correct
-            String currentAppVersion = jsonObject.optString( JSONKeys.APPLICATION_VERSION, "0.0" );
-            if( !currentAppVersion.equals( application.version() ) )
+            else
             {
                 if( migration != null )
                 {
-                    migration.migrate( jsonObject, application.version(), this );
+                    state = migration.migrate( jsonStateBuilder.build(), application.version(), this );
                 }
                 else
                 {
                     // Do nothing - set version to be correct
-                    jsonObject.put( JSONKeys.APPLICATION_VERSION, application.version() );
+                    jsonStateBuilder.add( JSONKeys.APPLICATION_VERSION, application.version() );
+                    state = jsonStateBuilder.build();
                 }
                 // State changed
                 status = EntityStatus.UPDATED;
             }
 
-            String type = jsonObject.getString( JSONKeys.TYPE );
+            String type = state.getString( JSONKeys.TYPE );
 
             EntityDescriptor entityDescriptor = module.entityDescriptor( type );
             if( entityDescriptor == null )
@@ -429,33 +453,40 @@ public class JSONMapEntityStoreMixin
                 throw new NoSuchEntityTypeException( type, module.name(), module.typeLookup() );
             }
 
-            return new JSONEntityState( module,
-                                        valueSerialization,
-                                        version,
-                                        modified,
+            return new JSONEntityState( module, serialization,
+                                        version, modified,
                                         EntityReference.create( identity ),
-                                        status,
-                                        entityDescriptor,
-                                        jsonObject
+                                        status, entityDescriptor,
+                                        state
             );
         }
-        catch( JSONException e )
+        catch( EntityStoreException ex )
         {
-            throw new EntityStoreException( e );
+            throw ex;
+        }
+        catch( Exception ex )
+        {
+            throw new EntityStoreException( ex );
         }
     }
 
     @Override
-    public JSONObject jsonStateOf( String id )
-        throws IOException
+    public JsonObject jsonStateOf( String id )
     {
-        try (Reader reader = mapEntityStore.get( EntityReference.parseEntityReference( id ) ))
+        try( Reader reader = mapEntityStore.get( EntityReference.parseEntityReference( id ) ) )
+        {
+            try( JsonReader jsonReader = Json.createReader( reader ) )
+            {
+                return jsonReader.readObject();
+            }
+        }
+        catch( EntityStoreException ex )
         {
-            return new JSONObject( new JSONTokener( reader ) );
+            throw ex;
         }
-        catch( JSONException e )
+        catch( Exception ex )
         {
-            throw new IOException( e );
+            throw new EntityStoreException( ex );
         }
     }
 
@@ -464,15 +495,20 @@ public class JSONMapEntityStoreMixin
         CacheState cacheState = cache.get( reference.identity().toString() );
         if( cacheState != null )
         {
-            JSONObject data = cacheState.json;
+            JsonObject data = cacheState.json;
             try
             {
                 String type = data.getString( JSONKeys.TYPE );
                 EntityDescriptor entityDescriptor = module.entityDescriptor( type );
-                Instant lastModified = Instant.ofEpochMilli(data.getLong(JSONKeys.MODIFIED));
-                return new JSONEntityState( module, valueSerialization, data.getString( JSONKeys.VERSION ), lastModified, reference, EntityStatus.LOADED, entityDescriptor, data );
+                String version = data.getString( JSONKeys.VERSION );
+                Instant lastModified = Instant.ofEpochMilli( data.getJsonNumber( JSONKeys.MODIFIED ).longValueExact() );
+                return new JSONEntityState( module, serialization,
+                                            version, lastModified,
+                                            reference,
+                                            EntityStatus.LOADED, entityDescriptor,
+                                            data );
             }
-            catch( JSONException e )
+            catch( Exception e )
             {
                 // Should not be able to happen, unless internal error in the cache system.
                 throw new EntityStoreException( e );
@@ -490,13 +526,13 @@ public class JSONMapEntityStoreMixin
     public static class CacheState
         implements Externalizable
     {
-        public JSONObject json;
+        public JsonObject json;
 
         public CacheState()
         {
         }
 
-        private CacheState( JSONObject state )
+        private CacheState( JsonObject state )
         {
             json = state;
         }
@@ -512,14 +548,7 @@ public class JSONMapEntityStoreMixin
         public void readExternal( ObjectInput in )
             throws IOException, ClassNotFoundException
         {
-            try
-            {
-                json = new JSONObject( in.readUTF() );
-            }
-            catch( JSONException e )
-            {
-                throw new IOException( e );
-            }
+            json = Json.createReader( new StringReader( in.readUTF() ) ).readObject();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONNamedAssociationState.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONNamedAssociationState.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONNamedAssociationState.java
index d37bb9c..74ea667 100644
--- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONNamedAssociationState.java
+++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/JSONNamedAssociationState.java
@@ -19,42 +19,56 @@
  */
 package org.apache.polygene.spi.entitystore.helpers;
 
+import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.List;
 import java.util.NoSuchElementException;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import javax.json.Json;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonValue;
 import org.apache.polygene.api.entity.EntityReference;
 import org.apache.polygene.spi.entity.NamedAssociationState;
 import org.apache.polygene.spi.entitystore.EntityStoreException;
 
 /**
  * JSON implementation of NamedAssociationState.
- * <p>Backed by a JSONObject.</p>
+ * <p>Backed by a JsonObject.</p>
  */
 public final class JSONNamedAssociationState
     implements NamedAssociationState
 {
 
     private final JSONEntityState entityState;
-    private final JSONObject references;
+    private final String stateName;
 
-    public JSONNamedAssociationState( JSONEntityState entityState, JSONObject references )
+    /* package */ JSONNamedAssociationState( JSONEntityState entityState, String stateName )
     {
         this.entityState = entityState;
-        this.references = references;
+        this.stateName = stateName;
+    }
+
+    private JsonObject getReferences()
+    {
+        JsonObject namedAssociations = entityState.state().getJsonObject( JSONKeys.NAMED_ASSOCIATIONS );
+        JsonValue references = namedAssociations.get( stateName );
+        if( references != null && references.getValueType() == JsonValue.ValueType.OBJECT )
+        {
+            return (JsonObject) references;
+        }
+        return Json.createObjectBuilder().build();
     }
 
     @Override
     public int count()
     {
-        return references.length();
+        return getReferences().size();
     }
 
     @Override
     public boolean containsName( String name )
     {
-        return references.has( name );
+        return getReferences().containsKey( name );
     }
 
     @Override
@@ -62,16 +76,16 @@ public final class JSONNamedAssociationState
     {
         try
         {
-            if( references.has( name ) && entityReference.identity().toString().equals( references.getString( name ) ) )
+            if( containsName( name )
+                && entityReference.identity().toString().equals( getReferences().getString( name ) ) )
             {
                 return false;
             }
-            entityState.cloneStateIfGlobalStateLoaded();
-            references.put( name, entityReference.identity().toString() );
+            entityState.stateCloneAddNamedAssociation( stateName, name, entityReference );
             entityState.markUpdated();
             return true;
         }
-        catch( JSONException ex )
+        catch( JsonException ex )
         {
             throw new EntityStoreException( ex );
         }
@@ -80,12 +94,11 @@ public final class JSONNamedAssociationState
     @Override
     public boolean remove( String name )
     {
-        if( !references.has( name ) )
+        if( !containsName( name ) )
         {
             return false;
         }
-        entityState.cloneStateIfGlobalStateLoaded();
-        references.remove( name );
+        entityState.stateCloneRemoveNamedAssociation( stateName, name );
         entityState.markUpdated();
         return true;
     }
@@ -93,29 +106,18 @@ public final class JSONNamedAssociationState
     @Override
     public EntityReference get( String name )
     {
-        try
-        {
-            return EntityReference.parseEntityReference( references.getString( name ) );
-        }
-        catch( JSONException ex )
-        {
-            return null;
-        }
+        String stringRef = getReferences().getString( name, null );
+        return stringRef == null ? null : EntityReference.parseEntityReference( stringRef );
     }
 
     @Override
     public String nameOf( EntityReference entityReference )
     {
-        JSONArray names = references.names();
-        if( names == null )
-        {
-            return null;
-        }
         try
         {
-            for( int idx = 0; idx < names.length(); idx++ )
+            JsonObject references = getReferences();
+            for( String name : references.keySet() )
             {
-                String name = names.getString( idx );
                 if( entityReference.identity().toString().equals( references.getString( name ) ) )
                 {
                     return name;
@@ -123,7 +125,7 @@ public final class JSONNamedAssociationState
             }
             return null;
         }
-        catch( JSONException ex )
+        catch( JsonException ex )
         {
             throw new EntityStoreException( ex );
         }
@@ -132,7 +134,7 @@ public final class JSONNamedAssociationState
     @Override
     public Iterator<String> iterator()
     {
-        final JSONArray names = references.names() == null ? new JSONArray() : references.names();
+        List<String> names = new ArrayList<>( getReferences().keySet() );
         return new Iterator<String>()
         {
             private int idx = 0;
@@ -140,7 +142,7 @@ public final class JSONNamedAssociationState
             @Override
             public boolean hasNext()
             {
-                return idx < names.length();
+                return idx < names.size();
             }
 
             @Override
@@ -148,11 +150,11 @@ public final class JSONNamedAssociationState
             {
                 try
                 {
-                    String next = names.getString( idx );
+                    String next = names.get( idx );
                     idx++;
                     return next;
                 }
-                catch( JSONException ex )
+                catch( JsonException ex )
                 {
                     throw new NoSuchElementException();
                 }
@@ -169,7 +171,6 @@ public final class JSONNamedAssociationState
     @Override
     public String toString()
     {
-        return references.toString();
+        return getReferences().toString();
     }
-
 }

http://git-wip-us.apache.org/repos/asf/polygene-java/blob/c3175b92/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/MapEntityStore.java
----------------------------------------------------------------------
diff --git a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/MapEntityStore.java b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/MapEntityStore.java
index a8ff1c9..901c2c1 100644
--- a/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/MapEntityStore.java
+++ b/core/spi/src/main/java/org/apache/polygene/spi/entitystore/helpers/MapEntityStore.java
@@ -22,48 +22,41 @@ package org.apache.polygene.spi.entitystore.helpers;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.Writer;
+import java.time.Instant;
 import java.util.stream.Stream;
 import org.apache.polygene.api.entity.EntityDescriptor;
 import org.apache.polygene.api.entity.EntityReference;
-import org.apache.polygene.spi.entitystore.EntityNotFoundException;
-import org.apache.polygene.spi.entitystore.EntityStoreException;
 
 /**
  * MapEntityStore.
  */
 public interface MapEntityStore
 {
-
     /**
      * @param entityReference The reference to the entity that we want to get.
      * @return Entity state Reader
      */
-    Reader get( EntityReference entityReference )
-        throws EntityStoreException;
+    Reader get( EntityReference entityReference ) throws Exception;
 
     /**
      * @return All entities state Readers, must be closed
      */
-    Stream<Reader> entityStates();
+    Stream<Reader> entityStates() throws Exception;
 
-    void applyChanges( MapChanges changes )
-        throws IOException;
+    void applyChanges( MapChanges changes ) throws Exception;
 
     /**
      * Changes to be applied on a MapEntityStore.
      */
     interface MapChanges
     {
-
         /**
          * Visitable MapChanges.
          *
          * @param changer Map changer
          * @throws IOException on error
          */
-        void visitMap( MapChanger changer )
-            throws IOException;
-
+        void visitMap( MapChanger changer ) throws Exception;
     }
 
     /**
@@ -71,16 +64,65 @@ public interface MapEntityStore
      */
     interface MapChanger
     {
-
         Writer newEntity( EntityReference ref, EntityDescriptor entityDescriptor )
-            throws IOException;
+            throws Exception;
 
-        Writer updateEntity( EntityReference ref, EntityDescriptor entityDescriptor )
-            throws IOException;
+        Writer updateEntity( MapChange mapChange ) throws Exception;
 
         void removeEntity( EntityReference ref, EntityDescriptor entityDescriptor )
-            throws EntityNotFoundException;
-
+            throws Exception;
     }
 
+    /**
+     * MapEntityStore change meta info.
+     *
+     * Implementations backed by a shared store can make use of this for e.g. optimistic locking.
+     */
+    class MapChange
+    {
+        private final EntityReference reference;
+        private final EntityDescriptor descriptor;
+        private final String previousVersion;
+        private final String newVersion;
+        private final Instant lastModified;
+
+        public MapChange( EntityReference reference, EntityDescriptor descriptor,
+                          String previousVersion, String newVersion,
+                          Instant lastModified )
+        {
+            this.reference = reference;
+            this.descriptor = descriptor;
+            this.previousVersion = previousVersion;
+            this.newVersion = newVersion;
+            this.lastModified = lastModified;
+        }
+
+        public EntityReference reference()
+        {
+            return reference;
+        }
+
+        public EntityDescriptor descriptor()
+        {
+            return descriptor;
+        }
+
+        /**
+         * @return null if the change is an insertion
+         */
+        public String previousVersion()
+        {
+            return previousVersion;
+        }
+
+        public String newVersion()
+        {
+            return newVersion;
+        }
+
+        public Instant lastModified()
+        {
+            return lastModified;
+        }
+    }
 }


Mime
View raw message