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 <[email protected]> Authored: Wed Mar 25 16:56:02 2015 +0100 Committer: Paul Merlin <[email protected]> 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() + { + } +}
