/*
* 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.distributed;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.commandhandling.CommandCallback;
import org.axonframework.commandhandling.CommandMessage;
import org.axonframework.commandhandling.MonitorAwareCallback;
import org.axonframework.commandhandling.distributed.commandfilter.CommandNameFilter;
import org.axonframework.commandhandling.distributed.commandfilter.DenyAll;
import org.axonframework.commandhandling.distributed.commandfilter.DenyCommandNameFilter;
import org.axonframework.common.Assert;
import org.axonframework.common.Registration;
import org.axonframework.messaging.MessageDispatchInterceptor;
import org.axonframework.messaging.MessageHandler;
import org.axonframework.monitoring.MessageMonitor;
import org.axonframework.monitoring.NoOpMessageMonitor;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
/**
* Implementation of a {@link CommandBus} that is aware of multiple instances of a CommandBus working together to
* spread load. Each "physical" CommandBus instance is considered a "segment" of a conceptual distributed CommandBus.
* <p/>
* The DistributedCommandBus relies on a {@link CommandBusConnector} to dispatch commands and replies to different
* segments of the CommandBus. Depending on the implementation used, each segment may run in a different JVM.
*
* @author Allard Buijze
* @since 2.0
*/
public class DistributedCommandBus implements CommandBus {
/**
* The initial load factor of this node when it is registered with the {@link CommandRouter}.
*/
public static final int INITIAL_LOAD_FACTOR = 100;
private static final String DISPATCH_ERROR_MESSAGE = "An error occurred while trying to dispatch a command "
+ "on the DistributedCommandBus";
private final CommandRouter commandRouter;
private final CommandBusConnector connector;
private final List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors = new CopyOnWriteArrayList<>();
private final AtomicReference<Predicate<CommandMessage<?>>> commandFilter = new AtomicReference<>(DenyAll.INSTANCE);
private final MessageMonitor<? super CommandMessage<?>> messageMonitor;
private volatile int loadFactor = INITIAL_LOAD_FACTOR;
/**
* Initializes the command bus with the given {@code commandRouter} and {@code connector}. The
* {@code commandRouter} is used to determine the target node for each dispatched command. The {@code connector}
* performs the actual transport of the message to the destination node.
*
* @param commandRouter the service registry that discovers the network of worker nodes
* @param connector the connector that connects the different command bus segments
*/
public DistributedCommandBus(CommandRouter commandRouter, CommandBusConnector connector) {
this(commandRouter, connector, NoOpMessageMonitor.INSTANCE);
}
/**
* Initializes the command bus with the given {@code commandRouter}, {@code connector} and {@code messageMonitor}.
* The {@code commandRouter} is used to determine the target node for each dispatched command.
* The {@code connector} performs the actual transport of the message to the destination node.
* The {@code messageMonitor} is used to monitor incoming messages and their execution result.
*
* @param commandRouter the service registry that discovers the network of worker nodes
* @param connector the connector that connects the different command bus segments
* @param messageMonitor the message monitor to notify of incoming messages and their execution result
*/
public DistributedCommandBus(CommandRouter commandRouter, CommandBusConnector connector, MessageMonitor<? super CommandMessage<?>> messageMonitor) {
Assert.notNull(commandRouter, () -> "serviceRegistry may not be null");
Assert.notNull(connector, () -> "connector may not be null");
Assert.notNull(messageMonitor, () -> "messageMonitor may not be null");
this.commandRouter = commandRouter;
this.connector = connector;
this.messageMonitor = messageMonitor;
}
@Override
public <C> void dispatch(CommandMessage<C> command) {
if (NoOpMessageMonitor.INSTANCE.equals(messageMonitor)) {
CommandMessage<? extends C> interceptedCommand = intercept(command);
Member destination = commandRouter.findDestination(command)
.orElseThrow(() -> new CommandDispatchException("No node known to accept " + command.getCommandName()));
try {
connector.send(destination, interceptedCommand);
} catch (Exception e) {
destination.suspect();
throw new CommandDispatchException(DISPATCH_ERROR_MESSAGE + ": " + e.getMessage(), e);
}
} else {
dispatch(command, null);
}
}
/**
* {@inheritDoc}
*
* @throws CommandDispatchException when an error occurs while dispatching the command to a segment
*/
@Override
public <C, R> void dispatch(CommandMessage<C> command, CommandCallback<? super C, R> callback) {
CommandMessage<? extends C> interceptedCommand = intercept(command);
MonitorAwareCallback<? super C, R> monitorAwareCallback = new MonitorAwareCallback<>(callback, messageMonitor.onMessageIngested(command));
Member destination = commandRouter.findDestination(command)
.orElseThrow(() -> new CommandDispatchException("No node known to accept " + command.getCommandName()));
try {
connector.send(destination, interceptedCommand, monitorAwareCallback);
} catch (Exception e) {
destination.suspect();
throw new CommandDispatchException(DISPATCH_ERROR_MESSAGE + ": " + e.getMessage(), e);
}
}
@SuppressWarnings("unchecked")
private <C> CommandMessage<? extends C> intercept(CommandMessage<C> command) {
CommandMessage<? extends C> interceptedCommand = command;
for (MessageDispatchInterceptor<? super CommandMessage<?>> interceptor : dispatchInterceptors) {
interceptedCommand = (CommandMessage<? extends C>) interceptor.handle(interceptedCommand);
}
return interceptedCommand;
}
/**
* {@inheritDoc}
* <p/>
* In the DistributedCommandBus, the handler is subscribed to the local segment only.
*/
@Override
public Registration subscribe(String commandName, MessageHandler<? super CommandMessage<?>> handler) {
Registration reg = connector.subscribe(commandName, handler);
updateFilter(commandFilter.get().or(new CommandNameFilter(commandName)));
return () -> {
updateFilter(commandFilter.get().and(new DenyCommandNameFilter(commandName)));
return reg.cancel();
};
}
private void updateFilter(Predicate<CommandMessage<?>> newFilter) {
if (!commandFilter.getAndSet(newFilter).equals(newFilter)) {
commandRouter.updateMembership(loadFactor, newFilter);
}
}
/**
* Returns the current load factor of this node.
*
* @return the current load factor
*/
public int getLoadFactor() {
return loadFactor;
}
/**
* Updates the load factor of this node compared to other nodes registered with the {@link CommandRouter}.
*
* @param loadFactor the new load factor of this node
*/
public void updateLoadFactor(int loadFactor) {
this.loadFactor = loadFactor;
commandRouter.updateMembership(loadFactor, commandFilter.get());
}
/**
* Registers the given list of dispatch interceptors to the command bus. All incoming commands will pass through
* the interceptors at the given order before the command is dispatched toward the command handler.
*
* @param dispatchInterceptor The interceptors to invoke when commands are dispatched
* @return handle to unregister the interceptor
*/
public Registration registerDispatchInterceptor(MessageDispatchInterceptor<? super CommandMessage<?>> dispatchInterceptor) {
dispatchInterceptors.add(dispatchInterceptor);
return () -> dispatchInterceptors.remove(dispatchInterceptor);
}
}