/* * Copyright (c) 2010-2016. Axon Framework * 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.axonframework.commandhandling; import org.axonframework.commandhandling.model.Aggregate; import org.axonframework.commandhandling.model.Repository; import org.axonframework.commandhandling.model.inspection.AggregateModel; import org.axonframework.commandhandling.model.inspection.CommandMessageHandlingMember; import org.axonframework.commandhandling.model.inspection.ModelInspector; import org.axonframework.common.Assert; import org.axonframework.common.Registration; import org.axonframework.messaging.MessageHandler; import org.axonframework.messaging.annotation.ClasspathParameterResolverFactory; import org.axonframework.messaging.annotation.MessageHandlingMember; import org.axonframework.messaging.annotation.ParameterResolverFactory; import java.util.*; /** * Command handler that handles commands based on {@link CommandHandler} * annotations on an aggregate. Those annotations may appear on methods, in which case a specific aggregate instance * needs to be targeted by the command, or on the constructor. The latter will create a new Aggregate instance, which * is then stored in the repository. * * @param <T> the type of aggregate this handler handles commands for * @author Allard Buijze * @since 1.2 */ public class AggregateAnnotationCommandHandler<T> implements MessageHandler<CommandMessage<?>>, SupportedCommandNamesAware { private final Repository<T> repository; private final CommandTargetResolver commandTargetResolver; private final Map<String, MessageHandler<CommandMessage<?>>> handlers; /** * Initializes an AnnotationCommandHandler based on the annotations on given {@code aggregateType}, using the * given {@code repository} to add and load aggregate instances. * * @param aggregateType The type of aggregate * @param repository The repository providing access to aggregate instances */ public AggregateAnnotationCommandHandler(Class<T> aggregateType, Repository<T> repository) { this(aggregateType, repository, new AnnotationCommandTargetResolver()); } /** * Initializes an AnnotationCommandHandler based on the annotations on given {@code aggregateType}, using the * given {@code repository} to add and load aggregate instances and the default ParameterResolverFactory. * * @param aggregateType The type of aggregate * @param repository The repository providing access to aggregate instances * @param commandTargetResolver The target resolution strategy */ public AggregateAnnotationCommandHandler(Class<T> aggregateType, Repository<T> repository, CommandTargetResolver commandTargetResolver) { this(aggregateType, repository, commandTargetResolver, ClasspathParameterResolverFactory.forClass(aggregateType)); } /** * Initializes an AnnotationCommandHandler based on the annotations on given {@code aggregateType}, using the * given {@code repository} to add and load aggregate instances and the given * {@code parameterResolverFactory}. * * @param aggregateType The type of aggregate * @param repository The repository providing access to aggregate instances * @param commandTargetResolver The target resolution strategy * @param parameterResolverFactory The strategy for resolving parameter values for handler methods */ public AggregateAnnotationCommandHandler(Class<T> aggregateType, Repository<T> repository, CommandTargetResolver commandTargetResolver, ParameterResolverFactory parameterResolverFactory) { this(repository, commandTargetResolver, ModelInspector.inspectAggregate(aggregateType, parameterResolverFactory)); } /** * Initializes an AnnotationCommandHandler based on the annotations on given {@code aggregateType}, using the * given {@code repository} to add and load aggregate instances and the given * {@code parameterResolverFactory}. * * @param repository The repository providing access to aggregate instances * @param commandTargetResolver The target resolution strategy * @param aggregateModel The description of the command handling model */ public AggregateAnnotationCommandHandler(Repository<T> repository, CommandTargetResolver commandTargetResolver, AggregateModel<T> aggregateModel) { Assert.notNull(aggregateModel, () -> "aggregateModel may not be null"); Assert.notNull(repository, () -> "repository may not be null"); Assert.notNull(commandTargetResolver, () -> "commandTargetResolver may not be null"); this.repository = repository; this.commandTargetResolver = commandTargetResolver; this.handlers = initializeHandlers(aggregateModel); } /** * Subscribe this command handler to the given {@code commandBus}. The command handler will be subscribed * for each of the supported commands. * * @param commandBus The command bus instance to subscribe to * @return A handle that can be used to unsubscribe */ public Registration subscribe(CommandBus commandBus) { Collection<Registration> subscriptions = new ArrayList<>(); for (String supportedCommand : supportedCommandNames()) { Registration subscription = commandBus.subscribe(supportedCommand, this); if (subscription != null) { subscriptions.add(subscription); } } return () -> { subscriptions.forEach(Registration::cancel); return true; }; } private Map<String, MessageHandler<CommandMessage<?>>> initializeHandlers(AggregateModel<T> aggregateModel) { Map<String, MessageHandler<CommandMessage<?>>> handlersFound = new HashMap<>(); AggregateCommandHandler aggregateCommandHandler = new AggregateCommandHandler(); aggregateModel.commandHandlers().forEach((k, v) -> { if (v.unwrap(CommandMessageHandlingMember.class) .map(CommandMessageHandlingMember::isFactoryHandler) .orElse(false)) { handlersFound.put(k, new AggregateConstructorCommandHandler(v)); } else { handlersFound.put(k, aggregateCommandHandler); } }); return handlersFound; } @Override public Object handle(CommandMessage<?> commandMessage) throws Exception { return handlers.get(commandMessage.getCommandName()).handle(commandMessage); } /** * Resolves the value to return when the given {@code command} has created the given {@code aggregate}. * This implementation returns the identifier of the created aggregate. * <p> * This method may be overridden to change the return value of this Command Handler * * @param command The command being executed * @param createdAggregate The aggregate that has been created as a result of the command * @return The value to report as result of the command */ protected Object resolveReturnValue(CommandMessage<?> command, Aggregate<T> createdAggregate) { return createdAggregate.identifier(); } @Override public Set<String> supportedCommandNames() { return handlers.keySet(); } private class AggregateConstructorCommandHandler implements MessageHandler<CommandMessage<?>> { private final MessageHandlingMember<?> handler; public AggregateConstructorCommandHandler(MessageHandlingMember<?> handler) { this.handler = handler; } @SuppressWarnings("unchecked") @Override public Object handle(CommandMessage<?> command) throws Exception { Aggregate<T> aggregate = repository.newInstance(() -> (T) handler.handle(command, null)); return resolveReturnValue(command, aggregate); } } private class AggregateCommandHandler implements MessageHandler<CommandMessage<?>> { @SuppressWarnings("unchecked") @Override public Object handle(CommandMessage<?> command) throws Exception { VersionedAggregateIdentifier iv = commandTargetResolver.resolveTarget(command); return repository.load(iv.getIdentifier(), iv.getVersion()).handle(command); } } }