Repository: zest-qi4j Updated Branches: refs/heads/ZEST-22_toEntity-toValue [created] 591562568
Implemented ZEST-22, but still have a test failure. This failure seems unrelated. Project: http://git-wip-us.apache.org/repos/asf/zest-qi4j/repo Commit: http://git-wip-us.apache.org/repos/asf/zest-qi4j/commit/a36aba1f Tree: http://git-wip-us.apache.org/repos/asf/zest-qi4j/tree/a36aba1f Diff: http://git-wip-us.apache.org/repos/asf/zest-qi4j/diff/a36aba1f Branch: refs/heads/ZEST-22_toEntity-toValue Commit: a36aba1fe72f9009bd6a76294617355f930d4f6e Parents: e691796 Author: Niclas Hedhman <[email protected]> Authored: Sat May 16 10:45:40 2015 +0800 Committer: Niclas Hedhman <[email protected]> Committed: Sat May 16 10:45:40 2015 +0800 ---------------------------------------------------------------------- core/api/src/main/java/org/qi4j/api/Qi4j.java | 84 +++++- .../java/org/qi4j/runtime/Qi4jRuntimeImpl.java | 257 ++++++++++++++++++- .../association/NamedAssociationInstance.java | 51 ++++ .../composite/FunctionStateResolver.java | 33 +++ .../unitofwork/EntityBuilderInstance.java | 26 +- .../runtime/value/ValueWithAssociationTest.java | 132 ++++++++++ .../spi/src/main/java/org/qi4j/spi/Qi4jSPI.java | 30 +++ .../qi4j/spi/entity/NamedAssociationState.java | 1 - 8 files changed, 585 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/api/src/main/java/org/qi4j/api/Qi4j.java ---------------------------------------------------------------------- diff --git a/core/api/src/main/java/org/qi4j/api/Qi4j.java b/core/api/src/main/java/org/qi4j/api/Qi4j.java index 317153c..d8f2d29 100644 --- a/core/api/src/main/java/org/qi4j/api/Qi4j.java +++ b/core/api/src/main/java/org/qi4j/api/Qi4j.java @@ -25,6 +25,7 @@ import org.qi4j.api.composite.InvalidCompositeException; import org.qi4j.api.composite.ModelDescriptor; import org.qi4j.api.composite.TransientDescriptor; import org.qi4j.api.entity.EntityDescriptor; +import org.qi4j.api.entity.Identity; import org.qi4j.api.property.Property; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.api.service.ServiceDescriptor; @@ -42,8 +43,9 @@ public interface Qi4j * then that reference must be dereferenced using this method * before handing it out for others to use. * - * @param <T> Parameterized type of the Composite + * @param <T> Parameterized type of the Composite * @param composite instance reference injected in Modified using @This + * * @return the dereferenced Composite */ <T> T dereference( T composite ); @@ -53,6 +55,7 @@ public interface Qi4j * * @param compositeOrUow The Composite (Service, Value, Entity or Transient) or UnitOfWork to lookup the Module it * belongs to. + * * @return The Module instance where the Composite or UnitOfWork belongs to. */ Module moduleOf( Object compositeOrUow ); @@ -62,6 +65,7 @@ public interface Qi4j * * @param compositeOrServiceReference The Composite (Service, Value, Entity or Transient) for which to lookup the * ModelDescriptor + * * @return The ModelDescriptor of the Composite */ ModelDescriptor modelDescriptorFor( Object compositeOrServiceReference ); @@ -71,6 +75,7 @@ public interface Qi4j * * @param compositeOrServiceReference The Composite (Service, Value, Entity or Transient) for which to lookup the * CompositeDescriptor + * * @return The CompositeDescriptor of the Composite */ CompositeDescriptor compositeDescriptorFor( Object compositeOrServiceReference ); @@ -79,6 +84,7 @@ public interface Qi4j * Returns the TransientDescriptor of the TransientComposite. * * @param transsient The TransientComposite for which to lookup the TransientDescriptor + * * @return The TransientDescriptor of the TransientComposite */ TransientDescriptor transientDescriptorFor( Object transsient ); @@ -87,6 +93,7 @@ public interface Qi4j * Returns the EntityDescriptor of the EntityComposite. * * @param entity The EntityComposite for which to lookup the EntityDescriptor + * * @return The EntityDescriptor of the EntityComposite */ EntityDescriptor entityDescriptorFor( Object entity ); @@ -95,6 +102,7 @@ public interface Qi4j * Returns the ValueDescriptor of the ValueComposite. * * @param value The ValueComposite for which to lookup the ValueDescriptor + * * @return The ValueDescriptor of the ValueComposite */ ValueDescriptor valueDescriptorFor( Object value ); @@ -103,6 +111,7 @@ public interface Qi4j * Returns the ServiceDescriptor of the ServiceComposite. * * @param service The ServiceComposite for which to lookup the ServiceDescriptor + * * @return The ServiceDescriptor of the ServiceComposite */ ServiceDescriptor serviceDescriptorFor( Object service ); @@ -111,6 +120,7 @@ public interface Qi4j * Returns the PropertyDescriptor of the Property. * * @param property The Property for which to lookup the PropertyDescriptor + * * @return The PropertyDescriptor of the Property */ PropertyDescriptor propertyDescriptorFor( Property<?> property ); @@ -119,11 +129,83 @@ public interface Qi4j * Returns the AssociationDescriptor of the Association. * * @param association The Association for which to lookup the AssociationDescriptor + * * @return The AssociationDescriptor of the Association */ AssociationDescriptor associationDescriptorFor( AbstractAssociation association ); /** + * Converts the provided Entity to a Value of the same type. + * This is a convenience method to convert an EntityComposite to a ValueComposite. + * <p/> + * All Property values are transferred across as-is, and the Association, ManyAssociation + * and NamedAssociatino values are kept in the ValueComposite as EntityReferences + * until they are dereferenced (get() and other methods), and IF a UnitOfWork is + * present at dereferencing the corresponding EntityCompoiste is retrieved from the + * EntityStore. If there is not an UnitOfWork present, an exception is thrown. + * <p/> + * For this to work, the Composites (both Entity and Value) must not declare the + * EntityComposite and ValueComposite super types, but rely on the declaration in + * the assembly, and also extend the Identity supertype. + * + * Example; + * <pre><code> + * public interface Person extends Identity { ... }; + * public class MyAssembler + * { + * public void assemble( ModuleAssembly module ) + * { + * module.values( Person.class ); + * module.entities( Person.class ); + * } + * } + * </code></pre> + * + * @param primaryType The shared type for which the properties and associations will + * be converted. Properties outside this type will be ignored. + * @param entityComposite The entity to be convered. + */ + <T extends Identity> T toValue( Class<T> primaryType, T entityComposite ); + + /** + * Converts the provided Value to an Entity of the same type. + * This is a convenience method to convert a ValueComposite to an EntityComposite. + * <p/> + * All Property values are transferred across as-is (no deep copy in case mutable + * types (DISCOURAGED!) are used), and the Association, ManyAssociation + * and NamedAssociatino that were in the ValueComposite as EntityReferences are + * transferred into the EntityComposite correctly, and can be dereferenced. + * <p/> + * This method MUST be called within a UnitOfWork. + * <p/> + * If an Entity with the Identity in the ValueComposite already exists, then that + * Entity is updated with the values from the ValueComposite. If an Entity of + * that Identity doesn't exist and new one is created. + * <p/> + * For this to work, the Composites (both Entity and Value) must not declare the + * EntityComposite and ValueComposite super types, but rely on the declaration in + * the assembly, and also extend the Identity supertype. + * + * Example; + * <pre><code> + * public interface Person extends Identity { ... }; + * public class MyAssembler + * { + * public void assemble( ModuleAssembly module ) + * { + * module.values( Person.class ); + * module.entities( Person.class ); + * } + * } + * </code></pre> + * + * @param primaryType The shared type for which the properties and associations will + * be converted. Properties outside this type will be ignored. + * @param valueComposite The Value to be convered into an Entity. + */ + <T extends Identity> T toEntity( Class<T> primaryType, T valueComposite ); + + /** * Function that returns the CompositeDescriptor of a Composite. */ Function<Composite, CompositeDescriptor> FUNCTION_DESCRIPTOR_FOR = new Function<Composite, CompositeDescriptor>() http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/runtime/src/main/java/org/qi4j/runtime/Qi4jRuntimeImpl.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/qi4j/runtime/Qi4jRuntimeImpl.java b/core/runtime/src/main/java/org/qi4j/runtime/Qi4jRuntimeImpl.java index 737e6bd..9b5dce7 100644 --- a/core/runtime/src/main/java/org/qi4j/runtime/Qi4jRuntimeImpl.java +++ b/core/runtime/src/main/java/org/qi4j/runtime/Qi4jRuntimeImpl.java @@ -15,12 +15,17 @@ package org.qi4j.runtime; import java.lang.reflect.InvocationHandler; import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import org.qi4j.api.Qi4j; import org.qi4j.api.association.AbstractAssociation; +import org.qi4j.api.association.Association; import org.qi4j.api.association.AssociationDescriptor; import org.qi4j.api.association.AssociationStateHolder; import org.qi4j.api.association.AssociationWrapper; +import org.qi4j.api.association.ManyAssociation; import org.qi4j.api.association.ManyAssociationWrapper; +import org.qi4j.api.association.NamedAssociation; import org.qi4j.api.association.NamedAssociationWrapper; import org.qi4j.api.composite.Composite; import org.qi4j.api.composite.CompositeDescriptor; @@ -28,8 +33,11 @@ import org.qi4j.api.composite.CompositeInstance; import org.qi4j.api.composite.ModelDescriptor; import org.qi4j.api.composite.TransientComposite; import org.qi4j.api.composite.TransientDescriptor; +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.property.Property; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.api.property.PropertyWrapper; @@ -38,18 +46,26 @@ import org.qi4j.api.service.ServiceComposite; import org.qi4j.api.service.ServiceDescriptor; import org.qi4j.api.service.ServiceReference; import org.qi4j.api.structure.Module; +import org.qi4j.api.unitofwork.NoSuchEntityException; import org.qi4j.api.unitofwork.UnitOfWork; +import org.qi4j.api.value.ValueBuilder; import org.qi4j.api.value.ValueComposite; import org.qi4j.api.value.ValueDescriptor; import org.qi4j.bootstrap.ApplicationAssemblyFactory; import org.qi4j.bootstrap.ApplicationModelFactory; import org.qi4j.bootstrap.Qi4jRuntime; +import org.qi4j.functional.Function; import org.qi4j.runtime.association.AbstractAssociationInstance; +import org.qi4j.runtime.association.AssociationInstance; +import org.qi4j.runtime.association.ManyAssociationInstance; +import org.qi4j.runtime.association.NamedAssociationInstance; import org.qi4j.runtime.bootstrap.ApplicationAssemblyFactoryImpl; import org.qi4j.runtime.bootstrap.ApplicationModelFactoryImpl; +import org.qi4j.runtime.composite.FunctionStateResolver; import org.qi4j.runtime.composite.ProxyReferenceInvocationHandler; import org.qi4j.runtime.composite.TransientInstance; import org.qi4j.runtime.entity.EntityInstance; +import org.qi4j.runtime.entity.EntityModel; import org.qi4j.runtime.property.PropertyInstance; import org.qi4j.runtime.service.ImportedServiceReferenceInstance; import org.qi4j.runtime.service.ServiceInstance; @@ -58,6 +74,7 @@ import org.qi4j.runtime.structure.ModuleUnitOfWork; import org.qi4j.runtime.value.ValueInstance; import org.qi4j.spi.Qi4jSPI; import org.qi4j.spi.entity.EntityState; +import org.qi4j.spi.entity.NamedAssociationState; import static java.lang.reflect.Proxy.getInvocationHandler; import static org.qi4j.runtime.composite.TransientInstance.compositeInstanceOf; @@ -160,7 +177,7 @@ public final class Qi4jRuntimeImpl else if( compositeOrServiceReferenceOrUow instanceof ImportedServiceReferenceInstance ) { ImportedServiceReferenceInstance<?> importedServiceReference - = (ImportedServiceReferenceInstance<?>) compositeOrServiceReferenceOrUow; + = (ImportedServiceReferenceInstance<?>) compositeOrServiceReferenceOrUow; return importedServiceReference.module(); } throw new IllegalArgumentException( "Wrong type. Must be one of " @@ -205,7 +222,7 @@ public final class Qi4jRuntimeImpl else if( compositeOrServiceReference instanceof ImportedServiceReferenceInstance ) { ImportedServiceReferenceInstance<?> importedServiceReference - = (ImportedServiceReferenceInstance<?>) compositeOrServiceReference; + = (ImportedServiceReferenceInstance<?>) compositeOrServiceReference; return importedServiceReference.serviceDescriptor(); } throw new IllegalArgumentException( "Wrong type. Must be one of " @@ -312,7 +329,7 @@ public final class Qi4jRuntimeImpl { association = ( (ManyAssociationWrapper) association ).next(); } - + while( association instanceof NamedAssociationWrapper ) { association = ( (NamedAssociationWrapper) association ).next(); @@ -321,10 +338,244 @@ public final class Qi4jRuntimeImpl return (AssociationDescriptor) ( (AbstractAssociationInstance) association ).associationInfo(); } + @Override + public <T extends Identity> T toValue( Class<T> primaryType, T entityComposite ) + { + EntityDescriptor entityDescriptor = entityDescriptorFor( entityComposite ); + Function<PropertyDescriptor, Object> propertyFunction = new ToValuePropertyMappingFunction<T>( entityComposite ); + Function<AssociationDescriptor, EntityReference> assocationFunction = new ToValueAssociationMappingFunction<T>( entityComposite ); + Function<AssociationDescriptor, Iterable<EntityReference>> manyAssocFunction = new ToValueManyAssociationMappingFunction<T>( entityComposite ); + Function<AssociationDescriptor, Map<String, EntityReference>> namedAssocFunction = new ToValueNameAssociationMappingFunction<T>( entityComposite ); + + @SuppressWarnings( "unchecked" ) + ValueBuilder<T> builder = moduleOf( entityComposite ).newValueBuilderWithState( + (Class<T>) entityDescriptor.primaryType(), propertyFunction, assocationFunction, manyAssocFunction, namedAssocFunction ); + return builder.newInstance(); + } + + @Override + public <T extends Identity> T toEntity( Class<T> primaryType, T valueComposite ) + { + Function<PropertyDescriptor, Object> propertyFunction = new ToEntityPropertyMappingFunction<T>( valueComposite ); + Function<AssociationDescriptor, EntityReference> assocationFunction = new ToEntityAssociationMappingFunction<T>( valueComposite ); + Function<AssociationDescriptor, Iterable<EntityReference>> manyAssocFunction = new ToEntityManyAssociationMappingFunction<T>( valueComposite ); + Function<AssociationDescriptor, Map<String, EntityReference>> namedAssocFunction = new ToEntityNameAssociationMappingFunction<T>( valueComposite ); + + UnitOfWork uow = moduleOf( valueComposite ).currentUnitOfWork(); + String identity = valueComposite.identity().get(); + try + { + T entity = uow.get( primaryType, identity ); + // If successful, then this entity is to by modified. + EntityInstance instance = EntityInstance.entityInstanceOf( (EntityComposite) entity ); + EntityState state = instance.entityState(); + FunctionStateResolver stateResolver = new FunctionStateResolver( propertyFunction, + assocationFunction, + manyAssocFunction, + namedAssocFunction ); + EntityModel model = (EntityModel) EntityInstance.entityInstanceOf( (EntityComposite) entity ).descriptor(); + stateResolver.populateState( model, state ); + return entity; + } + catch( NoSuchEntityException e ) + { + EntityBuilder<T> entityBuilder = uow.newEntityBuilderWithState( primaryType, + identity, + propertyFunction, + assocationFunction, + manyAssocFunction, + namedAssocFunction ); + return entityBuilder.newInstance(); + } + } + // SPI @Override public EntityState entityStateOf( EntityComposite composite ) { return EntityInstance.entityInstanceOf( composite ).entityState(); } + + @Override + public EntityReference entityReferenceOf( Association assoc ) + { + @SuppressWarnings( "unchecked" ) + Property<EntityReference> associationState = ( (AssociationInstance) assoc ).getAssociationState(); + return associationState.get(); + } + + @Override + public Iterable<EntityReference> entityReferenceOf( ManyAssociation assoc ) + { + return ( (ManyAssociationInstance) assoc ).getManyAssociationState(); + } + + @Override + public Iterable<Map.Entry<String, EntityReference>> entityReferenceOf( NamedAssociation assoc ) + { + return ( (NamedAssociationInstance) assoc ).getEntityReferences(); + } + + private class ToValuePropertyMappingFunction<T> + implements Function<PropertyDescriptor, Object> + { + private Object entity; + + public ToValuePropertyMappingFunction( Object entity ) + { + this.entity = entity; + } + + @Override + public Object map( PropertyDescriptor propertyDescriptor ) + { + EntityState entityState = entityStateOf( (EntityComposite) entity ); + return entityState.propertyValueOf( propertyDescriptor.qualifiedName() ); + } + } + + private class ToValueAssociationMappingFunction<T> + implements Function<AssociationDescriptor, EntityReference> + { + private final T entity; + + public ToValueAssociationMappingFunction( T entity ) + { + this.entity = entity; + } + + @Override + public EntityReference map( AssociationDescriptor associationDescriptor ) + { + EntityState entityState = entityStateOf( (EntityComposite) entity ); + return entityState.associationValueOf( associationDescriptor.qualifiedName() ); + } + } + + private class ToValueManyAssociationMappingFunction<T> + implements Function<AssociationDescriptor, Iterable<EntityReference>> + { + private final T entity; + + public ToValueManyAssociationMappingFunction( T entity ) + { + this.entity = entity; + } + + @Override + public Iterable<EntityReference> map( AssociationDescriptor associationDescriptor ) + { + EntityState entityState = entityStateOf( (EntityComposite) entity ); + return entityState.manyAssociationValueOf( associationDescriptor.qualifiedName() ); + } + } + + private class ToValueNameAssociationMappingFunction<T> + implements Function<AssociationDescriptor, Map<String, EntityReference>> + { + private final T entity; + + public ToValueNameAssociationMappingFunction( T entity ) + { + this.entity = entity; + } + + @Override + public Map<String, EntityReference> map( AssociationDescriptor associationDescriptor ) + { + Map<String, EntityReference> result = new HashMap<>(); + EntityState entityState = entityStateOf( (EntityComposite) entity ); + final NamedAssociationState state = entityState.namedAssociationValueOf( associationDescriptor.qualifiedName() ); + for( String name : state ) + { + result.put( name, state.get( name ) ); + } + return result; + } + } + + private class ToEntityPropertyMappingFunction<T> + implements Function<PropertyDescriptor, Object> + { + private final T value; + + public ToEntityPropertyMappingFunction( T value ) + { + this.value = value; + } + + @Override + public Object map( PropertyDescriptor propertyDescriptor ) + { + StateHolder state = stateOf( (ValueComposite) value ); + Property<Object> property = state.propertyFor( propertyDescriptor.accessor() ); + return property.get(); + } + } + + private class ToEntityAssociationMappingFunction<T> + implements Function<AssociationDescriptor, EntityReference> + { + + private final T value; + + public ToEntityAssociationMappingFunction( T value ) + { + this.value = value; + } + + @Override + public EntityReference map( AssociationDescriptor associationDescriptor ) + { + AssociationStateHolder state = stateOf( (ValueComposite) value ); + AssociationInstance<T> association = (AssociationInstance<T>) state.associationFor( associationDescriptor.accessor() ); + return association.getAssociationState().get(); + } + } + + private class ToEntityManyAssociationMappingFunction<T> + implements Function<AssociationDescriptor, Iterable<EntityReference>> + { + + private final T value; + + public ToEntityManyAssociationMappingFunction( T value ) + { + this.value = value; + } + + @Override + public Iterable<EntityReference> map( AssociationDescriptor associationDescriptor ) + { + AssociationStateHolder state = stateOf( (ValueComposite) value ); + ManyAssociationInstance<T> association = (ManyAssociationInstance<T>) state.manyAssociationFor( associationDescriptor + .accessor() ); + return association.getManyAssociationState(); + } + } + + private class ToEntityNameAssociationMappingFunction<T> + implements Function<AssociationDescriptor, Map<String, EntityReference>> + { + private final T value; + + public ToEntityNameAssociationMappingFunction( T value ) + { + this.value = value; + } + + @Override + public Map<String, EntityReference> map( AssociationDescriptor associationDescriptor ) + { + AssociationStateHolder state = stateOf( (ValueComposite) value ); + NamedAssociationInstance<T> association = (NamedAssociationInstance<T>) state.namedAssociationFor( associationDescriptor + .accessor() ); + HashMap<String, EntityReference> result = new HashMap<>(); + for( Map.Entry<String, EntityReference> entry : association.getEntityReferences() ) + { + result.put( entry.getKey(), entry.getValue() ); + } + return result; + } + } } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/runtime/src/main/java/org/qi4j/runtime/association/NamedAssociationInstance.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/qi4j/runtime/association/NamedAssociationInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/association/NamedAssociationInstance.java index 4ca67b2..284de5d 100644 --- a/core/runtime/src/main/java/org/qi4j/runtime/association/NamedAssociationInstance.java +++ b/core/runtime/src/main/java/org/qi4j/runtime/association/NamedAssociationInstance.java @@ -24,7 +24,9 @@ import java.util.Iterator; import java.util.Map; import org.qi4j.api.association.NamedAssociation; import org.qi4j.api.entity.EntityReference; +import org.qi4j.functional.Function; import org.qi4j.functional.Function2; +import org.qi4j.functional.Iterables; import org.qi4j.runtime.composite.ConstraintsCheck; import org.qi4j.spi.entity.NamedAssociationState; @@ -100,4 +102,53 @@ public class NamedAssociationInstance<T> return map; } + public Iterable<Map.Entry<String,EntityReference>> getEntityReferences() + { + return Iterables.map( new Function<String, Map.Entry<String,EntityReference>>() + { + @Override + public Map.Entry<String, EntityReference> map( final String key ) + { + final EntityReference value = namedAssociationState.get( key ); + return new Map.Entry<String, EntityReference>() + { + @Override + public String getKey() + { + return key; + } + + @Override + public EntityReference getValue() + { + return value; + } + + @Override + public EntityReference setValue( EntityReference value ) + { + throw new UnsupportedOperationException( "Immutable Map" ); + } + + @Override + public boolean equals( Object o ) + { + if( o instanceof Map.Entry) + { + Map.Entry other = (Map.Entry) o; + return key.equals(other.getKey()); + } + return false; + } + + @Override + public int hashCode() + { + return 997 * key.hashCode() + 981813497; + } + }; + } + }, namedAssociationState ); + } + } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/runtime/src/main/java/org/qi4j/runtime/composite/FunctionStateResolver.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/qi4j/runtime/composite/FunctionStateResolver.java b/core/runtime/src/main/java/org/qi4j/runtime/composite/FunctionStateResolver.java index 284929d..0217ed0 100644 --- a/core/runtime/src/main/java/org/qi4j/runtime/composite/FunctionStateResolver.java +++ b/core/runtime/src/main/java/org/qi4j/runtime/composite/FunctionStateResolver.java @@ -22,6 +22,10 @@ import org.qi4j.api.entity.EntityReference; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.functional.Function; import org.qi4j.functional.Iterables; +import org.qi4j.runtime.association.ManyAssociationModel; +import org.qi4j.runtime.association.NamedAssociationModel; +import org.qi4j.runtime.entity.EntityModel; +import org.qi4j.spi.entity.EntityState; /** * Function based StateResolver. @@ -68,4 +72,33 @@ public class FunctionStateResolver { return namedAssociationFunction.map( associationDescriptor ); } + + public void populateState( EntityModel model, EntityState state ) + { + for( PropertyDescriptor propDesc : model.state().properties() ) + { + Object value = getPropertyState( propDesc ); + state.setPropertyValue( propDesc.qualifiedName(), value ); + } + for( AssociationDescriptor assDesc : model.state().associations() ) + { + EntityReference ref = getAssociationState( assDesc ); + state.setAssociationValue( assDesc.qualifiedName(), ref ); + } + for( ManyAssociationModel manyAssDesc : model.state().manyAssociations() ) + { + for( EntityReference ref : getManyAssociationState( manyAssDesc ) ) + { + state.manyAssociationValueOf( manyAssDesc.qualifiedName() ).add( 0, ref ); + } + } + for( NamedAssociationModel namedAssDesc : model.state().namedAssociations() ) + { + for( Map.Entry<String, EntityReference> entry : getNamedAssociationState( namedAssDesc ).entrySet() ) + { + state.namedAssociationValueOf( namedAssDesc.qualifiedName() ).put( entry.getKey(), entry.getValue() ); + } + } + } + } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/runtime/src/main/java/org/qi4j/runtime/unitofwork/EntityBuilderInstance.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/qi4j/runtime/unitofwork/EntityBuilderInstance.java b/core/runtime/src/main/java/org/qi4j/runtime/unitofwork/EntityBuilderInstance.java index 8bb0ffd..95c2e92 100644 --- a/core/runtime/src/main/java/org/qi4j/runtime/unitofwork/EntityBuilderInstance.java +++ b/core/runtime/src/main/java/org/qi4j/runtime/unitofwork/EntityBuilderInstance.java @@ -26,6 +26,7 @@ import org.qi4j.api.entity.LifecycleException; import org.qi4j.api.property.PropertyDescriptor; import org.qi4j.runtime.association.ManyAssociationModel; import org.qi4j.runtime.association.NamedAssociationModel; +import org.qi4j.runtime.composite.FunctionStateResolver; import org.qi4j.runtime.entity.EntityInstance; import org.qi4j.runtime.entity.EntityModel; import org.qi4j.runtime.structure.ModelModule; @@ -91,30 +92,7 @@ public final class EntityBuilderInstance<T> model.model().initState( model.module(), entityState ); if( stateResolver != null ) { - for( PropertyDescriptor propDesc : model.model().state().properties() ) - { - Object value = stateResolver.getPropertyState( propDesc ); - entityState.setPropertyValue( propDesc.qualifiedName(), value ); - } - for( AssociationDescriptor assDesc : model.model().state().associations() ) - { - EntityReference ref = stateResolver.getAssociationState( assDesc ); - entityState.setAssociationValue( assDesc.qualifiedName(), ref ); - } - for( ManyAssociationModel manyAssDesc : model.model().state().manyAssociations() ) - { - for( EntityReference ref : stateResolver.getManyAssociationState( manyAssDesc ) ) - { - entityState.manyAssociationValueOf( manyAssDesc.qualifiedName() ).add( 0, ref ); - } - } - for( NamedAssociationModel namedAssDesc : model.model().state().namedAssociations() ) - { - for( Map.Entry<String, EntityReference> entry : stateResolver.getNamedAssociationState( namedAssDesc ).entrySet() ) - { - entityState.namedAssociationValueOf( namedAssDesc.qualifiedName() ).put( entry.getKey(), entry.getValue() ); - } - } + (( FunctionStateResolver) stateResolver).populateState( model.model(), entityState ); } entityState.setPropertyValue( IDENTITY_STATE_NAME, identity ); prototypeInstance = model.model().newInstance( uow, model.module(), entityState ); http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/runtime/src/test/java/org/qi4j/runtime/value/ValueWithAssociationTest.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/test/java/org/qi4j/runtime/value/ValueWithAssociationTest.java b/core/runtime/src/test/java/org/qi4j/runtime/value/ValueWithAssociationTest.java new file mode 100644 index 0000000..2f0fd2c --- /dev/null +++ b/core/runtime/src/test/java/org/qi4j/runtime/value/ValueWithAssociationTest.java @@ -0,0 +1,132 @@ +package org.qi4j.runtime.value; + +import org.junit.Ignore; +import org.junit.Test; +import org.qi4j.api.association.Association; +import org.qi4j.api.association.AssociationStateHolder; +import org.qi4j.api.association.ManyAssociation; +import org.qi4j.api.association.NamedAssociation; +import org.qi4j.api.entity.EntityBuilder; +import org.qi4j.api.entity.EntityComposite; +import org.qi4j.api.entity.EntityReference; +import org.qi4j.api.entity.Identity; +import org.qi4j.api.property.Property; +import org.qi4j.api.unitofwork.UnitOfWork; +import org.qi4j.api.unitofwork.UnitOfWorkCompletionException; +import org.qi4j.api.value.ValueBuilder; +import org.qi4j.api.value.ValueSerialization; +import org.qi4j.bootstrap.AssemblyException; +import org.qi4j.bootstrap.ModuleAssembly; +import org.qi4j.entitystore.memory.MemoryEntityStoreService; +import org.qi4j.spi.uuid.UuidIdentityGeneratorService; +import org.qi4j.test.AbstractQi4jTest; +import org.qi4j.valueserialization.orgjson.OrgJsonValueSerializationService; + +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; + +public class ValueWithAssociationTest extends AbstractQi4jTest +{ + @Override + public void assemble( ModuleAssembly module ) + throws AssemblyException + { + module.entities( SimpleEntity.class ); + module.entities( DualFaced.class ); + module.values( DualFaced.class ); + module.services( MemoryEntityStoreService.class ); + module.services( UuidIdentityGeneratorService.class ); + module.services( OrgJsonValueSerializationService.class ).taggedWith( ValueSerialization.Formats.JSON ); + } + + @Test @Ignore + public void givenEntityInStoreWhenFetchEntityReferenceExpectSuccess() + throws UnitOfWorkCompletionException + { + String identity1; + String identity2; + DualFaced value; + try (UnitOfWork uow = module.newUnitOfWork()) + { + EntityBuilder<SimpleEntity> builder1 = uow.newEntityBuilder( SimpleEntity.class ); + builder1.instance().name().set( "Niclas" ); + SimpleEntity simpleEntity = builder1.newInstance(); + identity1 = simpleEntity.identity().get(); + + EntityBuilder<DualFaced> builder2 = uow.newEntityBuilder( DualFaced.class ); + DualFaced proto = builder2.instance(); + proto.name().set( "Hedhman" ); + proto.simple().set( simpleEntity ); + proto.simples().add( simpleEntity ); + proto.namedSimples().put( "niclas", simpleEntity ); + DualFaced faced = builder2.newInstance(); + identity2 = faced.identity().get(); + value = spi.toValue( DualFaced.class, faced ); + assertThat( value.identity().get(), equalTo( identity2 ) ); + uow.complete(); + } + + try (UnitOfWork uow = module.newUnitOfWork()) + { + DualFaced entity = uow.get( DualFaced.class, identity2 ); + AssociationStateHolder holder = spi.stateOf( (EntityComposite) entity ); + Association<?> simple = holder.allAssociations().iterator().next(); + ManyAssociation<?> simples = holder.allManyAssociations().iterator().next(); + NamedAssociation<?> namedSimples = holder.allNamedAssociations().iterator().next(); + + assertThat( spi.entityReferenceOf( simple ), equalTo( EntityReference.parseEntityReference( identity1 ) ) ); + assertThat( spi.entityReferenceOf( simples ) + .iterator() + .next(), equalTo( EntityReference.parseEntityReference( identity1 ) ) ); + assertThat( spi.entityReferenceOf( namedSimples ) + .iterator() + .next() + .getValue(), equalTo( EntityReference.parseEntityReference( identity1 ) ) ); + + DualFaced resurrected = spi.toEntity( DualFaced.class, value ); + assertThat( resurrected.simple(), equalTo( entity.simple() ) ); + assertThat( resurrected.simples(), equalTo( entity.simples() ) ); + assertThat( resurrected.namedSimples(), equalTo( entity.namedSimples() ) ); + } + } + + @Test + public void givenNewValueWhenConvertingToEntityExpectNewEntityInStore() + throws UnitOfWorkCompletionException + { + ValueBuilder<DualFaced> builder = module.newValueBuilder( DualFaced.class ); + builder.prototype().identity().set( "1234" ); + builder.prototype().name().set( "Hedhman" ); + DualFaced value = builder.newInstance(); + + try (UnitOfWork uow = module.newUnitOfWork()) + { + spi.toEntity( DualFaced.class, value ); + uow.complete(); + } + + try (UnitOfWork uow = module.newUnitOfWork()) + { + DualFaced entity = uow.get( DualFaced.class, "1234" ); + assertThat( entity.identity().get(), equalTo( "1234" ) ); + assertThat( entity.name().get(), equalTo( "Hedhman" ) ); + uow.complete(); + } + } + + public interface SimpleEntity extends Identity + { + Property<String> name(); + } + + public interface DualFaced extends Identity + { + Property<String> name(); + + Association<SimpleEntity> simple(); + + ManyAssociation<SimpleEntity> simples(); + + NamedAssociation<SimpleEntity> namedSimples(); + } +} http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/spi/src/main/java/org/qi4j/spi/Qi4jSPI.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/qi4j/spi/Qi4jSPI.java b/core/spi/src/main/java/org/qi4j/spi/Qi4jSPI.java index 2586d95..cff2507 100644 --- a/core/spi/src/main/java/org/qi4j/spi/Qi4jSPI.java +++ b/core/spi/src/main/java/org/qi4j/spi/Qi4jSPI.java @@ -14,10 +14,15 @@ package org.qi4j.spi; +import java.util.Map; import org.qi4j.api.Qi4j; +import org.qi4j.api.association.Association; import org.qi4j.api.association.AssociationStateHolder; +import org.qi4j.api.association.ManyAssociation; +import org.qi4j.api.association.NamedAssociation; import org.qi4j.api.composite.TransientComposite; import org.qi4j.api.entity.EntityComposite; +import org.qi4j.api.entity.EntityReference; import org.qi4j.api.property.StateHolder; import org.qi4j.api.value.ValueComposite; import org.qi4j.spi.entity.EntityState; @@ -36,4 +41,29 @@ public interface Qi4jSPI // Entities EntityState entityStateOf( EntityComposite composite ); + + /** + * Fetches the EntityReference without loading the referenced entity. + * + * @param assoc The Association for which we want to obtain the EntityReference + * @return The EntityReference of the given Association. + */ + EntityReference entityReferenceOf( Association assoc ); + + /** + * Fetches the EntityReferences without loading the referenced entities. + * + * @param assoc The ManyAssociation for which we want to obtain the EntityReferences. + * @return An Iteranble of all the EntityReferences of the given ManyAssociation. + */ + Iterable<EntityReference> entityReferenceOf( ManyAssociation assoc ); + + /** + * Fetches the EntityReferences without loading the referenced entities. + * + * @param assoc The NamedAssociation for which we want to obtain the EntityReference + * @return An Iteranble of Map.Entry with the name and EntityReference of the given NamedAssociation. + */ + Iterable<Map.Entry<String,EntityReference>> entityReferenceOf( NamedAssociation assoc ); + } http://git-wip-us.apache.org/repos/asf/zest-qi4j/blob/a36aba1f/core/spi/src/main/java/org/qi4j/spi/entity/NamedAssociationState.java ---------------------------------------------------------------------- diff --git a/core/spi/src/main/java/org/qi4j/spi/entity/NamedAssociationState.java b/core/spi/src/main/java/org/qi4j/spi/entity/NamedAssociationState.java index 5cf3257..584feee 100644 --- a/core/spi/src/main/java/org/qi4j/spi/entity/NamedAssociationState.java +++ b/core/spi/src/main/java/org/qi4j/spi/entity/NamedAssociationState.java @@ -40,5 +40,4 @@ public interface NamedAssociationState EntityReference get( String name ); String nameOf( EntityReference entityReference ); - }
