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()
+ {
+ }
+}