You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polygene.apache.org by ni...@apache.org on 2015/04/17 18:43:01 UTC

[22/28] zest-qi4j git commit: QI-414 Entity creation and update from Values in 'conversion' library

QI-414 Entity creation and update from Values in 'conversion' library


Project: http://git-wip-us.apache.org/repos/asf/zest-qi4j/repo
Commit: http://git-wip-us.apache.org/repos/asf/zest-qi4j/commit/378e5406
Tree: http://git-wip-us.apache.org/repos/asf/zest-qi4j/tree/378e5406
Diff: http://git-wip-us.apache.org/repos/asf/zest-qi4j/diff/378e5406

Branch: refs/heads/develop
Commit: 378e5406c269f9a71b27a50271f4375a9b2b840e
Parents: 355ea47
Author: Paul Merlin <pa...@nosphere.org>
Authored: Wed Mar 25 16:56:02 2015 +0100
Committer: Paul Merlin <pa...@nosphere.org>
Committed: Wed Mar 25 16:56:02 2015 +0100

----------------------------------------------------------------------
 libraries/conversion/src/docs/conversion.txt    |  41 +-
 .../conversion/values/EntityToValue.java        |  34 +-
 .../qi4j/library/conversion/values/Shared.java  |  67 ++
 .../conversion/values/ValueToEntity.java        | 132 +++
 .../values/ValueToEntityAssembler.java          |  39 +
 .../conversion/values/ValueToEntityMixin.java   | 827 +++++++++++++++++++
 .../conversion/values/ValueToEntityService.java |  27 +
 .../conversion/values/EntityToValueTest.java    | 190 +----
 .../library/conversion/values/TestModel.java    | 200 +++++
 .../conversion/values/ValueToEntityTest.java    | 351 ++++++++
 10 files changed, 1706 insertions(+), 202 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/docs/conversion.txt
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/docs/conversion.txt b/libraries/conversion/src/docs/conversion.txt
index 9129b24..7d7da92 100644
--- a/libraries/conversion/src/docs/conversion.txt
+++ b/libraries/conversion/src/docs/conversion.txt
@@ -24,7 +24,7 @@ Let's say we have an interface defining state:
 
 [snippet,java]
 ----
-source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
 tag=state
 ----
 
@@ -32,7 +32,7 @@ An EntityComposite using the state as a Private Mixin:
 
 [snippet,java]
 ----
-source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
 tag=entity
 ----
 
@@ -40,7 +40,7 @@ And a ValueComposite extending this very same state;
 
 [snippet,java]
 ----
-source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
 tag=value
 ----
 
@@ -52,7 +52,38 @@ source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/Ent
 tag=conversion
 ----
 
-Associations are converted to Identity strings.
+
+== Values to Entities ==
+
+Using the ValueToEntity service one can create new Entities or update existing ones from Values.
+It is easy assembled:
+
+[snippet,java]
+----
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/ValueToEntityTest.java
+tag=assembly
+----
+
+Let's say we have the exact same model as described above.
+
+Here is how to create an EntityComposite from a ValueComposite:
+
+[snippet,java]
+----
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/ValueToEntityTest.java
+tag=creation
+----
+
+Here is how to update an EntityComposite from a ValueComposite:
+
+[snippet,java]
+----
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/ValueToEntityTest.java
+tag=update
+----
+
+
+== Associations are converted to Identity strings ==
 
 If your Entities and Values cannot use the same state type, you can annotate the Value that is the target of the
 conversion with the `@Unqualified` annotation. Then, the lookup of the Value Property will be performed using the
@@ -63,7 +94,7 @@ Here is an example:
 
 [snippet,java]
 ----
-source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
+source=libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
 tag=unqualified
 ----
 

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/EntityToValue.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/EntityToValue.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/EntityToValue.java
index c48266c..5d0fe0a 100644
--- a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/EntityToValue.java
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/EntityToValue.java
@@ -1,7 +1,7 @@
 /*
- * Copyright 2010 Niclas Hedhman.
+ * Copyright 2010-2012 Niclas Hedhman.
  * Copyright 2011 Rickard Öberg.
- * Copyright 2013-2014 Paul Merlin.
+ * Copyright 2013-2015 Paul Merlin.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -39,8 +39,6 @@ import org.qi4j.api.injection.scope.Structure;
 import org.qi4j.api.mixin.Mixins;
 import org.qi4j.api.property.PropertyDescriptor;
 import org.qi4j.api.structure.Module;
-import org.qi4j.api.type.CollectionType;
-import org.qi4j.api.type.MapType;
 import org.qi4j.api.value.NoSuchValueException;
 import org.qi4j.api.value.ValueBuilder;
 import org.qi4j.api.value.ValueDescriptor;
@@ -48,10 +46,13 @@ import org.qi4j.functional.Function;
 import org.qi4j.functional.Iterables;
 import org.qi4j.spi.Qi4jSPI;
 
+import static org.qi4j.library.conversion.values.Shared.STRING_COLLECTION_TYPE_SPEC;
+import static org.qi4j.library.conversion.values.Shared.STRING_MAP_TYPE_SPEC;
+import static org.qi4j.library.conversion.values.Shared.STRING_TYPE_SPEC;
+
 @Mixins( EntityToValue.EntityToValueMixin.class )
 public interface EntityToValue
 {
-
     /**
      * Convert an entity to a value.
      *
@@ -178,7 +179,7 @@ public interface EntityToValue
                         {
                             AssociationStateDescriptor entityState = entityDescriptor.state();
                             String associationName = descriptor.qualifiedName().name();
-                            if( descriptor.valueType().mainType().equals( String.class ) )
+                            if( STRING_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 // Find Association and convert to string
                                 AssociationDescriptor associationDescriptor;
@@ -201,10 +202,7 @@ public interface EntityToValue
                                     return null;
                                 }
                             }
-                            else if( descriptor.valueType() instanceof CollectionType
-                                     && ( (CollectionType) descriptor.valueType() ).collectedType()
-                                .mainType()
-                                .equals( String.class ) )
+                            else if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 AssociationDescriptor associationDescriptor;
                                 try
@@ -224,9 +222,7 @@ public interface EntityToValue
                                 }
                                 return entities;
                             }
-                            else if( descriptor.valueType() instanceof MapType
-                                     && ( (MapType) descriptor.valueType() ).keyType().mainType().equals( String.class )
-                                     && ( (MapType) descriptor.valueType() ).valueType().mainType().equals( String.class ) )
+                            else if( STRING_MAP_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 AssociationDescriptor associationDescriptor;
                                 try
@@ -291,8 +287,7 @@ public interface EntityToValue
             }
             else
             {
-                builder = module.newValueBuilderWithState(
-                    valueType,
+                builder = module.newValueBuilderWithState(valueType,
                     new Function<PropertyDescriptor, Object>()
                 {
                     @Override
@@ -307,7 +302,7 @@ public interface EntityToValue
                         }
                         catch( IllegalArgumentException e )
                         {
-                            if( descriptor.valueType().mainType().equals( String.class ) )
+                            if( STRING_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 // Find Association and convert to string
                                 AssociationDescriptor associationDescriptor;
@@ -331,8 +326,7 @@ public interface EntityToValue
                                     return null;
                                 }
                             }
-                            else if( descriptor.valueType() instanceof CollectionType
-                                     && ( (CollectionType) descriptor.valueType() ).collectedType().mainType().equals( String.class ) )
+                            else if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 AssociationDescriptor associationDescriptor;
                                 try
@@ -353,9 +347,7 @@ public interface EntityToValue
                                 }
                                 return entities;
                             }
-                            else if( descriptor.valueType() instanceof MapType
-                                     && ( (MapType) descriptor.valueType() ).keyType().mainType().equals( String.class )
-                                     && ( (MapType) descriptor.valueType() ).valueType().mainType().equals( String.class ) )
+                            else if( STRING_MAP_TYPE_SPEC.satisfiedBy( descriptor.valueType() ) )
                             {
                                 AssociationDescriptor associationDescriptor;
                                 try

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/Shared.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/Shared.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/Shared.java
new file mode 100644
index 0000000..5246014
--- /dev/null
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/Shared.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2014-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import org.qi4j.api.type.CollectionType;
+import org.qi4j.api.type.MapType;
+import org.qi4j.api.type.ValueType;
+import org.qi4j.functional.Specification;
+
+/**
+ * Shared.
+ */
+final class Shared
+{
+    static final Specification<ValueType> STRING_TYPE_SPEC;
+    static final Specification<ValueType> STRING_COLLECTION_TYPE_SPEC;
+    static final Specification<ValueType> STRING_MAP_TYPE_SPEC;
+
+    static
+    {
+        // Type Specifications
+        STRING_TYPE_SPEC = new Specification<ValueType>()
+        {
+            @Override
+            public boolean satisfiedBy( ValueType valueType )
+            {
+                return valueType.mainType().equals( String.class );
+            }
+        };
+        STRING_COLLECTION_TYPE_SPEC = new Specification<ValueType>()
+        {
+            @Override
+            public boolean satisfiedBy( ValueType valueType )
+            {
+                return valueType instanceof CollectionType
+                       && ( (CollectionType) valueType ).collectedType().mainType().equals( String.class );
+            }
+        };
+        STRING_MAP_TYPE_SPEC = new Specification<ValueType>()
+        {
+            @Override
+            public boolean satisfiedBy( ValueType valueType )
+            {
+                return valueType instanceof MapType
+                       && ( (MapType) valueType ).keyType().mainType().equals( String.class )
+                       && ( (MapType) valueType ).valueType().mainType().equals( String.class );
+            }
+        };
+    }
+
+    private Shared()
+    {
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntity.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntity.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntity.java
new file mode 100644
index 0000000..559fb37
--- /dev/null
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2014-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import org.qi4j.api.entity.EntityComposite;
+import org.qi4j.api.entity.Identity;
+import org.qi4j.api.unitofwork.NoSuchEntityException;
+import org.qi4j.api.value.ValueComposite;
+import org.qi4j.functional.Function;
+
+/**
+ * Create or update Entities from matching Values.
+ */
+public interface ValueToEntity
+{
+    /**
+     * Create an Entity from a Value.
+     * <p>
+     * If the Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if the Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>        Value Type
+     * @param entityType Entity Type
+     * @param value      Value
+     *
+     * @return the created Entity
+     */
+    <T> T create( Class<T> entityType, Object value );
+
+    /**
+     * Create an Entity from a Value.
+     * <p>
+     * If {@code identity} is not null, it is used as Entity identity.
+     * Else, if the Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if the Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>        Value Type
+     * @param entityType Entity Type
+     * @param identity   Entity Identity, may be null
+     * @param value      Value
+     *
+     * @return the created Entity
+     */
+    <T> T create( Class<T> entityType, String identity, Object value );
+
+    /**
+     * Create an Entity from a Value.
+     * <p>
+     * If the Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if the Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>                  Value Type
+     * @param entityType           Entity Type
+     * @param value                Value
+     * @param prototypeOpportunity A Function that will be mapped on the Entity prototype before instanciation
+     *
+     * @return the created Entity
+     */
+    <T> T create( Class<T> entityType, Object value, Function<T, T> prototypeOpportunity );
+
+    /**
+     * Create an Entity from a Value.
+     * <p>
+     * If {@code identity} is not null, it is used as Entity identity.
+     * Else, if the Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if the Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>                  Value Type
+     * @param entityType           Entity Type
+     * @param identity             Entity Identity, may be null
+     * @param value                Value
+     * @param prototypeOpportunity A Function that will be mapped on the Entity prototype before instanciation
+     *
+     * @return the created Entity
+     */
+    <T> T create( Class<T> entityType, String identity, Object value, Function<T, T> prototypeOpportunity );
+
+    /**
+     * Create an Iterable of Entities from an Iterable of Values.
+     * <p>
+     * If a Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if a Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>        Value Type
+     * @param entityType Entity Type
+     * @param values     An Iterable of Values
+     *
+     * @return the Iterable of created Entities
+     */
+    <T> Iterable<T> create( Class<T> entityType, Iterable<Object> values );
+
+    /**
+     * Create an Iterable of Entities from an Iterable of Values.
+     * <p>
+     * If a Value extends {@link Identity} the Entity identity is taken from the Value's state.
+     * Else, if a Value's state for {@code Identity} is absent, a new Identity is generated.
+     *
+     * @param <T>                  Value Type
+     * @param entityType           Entity Type
+     * @param values               An Iterable of Values
+     * @param prototypeOpportunity A Function that will be mapped on each Entity prototype before instanciation
+     *
+     * @return the Iterable of created Entities
+     */
+    <T> Iterable<T> create( Class<T> entityType, Iterable<Object> values, Function<T, T> prototypeOpportunity );
+
+    /**
+     * Update an Entity from a Value.
+     *
+     * @param entity Entity
+     * @param value  Value
+     *
+     * @throws ClassCastException    If {@code entity} is not an {@link EntityComposite}
+     *                               or if {@code value} is not a {@link ValueComposite}
+     * @throws NoSuchEntityException If some associated Entity is absent from the EntityStore/UoW
+     */
+    void update( Object entity, Object value )
+        throws ClassCastException, NoSuchEntityException;
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityAssembler.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityAssembler.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityAssembler.java
new file mode 100644
index 0000000..bc34007
--- /dev/null
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityAssembler.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2014-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import org.qi4j.bootstrap.Assemblers;
+import org.qi4j.bootstrap.AssemblyException;
+import org.qi4j.bootstrap.ModuleAssembly;
+import org.qi4j.bootstrap.ServiceDeclaration;
+
+/**
+ * ValueToEntity Service Assembler.
+ */
+public class ValueToEntityAssembler
+    extends Assemblers.VisibilityIdentity<EntityToValueAssembler>
+{
+    @Override
+    public void assemble( ModuleAssembly module )
+        throws AssemblyException
+    {
+        ServiceDeclaration service = module.services( ValueToEntityService.class ).visibleIn( visibility() );
+        if( hasIdentity() )
+        {
+            service.identifiedBy( identity() );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityMixin.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityMixin.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityMixin.java
new file mode 100644
index 0000000..9759fcd
--- /dev/null
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityMixin.java
@@ -0,0 +1,827 @@
+/*
+ * Copyright (c) 2014-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.qi4j.api.association.Association;
+import org.qi4j.api.association.AssociationDescriptor;
+import org.qi4j.api.association.AssociationStateDescriptor;
+import org.qi4j.api.association.AssociationStateHolder;
+import org.qi4j.api.association.ManyAssociation;
+import org.qi4j.api.association.NamedAssociation;
+import org.qi4j.api.common.QualifiedName;
+import org.qi4j.api.entity.EntityBuilder;
+import org.qi4j.api.entity.EntityComposite;
+import org.qi4j.api.entity.EntityDescriptor;
+import org.qi4j.api.entity.EntityReference;
+import org.qi4j.api.entity.Identity;
+import org.qi4j.api.injection.scope.Structure;
+import org.qi4j.api.property.PropertyDescriptor;
+import org.qi4j.api.structure.Module;
+import org.qi4j.api.unitofwork.EntityTypeNotFoundException;
+import org.qi4j.api.unitofwork.NoSuchEntityException;
+import org.qi4j.api.value.ValueComposite;
+import org.qi4j.api.value.ValueDescriptor;
+import org.qi4j.functional.Function;
+import org.qi4j.functional.Iterables;
+import org.qi4j.spi.Qi4jSPI;
+
+import static org.qi4j.library.conversion.values.Shared.STRING_COLLECTION_TYPE_SPEC;
+import static org.qi4j.library.conversion.values.Shared.STRING_MAP_TYPE_SPEC;
+import static org.qi4j.library.conversion.values.Shared.STRING_TYPE_SPEC;
+
+/**
+ * ValueToEntity Mixin.
+ */
+public class ValueToEntityMixin
+    implements ValueToEntity
+{
+    private static final QualifiedName IDENTITY_STATE_NAME;
+    private static final Function<ManyAssociation<?>, Iterable<EntityReference>> MANY_ASSOC_TO_ENTITY_REF_ITERABLE;
+    private static final Function<NamedAssociation<?>, Map<String, EntityReference>> NAMED_ASSOC_TO_ENTITY_REF_MAP;
+    private static final Function<Collection<String>, Iterable<EntityReference>> STRING_COLLEC_TO_ENTITY_REF_ITERABLE;
+    private static final Function<Map<String, String>, Map<String, EntityReference>> STRING_MAP_TO_ENTITY_REF_MAP;
+
+    static
+    {
+        try
+        {
+            IDENTITY_STATE_NAME = QualifiedName.fromAccessor( Identity.class.getMethod( "identity" ) );
+        }
+        catch( NoSuchMethodException e )
+        {
+            throw new InternalError( "Qi4j Core Runtime codebase is corrupted. Contact Qi4j team: ValueToEntityMixin" );
+        }
+        MANY_ASSOC_TO_ENTITY_REF_ITERABLE = new Function<ManyAssociation<?>, Iterable<EntityReference>>()
+        {
+            @Override
+            public Iterable<EntityReference> map( ManyAssociation<?> manyAssoc )
+            {
+                if( manyAssoc == null )
+                {
+                    return Iterables.empty();
+                }
+                List<EntityReference> refs = new ArrayList<>( manyAssoc.count() );
+                for( Object entity : manyAssoc )
+                {
+                    refs.add( EntityReference.entityReferenceFor( entity ) );
+                }
+                return refs;
+            }
+        };
+        NAMED_ASSOC_TO_ENTITY_REF_MAP = new Function<NamedAssociation<?>, Map<String, EntityReference>>()
+        {
+            @Override
+            public Map<String, EntityReference> map( NamedAssociation<?> namedAssoc )
+            {
+                if( namedAssoc == null )
+                {
+                    return Collections.emptyMap();
+                }
+                Map<String, EntityReference> refs = new LinkedHashMap<>( namedAssoc.count() );
+                for( String name : namedAssoc )
+                {
+                    refs.put( name, EntityReference.entityReferenceFor( namedAssoc.get( name ) ) );
+                }
+                return refs;
+            }
+        };
+        STRING_COLLEC_TO_ENTITY_REF_ITERABLE = new Function<Collection<String>, Iterable<EntityReference>>()
+        {
+            @Override
+            public Iterable<EntityReference> map( Collection<String> stringCollec )
+            {
+                if( stringCollec == null )
+                {
+                    return Iterables.empty();
+                }
+                List<EntityReference> refList = new ArrayList<>();
+                for( String assId : stringCollec )
+                {
+                    refList.add( EntityReference.parseEntityReference( assId ) );
+                }
+                return refList;
+            }
+        };
+        STRING_MAP_TO_ENTITY_REF_MAP = new Function<Map<String, String>, Map<String, EntityReference>>()
+        {
+            @Override
+            public Map<String, EntityReference> map( Map<String, String> stringMap )
+            {
+                if( stringMap == null )
+                {
+                    return Collections.emptyMap();
+                }
+                Map<String, EntityReference> refMap = new LinkedHashMap<>( stringMap.size() );
+                for( Map.Entry<String, String> entry : stringMap.entrySet() )
+                {
+                    refMap.put( entry.getKey(), EntityReference.parseEntityReference( entry.getValue() ) );
+                }
+                return refMap;
+            }
+        };
+    }
+
+    @Structure
+    private Qi4jSPI spi;
+
+    @Structure
+    private Module module;
+
+    @Override
+    public <T> T create( Class<T> entityType, Object value )
+    {
+        return create( entityType, null, value );
+    }
+
+    @Override
+    public <T> T create( Class<T> entityType, String identity, Object value )
+    {
+        return createInstance( doConversion( entityType, identity, value ) );
+    }
+
+    @Override
+    public <T> T create( Class<T> entityType, Object value, Function<T, T> prototypeOpportunity )
+    {
+        return create( entityType, null, value, prototypeOpportunity );
+    }
+
+    @Override
+    public <T> T create( Class<T> entityType, String identity, Object value, Function<T, T> prototypeOpportunity )
+    {
+        EntityBuilder<?> builder = doConversion( entityType, identity, value );
+        prototypeOpportunity.map( (T) builder.instance() );
+        return createInstance( builder );
+    }
+
+    @Override
+    public <T> Iterable<T> create( final Class<T> entityType, final Iterable<Object> values )
+    {
+        return Iterables.map(
+            new Function<Object, T>()
+            {
+                @Override
+                public T map( Object value )
+                {
+                    return create( entityType, value );
+                }
+            },
+            values
+        );
+    }
+
+    @Override
+    public <T> Iterable<T> create( final Class<T> entityType,
+                                   final Iterable<Object> values,
+                                   final Function<T, T> prototypeOpportunity )
+    {
+        return Iterables.map(
+            new Function<Object, T>()
+            {
+                @Override
+                public T map( Object value )
+                {
+                    return create( entityType, value, prototypeOpportunity );
+                }
+            },
+            values
+        );
+    }
+
+    private <T> EntityBuilder<?> doConversion( Class<T> entityType, String identity, Object value )
+    {
+        EntityDescriptor eDesc = module.entityDescriptor( entityType.getName() );
+        if( eDesc == null )
+        {
+            throw new EntityTypeNotFoundException( entityType.getName() );
+        }
+
+        ValueComposite vComposite = (ValueComposite) value;
+
+        ValueDescriptor vDesc = spi.valueDescriptorFor( vComposite );
+        AssociationStateHolder vState = spi.stateOf( vComposite );
+        AssociationStateDescriptor vStateDesc = vDesc.state();
+
+        Unqualified unqualified = vDesc.metaInfo( Unqualified.class );
+        if( unqualified == null || !unqualified.value() )
+        {
+            return doQualifiedConversion( entityType, identity, vState, vStateDesc );
+        }
+        return doUnqualifiedConversion( entityType, identity, vState, vStateDesc );
+    }
+
+    private <T> EntityBuilder<?> doQualifiedConversion(
+        Class<T> entityType, String identity,
+        final AssociationStateHolder vState, final AssociationStateDescriptor vStateDesc
+    )
+    {
+        Function<PropertyDescriptor, Object> props
+            = new Function<PropertyDescriptor, Object>()
+            {
+                @Override
+                public Object map( PropertyDescriptor ePropDesc )
+                {
+                    try
+                    {
+                        return vState.propertyFor( ePropDesc.accessor() ).get();
+                    }
+                    catch( IllegalArgumentException propNotFoundOnValue )
+                    {
+                        // Property not found
+                        return null;
+                    }
+                }
+            };
+        Function<AssociationDescriptor, EntityReference> assocs
+            = new Function<AssociationDescriptor, EntityReference>()
+            {
+                @Override
+                public EntityReference map( AssociationDescriptor eAssocDesc )
+                {
+                    try
+                    {
+                        return EntityReference.entityReferenceFor( vState.associationFor( eAssocDesc.accessor() ) );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // Find String Property and convert to Association
+                        String propName = eAssocDesc.qualifiedName().name();
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( propName );
+                            if( STRING_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                String assocState = (String) vState.propertyFor( vPropDesc.accessor() ).get();
+                                return EntityReference.parseEntityReference( assocState );
+                            }
+                            return null;
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return null;
+                        }
+                    }
+                }
+            };
+        Function<AssociationDescriptor, Iterable<EntityReference>> manyAssocs
+            = new Function<AssociationDescriptor, Iterable<EntityReference>>()
+            {
+                @Override
+                public Iterable<EntityReference> map( AssociationDescriptor eAssocDesc )
+                {
+                    try
+                    {
+                        ManyAssociation<Object> vAssocState = vState.manyAssociationFor( eAssocDesc.accessor() );
+                        return MANY_ASSOC_TO_ENTITY_REF_ITERABLE.map( vAssocState );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // Find Collection<String> Property and convert to ManyAssociation
+                        String propName = eAssocDesc.qualifiedName().name();
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( propName );
+                            if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                Collection<String> vAssocState = (Collection) vState
+                                .propertyFor( vPropDesc.accessor() ).get();
+                                return STRING_COLLEC_TO_ENTITY_REF_ITERABLE.map( vAssocState );
+                            }
+                            return Iterables.empty();
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return Iterables.empty();
+                        }
+                    }
+                }
+            };
+        Function<AssociationDescriptor, Map<String, EntityReference>> namedAssocs
+            = new Function<AssociationDescriptor, Map<String, EntityReference>>()
+            {
+                @Override
+                public Map<String, EntityReference> map( AssociationDescriptor eAssocDesc )
+                {
+                    try
+                    {
+                        NamedAssociation<?> vAssocState = vState.namedAssociationFor( eAssocDesc.accessor() );
+                        return NAMED_ASSOC_TO_ENTITY_REF_MAP.map( vAssocState );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // Find Map<String,String> Property and convert to NamedAssociation
+                        String propName = eAssocDesc.qualifiedName().name();
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( propName );
+                            if( STRING_MAP_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                Map<String, String> vAssocState = (Map) vState
+                                .propertyFor( vPropDesc.accessor() ).get();
+                                return STRING_MAP_TO_ENTITY_REF_MAP.map( vAssocState );
+                            }
+                            return Collections.EMPTY_MAP;
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return Collections.EMPTY_MAP;
+                        }
+                    }
+                }
+            };
+        return module.currentUnitOfWork().newEntityBuilderWithState(
+            entityType, identity, props, assocs, manyAssocs, namedAssocs
+        );
+    }
+
+    private <T> EntityBuilder<?> doUnqualifiedConversion(
+        Class<T> entityType, String identity,
+        final AssociationStateHolder vState, final AssociationStateDescriptor vStateDesc
+    )
+    {
+        Function<PropertyDescriptor, Object> props
+            = new Function<PropertyDescriptor, Object>()
+            {
+                @Override
+                public Object map( PropertyDescriptor ePropDesc )
+                {
+                    String propName = ePropDesc.qualifiedName().name();
+                    try
+                    {
+                        PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( propName );
+                        return vState.propertyFor( vPropDesc.accessor() ).get();
+                    }
+                    catch( IllegalArgumentException propNotFoundOnValue )
+                    {
+                        // Property not found on Value
+                        return null;
+                    }
+                }
+            };
+        Function<AssociationDescriptor, EntityReference> assocs
+            = new Function<AssociationDescriptor, EntityReference>()
+            {
+                @Override
+                public EntityReference map( AssociationDescriptor eAssocDesc )
+                {
+                    String assocName = eAssocDesc.qualifiedName().name();
+                    try
+                    {
+                        AssociationDescriptor vAssocDesc = vStateDesc.getAssociationByName( assocName );
+                        Object assocEntity = vState.associationFor( vAssocDesc.accessor() ).get();
+                        return assocEntity == null ? null : EntityReference.entityReferenceFor( assocEntity );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // Association not found on Value, find Property<String> and convert to Association
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( assocName );
+                            if( STRING_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                String assocId = (String) vState.propertyFor( vPropDesc.accessor() ).get();
+                                return assocId == null ? null : EntityReference.parseEntityReference( assocId );
+                            }
+                            return null;
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return null;
+                        }
+                    }
+                }
+            };
+        Function<AssociationDescriptor, Iterable<EntityReference>> manyAssocs
+            = new Function<AssociationDescriptor, Iterable<EntityReference>>()
+            {
+                @Override
+                public Iterable<EntityReference> map( AssociationDescriptor eAssocDesc )
+                {
+                    String assocName = eAssocDesc.qualifiedName().name();
+                    try
+                    {
+                        AssociationDescriptor vAssocDesc = vStateDesc.getManyAssociationByName( assocName );
+                        ManyAssociation<Object> vManyAssoc = vState.manyAssociationFor( vAssocDesc.accessor() );
+                        return MANY_ASSOC_TO_ENTITY_REF_ITERABLE.map( vManyAssoc );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // ManyAssociation not found on Value, find List<String> and convert to ManyAssociation
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( assocName );
+                            if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                Collection<String> vAssocState = (Collection) vState
+                                .propertyFor( vPropDesc.accessor() ).get();
+                                return STRING_COLLEC_TO_ENTITY_REF_ITERABLE.map( vAssocState );
+                            }
+                            return Iterables.empty();
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return Iterables.empty();
+                        }
+                    }
+                }
+            };
+        Function<AssociationDescriptor, Map<String, EntityReference>> namedAssocs
+            = new Function<AssociationDescriptor, Map<String, EntityReference>>()
+            {
+                @Override
+                public Map<String, EntityReference> map( AssociationDescriptor eAssocDesc )
+                {
+                    String assocName = eAssocDesc.qualifiedName().name();
+                    try
+                    {
+                        AssociationDescriptor vAssocDesc = vStateDesc.getNamedAssociationByName( assocName );
+                        NamedAssociation<Object> vAssocState = vState.namedAssociationFor( vAssocDesc.accessor() );
+                        return NAMED_ASSOC_TO_ENTITY_REF_MAP.map( vAssocState );
+                    }
+                    catch( IllegalArgumentException assocNotFoundOnValue )
+                    {
+                        // Find Map<String,String> Property and convert to NamedAssociation
+                        try
+                        {
+                            PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( assocName );
+                            if( STRING_MAP_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                            {
+                                Map<String, String> vAssocState = (Map) vState
+                                .propertyFor( vPropDesc.accessor() ).get();
+                                return STRING_MAP_TO_ENTITY_REF_MAP.map( vAssocState );
+                            }
+                            return Collections.EMPTY_MAP;
+                        }
+                        catch( IllegalArgumentException propNotFoundOnValue )
+                        {
+                            return Collections.EMPTY_MAP;
+                        }
+                    }
+                }
+            };
+        return module.currentUnitOfWork().newEntityBuilderWithState(
+            entityType, identity, props, assocs, manyAssocs, namedAssocs
+        );
+    }
+
+    protected <T> T createInstance( EntityBuilder<?> builder )
+    {
+        return (T) builder.newInstance();
+    }
+
+    @Override
+    public void update( Object entity, Object value )
+        throws NoSuchEntityException
+    {
+        EntityComposite eComposite = (EntityComposite) entity;
+        ValueComposite vComposite = (ValueComposite) value;
+
+        EntityDescriptor eDesc = spi.entityDescriptorFor( eComposite );
+        AssociationStateHolder eState = spi.stateOf( eComposite );
+        AssociationStateDescriptor eStateDesc = eDesc.state();
+
+        ValueDescriptor vDesc = spi.valueDescriptorFor( vComposite );
+        AssociationStateHolder vState = spi.stateOf( vComposite );
+        AssociationStateDescriptor vStateDesc = vDesc.state();
+
+        Unqualified unqualified = vDesc.metaInfo( Unqualified.class );
+        if( unqualified == null || !unqualified.value() )
+        {
+            doQualifiedUpdate( eState, eStateDesc, vState, vStateDesc );
+        }
+        else
+        {
+            doUnQualifiedUpdate( eState, eStateDesc, vState, vStateDesc );
+        }
+    }
+
+    private void doQualifiedUpdate(
+        AssociationStateHolder eState, AssociationStateDescriptor eStateDesc,
+        AssociationStateHolder vState, AssociationStateDescriptor vStateDesc
+    )
+        throws NoSuchEntityException
+    {
+        for( PropertyDescriptor ePropDesc : eStateDesc.properties() )
+        {
+            if( IDENTITY_STATE_NAME.equals( ePropDesc.qualifiedName() ) )
+            {
+                // Ignore Identity, could be logged
+                continue;
+            }
+            try
+            {
+                PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByQualifiedName( ePropDesc.qualifiedName() );
+                eState.propertyFor( ePropDesc.accessor() ).set( vState.propertyFor( vPropDesc.accessor() ).get() );
+            }
+            catch( IllegalArgumentException propNotFoundOnValue )
+            {
+                // Property not found on Value, do nothing
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.associations() )
+        {
+            Association<Object> eAssoc = eState.associationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssocDesc
+                    = vStateDesc.getAssociationByQualifiedName( eAssocDesc.qualifiedName() );
+                eAssoc.set( vState.associationFor( vAssocDesc.accessor() ).get() );
+            }
+            catch( IllegalArgumentException assocNotFoundOnValue )
+            {
+                // Association not found on Value, find Property<String> and load associated Entity
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        String assocId = (String) vState.propertyFor( vPropDesc.accessor() ).get();
+                        if( assocId != null )
+                        {
+                            eAssoc.set( module.currentUnitOfWork().get( (Class) eAssocDesc.type(), assocId ) );
+                        }
+                        else
+                        {
+                            eAssoc.set( null );
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.manyAssociations() )
+        {
+            ManyAssociation<Object> eManyAssoc = eState.manyAssociationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssocDesc
+                    = vStateDesc.getManyAssociationByQualifiedName( eAssocDesc.qualifiedName() );
+                ManyAssociation<Object> vManyAssoc = vState.manyAssociationFor( vAssocDesc.accessor() );
+                for( Object assoc : eManyAssoc.toList() )
+                {
+                    eManyAssoc.remove( assoc );
+                }
+                for( Object assoc : vManyAssoc.toList() )
+                {
+                    eManyAssoc.add( assoc );
+                }
+            }
+            catch( IllegalArgumentException assocNotFoundOnValue )
+            {
+                // ManyAssociation not found on Value, find Property<List<String>> and load associated Entities
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        Collection<String> vAssocState = (Collection) vState.propertyFor( vPropDesc.accessor() ).get();
+                        for( Object assoc : eManyAssoc.toList() )
+                        {
+                            eManyAssoc.remove( assoc );
+                        }
+                        if( vAssocState != null )
+                        {
+                            for( String eachAssoc : vAssocState )
+                            {
+                                eManyAssoc.add(
+                                    module.currentUnitOfWork().get( (Class) eAssocDesc.type(), eachAssoc )
+                                );
+                            }
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.namedAssociations() )
+        {
+            NamedAssociation<Object> eNamedAssoc = eState.namedAssociationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssocDesc
+                    = vStateDesc.getNamedAssociationByQualifiedName( eAssocDesc.qualifiedName() );
+                NamedAssociation<Object> vNamedAssoc = vState.namedAssociationFor( vAssocDesc.accessor() );
+                for( String assocName : Iterables.toList( eNamedAssoc ) )
+                {
+                    eNamedAssoc.remove( assocName );
+                }
+                for( Map.Entry<String, Object> assocEntry : vNamedAssoc.toMap().entrySet() )
+                {
+                    eNamedAssoc.put( assocEntry.getKey(), assocEntry.getValue() );
+                }
+            }
+            catch( IllegalArgumentException assocNotFoundOnValue )
+            {
+                // NamedAssociation not found on Value, find Property<Map<String,String>> and load associated Entities
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_MAP_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        Map<String, String> vAssocState = (Map) vState.propertyFor( vPropDesc.accessor() ).get();
+                        for( String assocName : Iterables.toList( eNamedAssoc ) )
+                        {
+                            eNamedAssoc.remove( assocName );
+                        }
+                        if( vAssocState != null )
+                        {
+                            for( Map.Entry<String, String> assocEntry : vAssocState.entrySet() )
+                            {
+                                eNamedAssoc.put(
+                                    assocEntry.getKey(),
+                                    module.currentUnitOfWork().get( (Class) eAssocDesc.type(), assocEntry.getValue() )
+                                );
+                            }
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+    }
+
+    private void doUnQualifiedUpdate(
+        AssociationStateHolder eState, AssociationStateDescriptor eStateDesc,
+        AssociationStateHolder vState, AssociationStateDescriptor vStateDesc
+    )
+    {
+        for( PropertyDescriptor ePropDesc : eStateDesc.properties() )
+        {
+            if( IDENTITY_STATE_NAME.equals( ePropDesc.qualifiedName() ) )
+            {
+                // Ignore Identity, could be logged
+                continue;
+            }
+            try
+            {
+                PropertyDescriptor vPropDesc = vStateDesc.findPropertyModelByName( ePropDesc.qualifiedName().name() );
+                eState.propertyFor( ePropDesc.accessor() ).set( vState.propertyFor( vPropDesc.accessor() ).get() );
+            }
+            catch( IllegalArgumentException propNotFoundOnValue )
+            {
+                // Property not found on Value, do nothing
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.associations() )
+        {
+            Association<Object> eAssoc = eState.associationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssocDesc = vStateDesc.getAssociationByName( eAssocDesc.qualifiedName().name() );
+                eAssoc.set( vState.associationFor( vAssocDesc.accessor() ).get() );
+            }
+            catch( IllegalArgumentException assocNotFoundOnValue )
+            {
+                // Association not found on Value, find Property<String> and load associated Entity
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        String assocId = (String) vState.propertyFor( vPropDesc.accessor() ).get();
+                        if( assocId != null )
+                        {
+                            eAssoc.set( module.currentUnitOfWork().get( (Class) eAssocDesc.type(), assocId ) );
+                        }
+                        else
+                        {
+                            eAssoc.set( null );
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.manyAssociations() )
+        {
+            ManyAssociation<Object> eManyAssoc = eState.manyAssociationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssDesc
+                    = vStateDesc.getManyAssociationByName( eAssocDesc.qualifiedName().name() );
+                ManyAssociation<Object> vManyAss = vState.manyAssociationFor( vAssDesc.accessor() );
+                for( Object ass : eManyAssoc.toList() )
+                {
+                    eManyAssoc.remove( ass );
+                }
+                for( Object ass : vManyAss.toList() )
+                {
+                    eManyAssoc.add( ass );
+                }
+            }
+            catch( IllegalArgumentException assNotFoundOnValue )
+            {
+                // ManyAssociation not found on Value, find Property<List<String>> and load associated Entities
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_COLLECTION_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        Collection<String> vAssocState = (Collection) vState.propertyFor( vPropDesc.accessor() ).get();
+                        for( Object ass : eManyAssoc.toList() )
+                        {
+                            eManyAssoc.remove( ass );
+                        }
+                        if( vAssocState != null )
+                        {
+                            for( String eachAssoc : vAssocState )
+                            {
+                                eManyAssoc.add(
+                                    module.currentUnitOfWork().get( (Class) eAssocDesc.type(), eachAssoc )
+                                );
+                            }
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+        for( AssociationDescriptor eAssocDesc : eStateDesc.namedAssociations() )
+        {
+            NamedAssociation<Object> eNamedAssoc = eState.namedAssociationFor( eAssocDesc.accessor() );
+            try
+            {
+                AssociationDescriptor vAssocDesc
+                    = vStateDesc.getNamedAssociationByName( eAssocDesc.qualifiedName().name() );
+                NamedAssociation<Object> vNamedAssoc = vState.namedAssociationFor( vAssocDesc.accessor() );
+                for( String assocName : Iterables.toList( eNamedAssoc ) )
+                {
+                    eNamedAssoc.remove( assocName );
+                }
+                for( Map.Entry<String, Object> assocEntry : vNamedAssoc.toMap().entrySet() )
+                {
+                    eNamedAssoc.put( assocEntry.getKey(), assocEntry.getValue() );
+                }
+            }
+            catch( IllegalArgumentException assocNotFoundOnValue )
+            {
+                // NamedAssociation not found on Value, find Property<Map<String,String>> and load associated Entities
+                try
+                {
+                    PropertyDescriptor vPropDesc
+                        = vStateDesc.findPropertyModelByName( eAssocDesc.qualifiedName().name() );
+                    if( STRING_MAP_TYPE_SPEC.satisfiedBy( vPropDesc.valueType() ) )
+                    {
+                        Map<String, String> vAssocState = (Map) vState.propertyFor( vPropDesc.accessor() ).get();
+                        for( String assocName : Iterables.toList( eNamedAssoc ) )
+                        {
+                            eNamedAssoc.remove( assocName );
+                        }
+                        if( vAssocState != null )
+                        {
+                            for( Map.Entry<String, String> assocEntry : vAssocState.entrySet() )
+                            {
+                                eNamedAssoc.put(
+                                    assocEntry.getKey(),
+                                    module.currentUnitOfWork().get( (Class) eAssocDesc.type(), assocEntry.getValue() )
+                                );
+                            }
+                        }
+                    }
+                }
+                catch( IllegalArgumentException propNotFoundOnValue )
+                {
+                    // Do nothing
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityService.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityService.java b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityService.java
new file mode 100644
index 0000000..824c1d6
--- /dev/null
+++ b/libraries/conversion/src/main/java/org/qi4j/library/conversion/values/ValueToEntityService.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2014-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import org.qi4j.api.mixin.Mixins;
+
+/**
+ * Service that creates or updates Entities from matching Values.
+ */
+@Mixins( ValueToEntityMixin.class )
+public interface ValueToEntityService
+    extends ValueToEntity
+{
+}

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java b/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
index 7f29eb3..a6e980c 100644
--- a/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
+++ b/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/EntityToValueTest.java
@@ -1,5 +1,7 @@
 /*
- * Copyright 2010 Niclas Hedhman.
+ * Copyright 2010-2012 Niclas Hedhman.
+ * Copyright 2011 Rickard Öberg.
+ * Copyright 2013-2015 Paul Merlin.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,36 +19,30 @@
  */
 package org.qi4j.library.conversion.values;
 
-import java.util.Calendar;
 import java.util.Date;
-import java.util.List;
-import java.util.TimeZone;
 import org.junit.Test;
-import org.qi4j.api.association.Association;
-import org.qi4j.api.association.ManyAssociation;
-import org.qi4j.api.common.Optional;
 import org.qi4j.api.constraint.ConstraintViolationException;
-import org.qi4j.api.entity.EntityBuilder;
-import org.qi4j.api.entity.EntityComposite;
-import org.qi4j.api.injection.scope.This;
-import org.qi4j.api.mixin.Mixins;
-import org.qi4j.api.property.Property;
 import org.qi4j.api.service.ServiceReference;
 import org.qi4j.api.unitofwork.UnitOfWork;
 import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
-import org.qi4j.api.value.ValueComposite;
 import org.qi4j.bootstrap.AssemblyException;
 import org.qi4j.bootstrap.ModuleAssembly;
 import org.qi4j.functional.Function;
+import org.qi4j.library.conversion.values.TestModel.PersonEntity;
+import org.qi4j.library.conversion.values.TestModel.PersonValue;
+import org.qi4j.library.conversion.values.TestModel.PersonValue2;
+import org.qi4j.library.conversion.values.TestModel.PersonValue3;
+import org.qi4j.library.conversion.values.TestModel.PersonValue4;
 import org.qi4j.test.AbstractQi4jTest;
 import org.qi4j.test.EntityTestAssembler;
 
 import static org.junit.Assert.assertEquals;
+import static org.qi4j.library.conversion.values.TestModel.createBirthDate;
+import static org.qi4j.library.conversion.values.TestModel.createPerson;
 
 public class EntityToValueTest
     extends AbstractQi4jTest
 {
-
     @Override
     public void assemble( ModuleAssembly module )
         throws AssemblyException
@@ -191,7 +187,7 @@ public class EntityToValueTest
         }
     }
 
-    private PersonEntity setupPersonEntities( UnitOfWork uow )
+    private static PersonEntity setupPersonEntities( UnitOfWork uow )
     {
         PersonEntity niclas = createNiclas( uow );
         PersonEntity lis = createLis( uow );
@@ -209,7 +205,7 @@ public class EntityToValueTest
         return niclas;
     }
 
-    private PersonEntity createNiclas( UnitOfWork uow )
+    private static PersonEntity createNiclas( UnitOfWork uow )
     {
         String firstName = "Niclas";
         String lastName = "Hedhman";
@@ -217,7 +213,7 @@ public class EntityToValueTest
         return createPerson( uow, firstName, lastName, birthTime );
     }
 
-    private PersonEntity createLis( UnitOfWork uow )
+    private static PersonEntity createLis( UnitOfWork uow )
     {
         String firstName = "Lis";
         String lastName = "Gazi";
@@ -225,169 +221,11 @@ public class EntityToValueTest
         return createPerson( uow, firstName, lastName, birthTime );
     }
 
-    private PersonEntity createEric( UnitOfWork uow )
+    private static PersonEntity createEric( UnitOfWork uow )
     {
         String firstName = "Eric";
         String lastName = "Hedman";
         Date birthTime = createBirthDate( 2004, 4, 8 );
         return createPerson( uow, firstName, lastName, birthTime );
     }
-
-    private PersonEntity createPerson( UnitOfWork uow, String firstName, String lastName, Date birthTime )
-    {
-        EntityBuilder<PersonEntity> builder = uow.newEntityBuilder( PersonEntity.class, "id:" + firstName );
-        PersonState state = builder.instanceFor( PersonState.class );
-        state.firstName().set( firstName );
-        state.lastName().set( lastName );
-        state.dateOfBirth().set( birthTime );
-        return builder.newInstance();
-    }
-
-    private Date createBirthDate( int year, int month, int day )
-    {
-        Calendar calendar = Calendar.getInstance();
-        calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
-        calendar.set( year, month - 1, day, 12, 0, 0 );
-        return calendar.getTime();
-    }
-
-    // START SNIPPET: state
-    public interface PersonState
-    {
-
-        Property<String> firstName();
-
-        Property<String> lastName();
-
-        Property<Date> dateOfBirth();
-
-    }
-    // END SNIPPET: state
-
-    // START SNIPPET: value
-    public interface PersonValue
-        extends PersonState, ValueComposite
-    {
-
-        @Optional
-        Property<String> spouse();
-
-        @Optional
-        Property<List<String>> children();
-
-    }
-    // END SNIPPET: value
-
-    // START SNIPPET: entity
-    @Mixins( PersonMixin.class )
-    public interface PersonEntity
-        extends EntityComposite
-    {
-
-        String firstName();
-
-        String lastName();
-
-        Integer age();
-
-        @Optional
-        Association<PersonEntity> spouse();
-
-        ManyAssociation<PersonEntity> children();
-
-    }
-    // END SNIPPET: entity
-
-    // START SNIPPET: entity
-    public static abstract class PersonMixin
-        implements PersonEntity
-    {
-
-        @This
-        private PersonState state;
-        // END SNIPPET: entity
-
-        @Override
-        public String firstName()
-        {
-            return state.firstName().get();
-        }
-
-        @Override
-        public String lastName()
-        {
-            return state.lastName().get();
-        }
-
-        @Override
-        public Integer age()
-        {
-            long now = System.currentTimeMillis();
-            long birthdate = state.dateOfBirth().get().getTime();
-            return (int) ( ( now - birthdate ) / 1000 / 3600 / 24 / 365.25 );
-        }
-
-        // START SNIPPET: entity
-    }
-    // END SNIPPET: entity
-
-    // START SNIPPET: unqualified
-    @Unqualified
-    public interface PersonValue2
-        extends ValueComposite
-    {
-
-        Property<String> firstName();
-
-        Property<String> lastName();
-
-        Property<Date> dateOfBirth();
-
-        @Optional
-        Property<String> spouse();
-
-        @Optional
-        Property<List<String>> children();
-
-    }
-    // END SNIPPET: unqualified
-
-    @Unqualified( true )
-    public interface PersonValue3
-        extends ValueComposite
-    {
-
-        Property<String> firstName();
-
-        Property<String> lastName();
-
-        Property<Date> dateOfBirth();
-
-        @Optional
-        Property<String> spouse();
-
-        @Optional
-        Property<List<String>> children();
-
-    }
-
-    @Unqualified( false )
-    public interface PersonValue4
-        extends ValueComposite
-    {
-
-        Property<String> firstName();
-
-        Property<String> lastName();
-
-        Property<Date> dateOfBirth();
-
-        @Optional
-        Property<String> spouse();
-
-        @Optional
-        Property<List<String>> children();
-
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/378e5406/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
----------------------------------------------------------------------
diff --git a/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java b/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
new file mode 100644
index 0000000..1361380
--- /dev/null
+++ b/libraries/conversion/src/test/java/org/qi4j/library/conversion/values/TestModel.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010-2012 Niclas Hedhman.
+ * Copyright 2011 Rickard Öberg.
+ * Copyright 2013-2015 Paul Merlin.
+ *
+ * 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.library.conversion.values;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+import org.qi4j.api.association.Association;
+import org.qi4j.api.association.ManyAssociation;
+import org.qi4j.api.common.Optional;
+import org.qi4j.api.entity.EntityBuilder;
+import org.qi4j.api.entity.EntityComposite;
+import org.qi4j.api.injection.scope.This;
+import org.qi4j.api.mixin.Mixins;
+import org.qi4j.api.property.Property;
+import org.qi4j.api.unitofwork.UnitOfWork;
+import org.qi4j.api.value.ValueComposite;
+
+/**
+ * Test Model.
+ */
+final class TestModel
+{
+    static PersonEntity createPerson( UnitOfWork uow, String firstName, String lastName, Date birthTime )
+    {
+        EntityBuilder<PersonEntity> builder = uow.newEntityBuilder( PersonEntity.class, "id:" + firstName );
+        PersonState state = builder.instanceFor( PersonState.class );
+        state.firstName().set( firstName );
+        state.lastName().set( lastName );
+        state.dateOfBirth().set( birthTime );
+        return builder.newInstance();
+    }
+
+    static Date createBirthDate( int year, int month, int day )
+    {
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
+        calendar.set( year, month - 1, day, 12, 0, 0 );
+        return calendar.getTime();
+    }
+
+    // START SNIPPET: state
+    public interface PersonState
+    {
+
+        Property<String> firstName();
+
+        Property<String> lastName();
+
+        Property<Date> dateOfBirth();
+
+    }
+    // END SNIPPET: state
+
+    // START SNIPPET: value
+    public interface PersonValue
+        extends PersonState, ValueComposite
+    {
+
+        @Optional
+        Property<String> spouse();
+
+        @Optional
+        Property<List<String>> children();
+
+    }
+    // END SNIPPET: value
+
+    // START SNIPPET: entity
+    @Mixins( PersonMixin.class )
+    public interface PersonEntity
+        extends EntityComposite
+    {
+
+        String firstName();
+
+        String lastName();
+
+        Integer age();
+
+        @Optional
+        Association<PersonEntity> spouse();
+
+        ManyAssociation<PersonEntity> children();
+
+    }
+    // END SNIPPET: entity
+
+    // START SNIPPET: entity
+    public static abstract class PersonMixin
+        implements PersonEntity
+    {
+
+        @This
+        private PersonState state;
+        // END SNIPPET: entity
+
+        @Override
+        public String firstName()
+        {
+            return state.firstName().get();
+        }
+
+        @Override
+        public String lastName()
+        {
+            return state.lastName().get();
+        }
+
+        @Override
+        public Integer age()
+        {
+            long now = System.currentTimeMillis();
+            long birthdate = state.dateOfBirth().get().getTime();
+            return (int) ( ( now - birthdate ) / 1000 / 3600 / 24 / 365.25 );
+        }
+
+        // START SNIPPET: entity
+    }
+    // END SNIPPET: entity
+
+    // START SNIPPET: unqualified
+    @Unqualified
+    public interface PersonValue2
+        extends ValueComposite
+    {
+
+        Property<String> firstName();
+
+        Property<String> lastName();
+
+        Property<Date> dateOfBirth();
+
+        @Optional
+        Property<String> spouse();
+
+        @Optional
+        Property<List<String>> children();
+
+    }
+    // END SNIPPET: unqualified
+
+    @Unqualified( true )
+    public interface PersonValue3
+        extends ValueComposite
+    {
+
+        Property<String> firstName();
+
+        Property<String> lastName();
+
+        Property<Date> dateOfBirth();
+
+        @Optional
+        Property<String> spouse();
+
+        @Optional
+        Property<List<String>> children();
+
+    }
+
+    @Unqualified( false )
+    public interface PersonValue4
+        extends ValueComposite
+    {
+
+        Property<String> firstName();
+
+        Property<String> lastName();
+
+        Property<Date> dateOfBirth();
+
+        @Optional
+        Property<String> spouse();
+
+        @Optional
+        Property<List<String>> children();
+
+    }
+
+    private TestModel()
+    {
+    }
+}