Skip to content

Commit

Permalink
Merge pull request #17 from pith/assembler-dsl-with-tuple#14
Browse files Browse the repository at this point in the history
Added support for tuple in the assembler DSL
  • Loading branch information
adrienlauer committed Apr 17, 2015
2 parents a393a4d + 756f94b commit 35274f7
Show file tree
Hide file tree
Showing 28 changed files with 885 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,10 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.seedstack.business.api.domain.Factory;
import org.seedstack.business.api.domain.Repository;
import org.seedstack.business.api.interfaces.assembler.FluentAssembler;
import org.seedstack.business.api.interfaces.assembler.dsl.AggregateNotFoundException;
import org.seedstack.business.core.interfaces.assembler.dsl.fixture.Customer;
import org.seedstack.business.core.interfaces.assembler.dsl.fixture.Order;
import org.seedstack.business.core.interfaces.assembler.dsl.fixture.OrderDto;
import org.seedstack.business.core.interfaces.assembler.dsl.fixture.OrderFactory;
import org.seedstack.business.api.Tuples;
import org.seedstack.business.core.interfaces.assembler.dsl.fixture.*;
import org.seedstack.seed.it.SeedITRunner;

import javax.inject.Inject;
Expand All @@ -41,48 +36,51 @@ public class AssemblerDslWithTupleIT {
private Repository<Order, String> orderRepository;

@Inject
private OrderFactory orderFactory;
private CustomerRepository customerRepository;

@Inject
private Factory<Customer> customerFactory;
private OrderFactory orderFactory;

@Inject
private FluentAssembler fluently;

@Test
@Ignore
public void testAssembleFromFactory() {
OrderDto orderDto = new OrderDto("1", "light saber");
orderDto.setCustomerName("luke");
Recipe recipe = new Recipe("customer1", "luke", "order1", "light saber");

Pair<Order, Customer> orderCustomerClasses = Tuples.create(Order.class, Customer.class);
Pair<Order, Customer> orderCustomerPair = fluently.assemble().dto(orderDto).to(orderCustomerClasses).fromFactory();
Pair<Order, Customer> orderCustomerPair = fluently.assemble().dto(recipe).<Pair<Order, Customer>>to(Order.class, Customer.class).fromFactory();

Assertions.assertThat(orderCustomerPair.getValue0()).isNotNull();
Assertions.assertThat(orderCustomerPair.getValue0().getEntityId()).isEqualTo("1");
Assertions.assertThat(orderCustomerPair.getValue0().getEntityId()).isEqualTo("order1");
Assertions.assertThat(orderCustomerPair.getValue0().getProduct()).isEqualTo("light saber");
// the customer name is not part of the factory parameters, so it is set by the assembler
Assertions.assertThat(orderCustomerPair.getValue1().getEntityId()).isEqualTo("luke");
Assertions.assertThat(orderCustomerPair.getValue1().getEntityId()).isEqualTo("customer1");
Assertions.assertThat(orderCustomerPair.getValue1().getName()).isEqualTo("luke");
}

@Test
@Ignore
public void testAssembleFromRepository() {
Order order = orderFactory.create("1", "death star");
Recipe recipe = new Recipe("customer1", "luke", "order1", "light saber");

Order order = orderFactory.create("order1", "death star");
order.setOtherDetails("some details");
orderRepository.persist(order);

Order aggregateRoot = null;
Customer customer = new Customer("customer1");
customerRepository.persist(customer);

Pair<Order, Customer> orderCustomerPair = null;
try {
aggregateRoot = fluently.assemble().dto(new OrderDto("1", "light saber")).to(Order.class).fromRepository().orFail();
orderCustomerPair = fluently.assemble().dto(recipe).<Pair<Order, Customer>>to(Order.class, Customer.class).fromRepository().orFail();
} catch (AggregateNotFoundException e) {
fail();
}
Assertions.assertThat(aggregateRoot).isNotNull();
Assertions.assertThat(aggregateRoot.getEntityId()).isEqualTo("1");
Assertions.assertThat(aggregateRoot.getProduct()).isEqualTo("light saber");
// other details come from the aggregate loaded from the repository
Assertions.assertThat(aggregateRoot.getOtherDetails()).isEqualTo("some details");
Assertions.assertThat(orderCustomerPair.getValue0()).isNotNull();
Assertions.assertThat(orderCustomerPair.getValue0().getEntityId()).isEqualTo("order1");
Assertions.assertThat(orderCustomerPair.getValue0().getProduct()).isEqualTo("light saber");
// the customer name is not part of the factory parameters, so it is set by the assembler
Assertions.assertThat(orderCustomerPair.getValue1().getEntityId()).isEqualTo("customer1");
Assertions.assertThat(orderCustomerPair.getValue1().getName()).isEqualTo("luke");
}

@Test
Expand Down
4 changes: 4 additions & 0 deletions core/src/it/resources/logback-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<logger name="org.reflections" level="OFF"/>
<logger name="io.nuun" level="WARN"/>

<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import javax.inject.Inject;
import java.lang.reflect.Constructor;
import java.util.Arrays;

/**
* FactoryInternal allows the creations of {@link org.seedstack.business.api.domain.DomainObject} objects using their constructors.
Expand Down Expand Up @@ -79,17 +80,17 @@ public DO create(Object... args) {
Constructor<?> constructor = MethodMatcher.findMatchingConstructor(getProducedClass(), args);
DO domainObject;
if (constructor == null) {
throw SeedException.createNew(DomainErrorCodes.DOMAIN_OBJECT_CONSTRUCTOR_NOT_FOUND)
.put("domainObject", getProducedClass().getSimpleName()).put("parameters", args);
}
try {
throw SeedException.createNew(DomainErrorCodes.DOMAIN_OBJECT_CONSTRUCTOR_NOT_FOUND)
.put("domainObject", getProducedClass()).put("parameters", Arrays.toString(args));
}
try {
constructor.setAccessible(true);
//noinspection unchecked
domainObject = (DO) constructor.newInstance(args);

} catch (Exception e) {
throw SeedException.wrap(e, DomainErrorCodes.UNABLE_TO_INVOKE_CONSTRUCTOR).put("constructor", constructor)
.put("domainObject", getProducedClass().getSimpleName()).put("parameters", args);
.put("domainObject", getProducedClass()).put("parameters", Arrays.toString(args));
}
return domainObject;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
package org.seedstack.business.core.interfaces.assembler.dsl;

import org.javatuples.Tuple;
import org.seedstack.business.api.Tuples;
import org.seedstack.business.api.domain.AggregateRoot;
import org.seedstack.business.api.interfaces.assembler.Assembler;
import org.seedstack.business.api.interfaces.assembler.dsl.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
Expand All @@ -36,8 +39,12 @@ public <A extends AggregateRoot<?>> AggAssemblerWithRepoProvider<A> to(Class<A>
}

@Override
public <T extends Tuple> TupleAggAssemblerWithRepoProvider<T> to(T aggregateRootTuple) {
assemblerContext.setAggregateClasses(aggregateRootTuple);
public <T extends Tuple> TupleAggAssemblerWithRepoProvider<T> to(Class<? extends AggregateRoot<?>> firstAggregateClass, Class<? extends AggregateRoot<?>> secondAggregateClass, Class<? extends AggregateRoot<?>>... otherAggregateClasses) {
List<Class<?>> aggregateRootClasses = new ArrayList<Class<?>>();
aggregateRootClasses.add(firstAggregateClass);
aggregateRootClasses.add(secondAggregateClass);
aggregateRootClasses.addAll(Arrays.asList(otherAggregateClasses));
assemblerContext.setAggregateClasses(Tuples.create((List)aggregateRootClasses));
return new TupleAggAssemblerWithRepoProviderImpl<T>(registry, assemblerContext);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@

import org.seedstack.business.api.domain.AggregateRoot;
import org.seedstack.business.api.domain.GenericFactory;
import org.seedstack.business.api.domain.Repository;
import org.seedstack.business.api.interfaces.assembler.Assembler;
import org.seedstack.business.api.interfaces.assembler.dsl.AggAssemblerWithRepoAndFactProvider;
import org.seedstack.business.api.interfaces.assembler.dsl.AggAssemblerWithRepoProvider;
import org.seedstack.business.api.interfaces.assembler.dsl.AggregateNotFoundException;
import org.seedstack.business.api.interfaces.assembler.resolver.ParameterHolder;

/**
* @author Pierre Thirouin <pierre.thirouin@ext.mpsa.com>
Expand All @@ -36,10 +39,24 @@ public AggAssemblerWithRepoAndFactProvider<A> fromRepository() {
@Override
public A fromFactory() {
GenericFactory<A> genericFactory = (GenericFactory<A>) registry.genericFactoryOf(assemblerContext.getAggregateClass());
A aggregateRoot = (A) getAggregateFromFactory(genericFactory, assemblerContext.getAggregateClass());
ParameterHolder parameterHolder = dtoInfoResolver.resolveAggregate(assemblerContext.getDto());
A aggregateRoot = (A) getAggregateFromFactory(genericFactory, assemblerContext.getAggregateClass(), parameterHolder.parameters());
return assembleWithDto(aggregateRoot);
}

/**
* Loads an aggregate roots from a repository.
*
* @param key the aggregate roots identity
* @param <A> the aggregate root type
* @return the loaded aggregate root
*/
protected <A> A loadFromRepo(Object key) {
Repository repository = assemblerContext.getRepository();
//noinspection unchecked
return (A) repository.load(key);
}

// --------------------------- AggAssemblerWithRepoAndFactProvider methods

@Override
Expand Down Expand Up @@ -69,4 +86,18 @@ public A thenFromFactory() {
}
}

/**
* Assemble one or a tuple of aggregate root from a dto.
*
* @param aggregateRoots the aggregate root(s) to assemble
* @param <T> type of aggregate root(s). It could be a {@code Tuple} or an {@code AggregateRoot}
* @return the assembled aggregate root(s)
*/
protected <T> T assembleWithDto(T aggregateRoots) {
Assembler assembler = registry.assemblerOf(assemblerContext.getAggregateClass(), assemblerContext.getDto().getClass());
//noinspection unchecked
assembler.mergeAggregateWithDto(aggregateRoots, assemblerContext.getDto());
return aggregateRoots;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
*/
package org.seedstack.business.core.interfaces.assembler.dsl;

import org.javatuples.Tuple;
import org.seedstack.business.api.Tuples;
import org.seedstack.business.api.domain.*;
import org.seedstack.business.api.interfaces.assembler.Assembler;
import org.seedstack.business.api.interfaces.assembler.resolver.DtoInfoResolver;
import org.seedstack.business.api.interfaces.assembler.resolver.ParameterHolder;
import org.seedstack.business.core.interfaces.assembler.resolver.AnnotationResolver;
Expand All @@ -20,7 +21,9 @@

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
* @author Pierre Thirouin <pierre.thirouin@ext.mpsa.com>
Expand All @@ -42,88 +45,94 @@ public BaseAggAssemblerWithRepoProviderImpl(InternalRegistry registry, Assembler
this.assemblerContext = assemblerContext;
}

/**
* Assemble one or a tuple of aggregate root from a dto.
*
* @param aggregateRoots the aggregate root(s) to assemble
* @param <T> type of aggregate root(s). It could be a {@code Tuple} or an {@code AggregateRoot}
* @return the assembled aggregate root(s)
*/
protected <T> T assembleWithDto(T aggregateRoots) {
Assembler assembler = registry.assemblerOf(assemblerContext.getAggregateClass(), assemblerContext.getDto().getClass());
//noinspection unchecked
assembler.mergeAggregateWithDto(aggregateRoots, assemblerContext.getDto());
return aggregateRoots;
}
protected Object resolveId(Object dto, Class<? extends AggregateRoot<?>> aggregateRootClass) {
SeedCheckUtils.checkIfNotNull(dto);
SeedCheckUtils.checkIfNotNull(aggregateRootClass);

/**
* Loads an aggregate roots from a repository.
*
* @param key the aggregate roots identity
* @param <A> the aggregate root type
* @return the loaded aggregate root
*/
protected <A> A loadFromRepo(Object key) {
Repository repository = assemblerContext.getRepository();
//noinspection unchecked
return (A) repository.load(key);
ParameterHolder parameterHolder = dtoInfoResolver.resolveId(dto);
if (parameterHolder.isEmpty()) {
throw new IllegalArgumentException("No id found in the DTO. Please check the @MatchingEntityId annotation.");
}

return paramsToIds(aggregateRootClass, parameterHolder, -1);
}

protected Object resolveId(Object dto, Class<? extends AggregateRoot<?>> aggregateRootClass) {
Object id;
protected Tuple resolveIds(Object dto, Tuple aggregateRootClasses) {
SeedCheckUtils.checkIfNotNull(dto);
SeedCheckUtils.checkIfNotNull(aggregateRootClasses);

ParameterHolder parameterHolder = dtoInfoResolver.resolveId(dto);
if (parameterHolder.isEmpty()) {
throw new IllegalArgumentException("No id found in the DTO. Please check the @MatchingEntityId annotation.");
}

List<Object> ids = new ArrayList<Object>();
int aggregateIndex = 0;
for (Object aggregateRootClass : aggregateRootClasses) {
//noinspection unchecked
ids.add(paramsToIds((Class<? extends AggregateRoot<?>>) aggregateRootClass, parameterHolder, aggregateIndex));
aggregateIndex++;
}

return Tuples.create(ids);
}

private Object paramsToIds(Class<? extends AggregateRoot<?>> aggregateRootClass, ParameterHolder parameterHolder, int aggregateIndex) {
Object id;

//noinspection unchecked
Class<? extends DomainObject> aggregateIdClass = (Class<? extends DomainObject>) BusinessUtils.getAggregateIdClass(aggregateRootClass);
// TODO <pith> : check the case when one of the parameters is null
if (parameterHolder.first() != null && aggregateIdClass.isAssignableFrom(parameterHolder.first().getClass())) {

Object element = parameterHolder.uniqueElementForAggregateRoot(aggregateIndex);
if (element != null && aggregateIdClass.isAssignableFrom(element.getClass())) {
// The first parameter is already the id we are looking for
id = parameterHolder.first();
id = element;
} else {
if (!ValueObject.class.isAssignableFrom(aggregateIdClass)) {
throw new IllegalStateException("The " + aggregateRootClass.getCanonicalName() + "'s id is not a value object, so you don't have to specify the index in @MatchingEntityId(index = 0)");
}
// Get the "magic" factory for the aggregate id class
Factory<?> factory = registry.defaultFactoryOf(aggregateIdClass);
// Create the id based on the id constructor matching the given parameters
id = factory.create(parameterHolder.parameters());
// TODO <pith> : what if there is an actual factory for this value object ?
// TODO <pith> : check the case when one of the parameters is null
id = factory.create(parameterHolder.parametersOfAggregateRoot(aggregateIndex));
}

if (id == null) {
throw new IllegalArgumentException("No id found in the DTO. Please check the @MatchingEntityId annotation.");
}

return id;
}

protected Object getAggregateFromFactory(GenericFactory<?> factory, Class<? extends AggregateRoot<?>> aggregateClass) {
protected Object getAggregateFromFactory(GenericFactory<?> factory, Class<? extends AggregateRoot<?>> aggregateClass, Object[] parameters) {
SeedCheckUtils.checkIfNotNull(factory);
SeedCheckUtils.checkIfNotNull(aggregateClass);
SeedCheckUtils.checkIfNotNull(parameters);

// Extract the factory parameters from the DTO (using @MatchingFactoryParameter)
ParameterHolder parameterHolder = dtoInfoResolver.resolveAggregate(assemblerContext.getDto());
if (parameterHolder.isEmpty()) {
throw new IllegalArgumentException("No factory parameters found in the DTO. Please check the @MatchingFactoryParameter annotation.");
}

// Find the method in the factory which match the signature determined with the previously extracted parameters
Method factoryMethod = MethodMatcher.findMatchingMethod(factory.getClass(), aggregateClass, parameterHolder.parameters());
if (factoryMethod == null) {
throw new IllegalStateException(factory.getClass().getSimpleName() +
": Enable to find a method matching the parameter [" +
Arrays.toString(parameterHolder.parameters()) + "]");
if (parameters.length == 0) {
throw new IllegalArgumentException(assemblerContext.getDto().getClass() + " - No factory parameters found in the DTO. Please check the @MatchingFactoryParameter annotation.");
}

// Invoke the factory to create the aggregate root
try {
//noinspection unchecked
return factoryMethod.invoke(factory, parameterHolder.parameters());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Failed to call " + factoryMethod.getName(), e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Failed to call " + factoryMethod.getName(), e);
if (Factory.class.isAssignableFrom(factory.getClass())) {
Factory<?> defaultFactory = (Factory<?>) factory;
return defaultFactory.create(parameters);
} else {
// Find the method in the factory which match the signature determined with the previously extracted parameters
Method factoryMethod = MethodMatcher.findMatchingMethod(factory.getClass(), aggregateClass, parameters);
if (factoryMethod == null) {
throw new IllegalStateException(factory.getClass().getSimpleName() +
" - Enable to find a method matching the parameters " +
Arrays.toString(parameters));
}

// Invoke the factory to create the aggregate root
try {
//noinspection unchecked
return factoryMethod.invoke(factory, parameters);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Failed to call " + factoryMethod.getName(), e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Failed to call " + factoryMethod.getName(), e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ public InternalRegistryInternal(Injector injector) {
public Assembler<?, ?> tupleAssemblerOf(Tuple aggregateRootTuple, Class<?> dto) {
List<Class<? extends AggregateRoot<?>>> aggregateClasses = Lists.newArrayList();
for (Object o : aggregateRootTuple) {
if (!(o instanceof AggregateRoot<?>)) {
if (!(o instanceof Class<?>) || !AggregateRoot.class.isAssignableFrom((Class)o)) {
throw new IllegalArgumentException("The aggregateRootTuple parameter should only contain aggregates. But found " + o);
}
aggregateClasses.add((Class<? extends AggregateRoot<?>>) o.getClass());
aggregateClasses.add((Class<? extends AggregateRoot<?>>) o);
}
return tupleAssemblerOf(aggregateClasses, dto);
}
Expand Down
Loading

0 comments on commit 35274f7

Please sign in to comment.