ARTEMIS-2937 Server Side AMQP Connectivity with options to transfer queues or replicate data

This commit is contained in:
Clebert Suconic 2020-10-26 21:23:01 -04:00
parent 4d6096f88d
commit 8499eac76c
95 changed files with 6419 additions and 1077 deletions

3
.gitignore vendored
View File

@ -35,3 +35,6 @@ docs/hacking-guide/en/_book
# overlay outpit
**/overlays/**/*
# result of gitbook pdf under docs
docs/user-manual/en/book.pdf

View File

@ -2753,4 +2753,30 @@ public interface AuditLogger extends BasicLogger {
@LogMessage(level = Logger.Level.INFO)
@Message(id = 601737, value = "User {0} is getting authorization cache size on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
void getAuthorizationCacheSize(String user, Object source, Object... args);
static void listBrokerConnections() {
LOGGER.listBrokerConnections(getCaller());
}
@LogMessage(level = Logger.Level.INFO)
@Message(id = 601738, value = "User {0} is requesting a list of broker connections", format = Message.Format.MESSAGE_FORMAT)
void listBrokerConnections(String user);
static void stopBrokerConnection(String name) {
LOGGER.stopBrokerConnection(getCaller(), name);
}
@LogMessage(level = Logger.Level.INFO)
@Message(id = 601739, value = "User {0} is requesting to stop broker connection {1}", format = Message.Format.MESSAGE_FORMAT)
void stopBrokerConnection(String user, String name);
static void startBrokerConnection(String name) {
LOGGER.startBrokerConnection(getCaller(), name);
}
@LogMessage(level = Logger.Level.INFO)
@Message(id = 601740, value = "User {0} is requesting to start broker connection {1}", format = Message.Format.MESSAGE_FORMAT)
void startBrokerConnection(String user, String name);
}

View File

@ -53,21 +53,29 @@ public class ByteUtil {
public static void debugFrame(Logger logger, String message, ByteBuf byteIn) {
if (logger.isTraceEnabled()) {
int location = byteIn.readerIndex();
// debugging
byte[] frame = new byte[byteIn.writerIndex()];
byteIn.readBytes(frame);
try {
logger.trace(message + "\n" + ByteUtil.formatGroup(ByteUtil.bytesToHex(frame), 8, 16));
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
byteIn.readerIndex(location);
outFrame(logger, message, byteIn, false);
}
}
public static void outFrame(Logger logger, String message, ByteBuf byteIn, boolean info) {
int location = byteIn.readerIndex();
// debugging
byte[] frame = new byte[byteIn.writerIndex()];
byteIn.readBytes(frame);
try {
if (info) {
logger.info(message + "\n" + ByteUtil.formatGroup(ByteUtil.bytesToHex(frame), 8, 16));
} else {
logger.trace(message + "\n" + ByteUtil.formatGroup(ByteUtil.bytesToHex(frame), 8, 16));
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
byteIn.readerIndex(location);
}
public static String formatGroup(String str, int groupSize, int lineBreak) {
StringBuffer buffer = new StringBuffer();

View File

@ -16,6 +16,8 @@
*/
package org.apache.activemq.artemis.utils.collections;
import java.util.function.ToLongFunction;
public interface LinkedList<E> {
void addHead(E e);
@ -29,4 +31,13 @@ public interface LinkedList<E> {
void clear();
int size();
void clearID();
/** The ID Supplier function needs to return positive IDs (>= 0).
* If you spply a negative ID, it will be considered a null value, and
* the value will just be ignored. */
void setIDSupplier(ToLongFunction<E> supplier);
E removeWithID(long id);
}

View File

@ -20,11 +20,14 @@ import java.lang.reflect.Array;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.ToLongFunction;
import io.netty.util.collection.LongObjectHashMap;
/**
* A linked list implementation which allows multiple iterators to exist at the same time on the queue, and which see any
* elements added or removed from the queue either directly or via iterators.
*
* <p>
* This class is not thread safe.
*/
public class LinkedListImpl<E> implements LinkedList<E> {
@ -32,27 +35,67 @@ public class LinkedListImpl<E> implements LinkedList<E> {
private static final int INITIAL_ITERATOR_ARRAY_SIZE = 10;
private final Node<E> head = new NodeHolder<>(null);
private final Comparator<E> comparator;
LongObjectHashMap<Node<E>> nodeMap;
private Node<E> tail = null;
private int size;
// We store in an array rather than a Map for the best performance
private volatile Iterator[] iters;
private int numIters;
private int nextIndex;
private final Comparator<E> comparator;
private ToLongFunction<E> idSupplier;
public LinkedListImpl() {
this(null);
this(null, null);
}
public LinkedListImpl(Comparator<E> comparator) {
this(comparator, null);
}
public LinkedListImpl(Comparator<E> comparator, ToLongFunction<E> supplier) {
iters = createIteratorArray(INITIAL_ITERATOR_ARRAY_SIZE);
this.comparator = comparator;
this.idSupplier = supplier;
if (idSupplier != null) {
this.nodeMap = newLongHashMap();
} else {
this.nodeMap = null;
}
}
@Override
public void clearID() {
idSupplier = null;
if (nodeMap != null) {
nodeMap.clear();
nodeMap = null;
}
}
@Override
public void setIDSupplier(ToLongFunction<E> supplier) {
this.idSupplier = supplier;
nodeMap = newLongHashMap();
try (Iterator iterator = (Iterator) iterator()) {
while (iterator.hasNext()) {
E value = iterator.next();
Node<E> position = iterator.last;
putID(value, position);
}
}
}
private LongObjectHashMap<Node<E>> newLongHashMap() {
return new LongObjectHashMap<>(Math.max(8, this.size));
}
private void putID(E value, Node<E> position) {
long id = idSupplier.applyAsLong(value);
if (id >= 0) {
nodeMap.put(id, position);
}
}
@Override
@ -72,9 +115,43 @@ public class LinkedListImpl<E> implements LinkedList<E> {
node.next.prev = node;
}
itemAdded(node, e);
size++;
}
@Override
public E removeWithID(long id) {
if (nodeMap == null) {
return null;
}
Node<E> node = nodeMap.get(id);
if (node == null) {
return null;
}
if (node.prev != null) {
removeAfter(node.prev);
}
return node.val();
}
private void itemAdded(Node<E> node, E item) {
if (nodeMap != null) {
putID(item, node);
}
}
private void itemRemoved(Node<E> node) {
if (nodeMap != null) {
long id = idSupplier.applyAsLong(node.val());
if (id >= 0) {
nodeMap.remove(id);
}
}
}
@Override
public void addTail(E e) {
if (size == 0) {
@ -88,6 +165,8 @@ public class LinkedListImpl<E> implements LinkedList<E> {
tail = node;
itemAdded(node, e);
size++;
}
}
@ -143,6 +222,7 @@ public class LinkedListImpl<E> implements LinkedList<E> {
newNode.prev = node;
newNode.next = nextNode;
nextNode.prev = newNode;
itemAdded(node, e);
size++;
}
@ -175,6 +255,13 @@ public class LinkedListImpl<E> implements LinkedList<E> {
return size;
}
/**
* Return the number of elements we have on suppliedIDs
*/
public int getSizeOfSuppliedIDs() {
return nodeMap == null ? 0 : nodeMap.size();
}
@Override
public LinkedListIterator<E> iterator() {
return new Iterator();
@ -216,6 +303,8 @@ public class LinkedListImpl<E> implements LinkedList<E> {
toRemove.next.prev = node;
}
itemRemoved(toRemove);
if (toRemove == tail) {
tail = node;
}
@ -307,6 +396,20 @@ public class LinkedListImpl<E> implements LinkedList<E> {
private int iterCount;
private static <T> Node<T> with(final T o) {
Objects.requireNonNull(o, "Only HEAD nodes are allowed to hold null values");
if (o instanceof Node) {
final Node node = (Node) o;
//only a node that not belong already to a list is allowed to be reused
if (node.prev == null && node.next == null) {
//reset the iterCount
node.iterCount = 0;
return node;
}
}
return new NodeHolder<>(o);
}
@SuppressWarnings("unchecked")
protected T val() {
return (T) this;
@ -324,20 +427,6 @@ public class LinkedListImpl<E> implements LinkedList<E> {
public String toString() {
return val() == this ? "Intrusive Node" : "Node, value = " + val();
}
private static <T> Node<T> with(final T o) {
Objects.requireNonNull(o, "Only HEAD nodes are allowed to hold null values");
if (o instanceof Node) {
final Node node = (Node) o;
//only a node that not belong already to a list is allowed to be reused
if (node.prev == null && node.next == null) {
//reset the iterCount
node.iterCount = 0;
return node;
}
}
return new NodeHolder<>(o);
}
}
private class Iterator implements LinkedListIterator<E> {

View File

@ -16,6 +16,8 @@
*/
package org.apache.activemq.artemis.utils.collections;
import java.util.function.ToLongFunction;
/**
* A type of linked list which maintains items according to a priority
* and allows adding and removing of elements at both ends, and peeking.<br>
@ -33,6 +35,14 @@ public interface PriorityLinkedList<T> {
void clear();
/**
* @see LinkedList#setIDSupplier(ToLongFunction)
* @param supplier
*/
void setIDSupplier(ToLongFunction<T> supplier);
T removeWithID(long id);
/**
* Returns the size of this list.<br>
* It is safe to be called concurrently.

View File

@ -20,6 +20,7 @@ import java.lang.reflect.Array;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.function.ToLongFunction;
/**
* A priority linked list implementation
@ -95,6 +96,38 @@ public class PriorityLinkedListImpl<T> implements PriorityLinkedList<T> {
exclusiveIncrementSize(1);
}
@Override
public void setIDSupplier(ToLongFunction<T> supplier) {
for (LinkedList<T> list : levels) {
list.setIDSupplier(supplier);
}
}
@Override
public T removeWithID(long id) {
// we start at 4 just as an optimization, since most times we only use level 4 as the level on messages
if (levels.length > 4) {
for (int l = 4; l < levels.length; l++) {
T removed = levels[l].removeWithID(id);
if (removed != null) {
exclusiveIncrementSize(-1);
return removed;
}
}
}
for (int l = Math.min(3, levels.length); l >= 0; l--) {
T removed = levels[l].removeWithID(id);
if (removed != null) {
exclusiveIncrementSize(-1);
return removed;
}
}
return null;
}
@Override
public T poll() {
T t = null;

View File

@ -0,0 +1,140 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.utils;
import org.jboss.logging.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;
public class ExecuteUtil {
public static class ProcessHolder {
Thread inputStreamReader;
Thread errorStreamReader;
Process process;
public void kill() throws InterruptedException {
process.destroy();
errorStreamReader.join();
inputStreamReader.join();
}
public int waitFor(long timeout, TimeUnit unit) throws InterruptedException {
if (!process.waitFor(timeout, unit)) {
logger.warn("could not complete execution in time");
return -1;
}
errorStreamReader.join();
inputStreamReader.join();
return process.exitValue();
}
public int waitFor() throws InterruptedException {
process.waitFor();
errorStreamReader.join();
inputStreamReader.join();
return process.exitValue();
}
}
private static final Logger logger = Logger.getLogger(ExecuteUtil.class);
public static int runCommand(boolean logOutput, String... command) throws Exception {
return runCommand(logOutput, 10, TimeUnit.SECONDS, command);
}
public static int runCommand(boolean logOutput,
long timeout,
TimeUnit timeoutUnit,
String... command) throws Exception {
final ProcessHolder processHolder = run(logOutput, command);
return processHolder.waitFor(timeout, timeoutUnit);
}
public static ProcessHolder run(boolean logOutput, String... command) throws IOException {
logCommand(command);
// it did not work with a simple isReachable, it could be because there's no root access, so we will try ping executable
ProcessBuilder processBuilder = new ProcessBuilder(command);
final ProcessHolder processHolder = new ProcessHolder();
processHolder.process = processBuilder.start();
processHolder.inputStreamReader = new Thread() {
@Override
public void run() {
try {
readStream(processHolder.process.getInputStream(), true, logOutput);
} catch (Exception dontCare) {
}
}
};
processHolder.errorStreamReader = new Thread() {
@Override
public void run() {
try {
readStream(processHolder.process.getErrorStream(), true, logOutput);
} catch (Exception dontCare) {
}
}
};
processHolder.errorStreamReader.start();
processHolder.inputStreamReader.start();
return processHolder;
}
private static void logCommand(String[] command) {
StringBuffer logCommand = new StringBuffer();
for (String c : command) {
logCommand.append(c + " ");
}
System.out.println("command::" + logCommand.toString());
}
private static void readStream(InputStream stream, boolean error, boolean logOutput) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String inputLine;
while ((inputLine = reader.readLine()) != null) {
if (logOutput) {
System.out.println(inputLine);
} else {
if (error) {
logger.warn(inputLine);
} else {
logger.trace(inputLine);
}
}
}
reader.close();
}
}

View File

@ -17,10 +17,6 @@
package org.apache.activemq.artemis.utils.network;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
@ -28,11 +24,10 @@ import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.core.server.NetworkHealthCheck;
import org.jboss.logging.Logger;
import org.apache.activemq.artemis.utils.ExecuteUtil;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Test;
@ -53,7 +48,7 @@ import org.junit.Test;
* yourUserName ALL = NOPASSWD: /sbin/ifconfig");
* # ------------------------------------------------------- ");
* */
public class NetUtil {
public class NetUtil extends ExecuteUtil {
public static boolean checkIP(String ip) throws Exception {
InetAddress ipAddress = null;
@ -130,12 +125,12 @@ public class NetUtil {
public static void netUp(String ip, String deviceID) throws Exception {
if (osUsed == OS.MAC) {
if (runCommand("sudo", "-n", "ifconfig", "lo0", "alias", ip) != 0) {
if (runCommand(false, "sudo", "-n", "ifconfig", "lo0", "alias", ip) != 0) {
Assert.fail("Cannot sudo ifconfig for ip " + ip);
}
networks.put(ip, "lo0");
} else if (osUsed == OS.LINUX) {
if (runCommand("sudo", "-n", "ifconfig", deviceID, ip, "netmask", "255.0.0.0") != 0) {
if (runCommand(false, "sudo", "-n", "ifconfig", deviceID, ip, "netmask", "255.0.0.0") != 0) {
Assert.fail("Cannot sudo ifconfig for ip " + ip);
}
networks.put(ip, deviceID);
@ -162,13 +157,13 @@ public class NetUtil {
networks.remove(ip);
if (osUsed == OS.MAC) {
if (runCommand("sudo", "-n", "ifconfig", "lo0", "-alias", ip) != 0) {
if (runCommand(false, "sudo", "-n", "ifconfig", "lo0", "-alias", ip) != 0) {
if (!force) {
Assert.fail("Cannot sudo ifconfig for ip " + ip);
}
}
} else if (osUsed == OS.LINUX) {
if (runCommand("sudo", "-n", "ifconfig", device, "down") != 0) {
if (runCommand(false, "sudo", "-n", "ifconfig", device, "down") != 0) {
if (!force) {
Assert.fail("Cannot sudo ifconfig for ip " + ip);
}
@ -178,62 +173,9 @@ public class NetUtil {
}
}
private static final Logger logger = Logger.getLogger(NetUtil.class);
public static int runCommand(String... command) throws Exception {
return runCommand(10, TimeUnit.SECONDS, command);
}
public static int runCommand(long timeout, TimeUnit timeoutUnit, String... command) throws Exception {
logCommand(command);
// it did not work with a simple isReachable, it could be because there's no root access, so we will try ping executable
ProcessBuilder processBuilder = new ProcessBuilder(command);
final Process process = processBuilder.start();
Thread t = new Thread() {
@Override
public void run() {
try {
readStream(process.getInputStream(), true);
} catch (Exception dontCare) {
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
readStream(process.getErrorStream(), true);
} catch (Exception dontCare) {
}
}
};
t2.start();
int value = process.waitFor();
t.join(timeoutUnit.toMillis(timeout));
Assert.assertFalse(t.isAlive());
t2.join(timeoutUnit.toMillis(timeout));
return value;
}
private static void logCommand(String[] command) {
StringBuffer logCommand = new StringBuffer();
for (String c : command) {
logCommand.append(c + " ");
}
System.out.println("NetUTIL command::" + logCommand.toString());
}
public static boolean canSudo() {
try {
return runCommand("sudo", "-n", "ifconfig") == 0;
return runCommand(false, "sudo", "-n", "ifconfig") == 0;
} catch (Exception e) {
e.printStackTrace();
return false;
@ -244,20 +186,4 @@ public class NetUtil {
public void testCanSudo() throws Exception {
Assert.assertTrue(canSudo());
}
private static void readStream(InputStream stream, boolean error) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String inputLine;
while ((inputLine = reader.readLine()) != null) {
if (error) {
logger.warn(inputLine);
} else {
logger.trace(inputLine);
}
}
reader.close();
}
}

View File

@ -1751,6 +1751,15 @@ public interface ActiveMQServerControl {
@Operation(desc = "Destroy a bridge", impact = MBeanOperationInfo.ACTION)
void destroyBridge(@Parameter(name = "name", desc = "Name of the bridge") String name) throws Exception;
@Operation(desc = "List the existing broker connections", impact = MBeanOperationInfo.INFO)
String listBrokerConnections();
@Operation(desc = "Activate a broker connection that is pre configured", impact = MBeanOperationInfo.ACTION)
void startBrokerConnection(@Parameter(name = "name", desc = "Name of the broker connection to be started") String name) throws Exception;
@Operation(desc = "Stops a broker connection that is pre configured", impact = MBeanOperationInfo.ACTION)
void stopBrokerConnection(@Parameter(name = "name", desc = "Name of the broker connection to be stopped") String name) throws Exception;
@Operation(desc = "Create a connector service", impact = MBeanOperationInfo.ACTION)
void createConnectorService(@Parameter(name = "name", desc = "Name of the connector service") String name,
@Parameter(name = "factoryClass", desc = "Class name of the connector service factory") String factoryClass,

View File

@ -174,6 +174,8 @@ public class NettyConnector extends AbstractConnector {
// Attributes ----------------------------------------------------
private final boolean serverConnection;
private Class<? extends Channel> channelClazz;
private Bootstrap bootstrap;
@ -316,15 +318,27 @@ public class NettyConnector extends AbstractConnector {
final Executor threadPool,
final ScheduledExecutorService scheduledThreadPool,
final ClientProtocolManager protocolManager) {
this(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager, false);
}
public NettyConnector(final Map<String, Object> configuration,
final BufferHandler handler,
final BaseConnectionLifeCycleListener<?> listener,
final Executor closeExecutor,
final Executor threadPool,
final ScheduledExecutorService scheduledThreadPool,
final ClientProtocolManager protocolManager,
final boolean serverConnection) {
super(configuration);
this.serverConnection = serverConnection;
this.protocolManager = protocolManager;
if (listener == null) {
throw ActiveMQClientMessageBundle.BUNDLE.nullListener();
}
if (handler == null) {
if (!serverConnection && handler == null) {
throw ActiveMQClientMessageBundle.BUNDLE.nullHandler();
}
@ -460,6 +474,14 @@ public class NettyConnector extends AbstractConnector {
"]";
}
public ChannelGroup getChannelGroup() {
return channelGroup;
}
public boolean isServerConnection() {
return serverConnection;
}
private String getHttpUpgradeInfo() {
if (!httpUpgradeEnabled) {
return "";
@ -664,10 +686,14 @@ public class NettyConnector extends AbstractConnector {
pipeline.addLast("http-upgrade", new HttpUpgradeHandler(pipeline, httpClientCodec));
}
protocolManager.addChannelHandlers(pipeline);
if (protocolManager != null) {
protocolManager.addChannelHandlers(pipeline);
}
pipeline.addLast(new ActiveMQClientChannelHandler(channelGroup, handler, new Listener(), closeExecutor));
logger.debugf("Added ActiveMQClientChannelHandler to Channel with id = %s ", channel.id());
if (handler != null) {
pipeline.addLast(new ActiveMQClientChannelHandler(channelGroup, handler, new Listener(), closeExecutor));
logger.debugf("Added ActiveMQClientChannelHandler to Channel with id = %s ", channel.id());
}
}
});
@ -809,6 +835,10 @@ public class NettyConnector extends AbstractConnector {
return null;
}
return createConnection(onConnect, host, port);
}
public NettyConnection createConnection(Consumer<ChannelFuture> onConnect, String host, int port) {
InetSocketAddress remoteDestination;
if (proxyEnabled && proxyRemoteDNS) {
remoteDestination = InetSocketAddress.createUnresolved(IPV6Util.stripBracketsAndZoneID(host), port);
@ -845,14 +875,14 @@ public class NettyConnector extends AbstractConnector {
if (handshakeFuture.isSuccess()) {
ChannelPipeline channelPipeline = ch.pipeline();
ActiveMQChannelHandler channelHandler = channelPipeline.get(ActiveMQChannelHandler.class);
if (channelHandler != null) {
channelHandler.active = true;
} else {
ch.close().awaitUninterruptibly();
ActiveMQClientLogger.LOGGER.errorCreatingNettyConnection(
new IllegalStateException("No ActiveMQChannelHandler has been found while connecting to " +
remoteDestination + " from Channel with id = " + ch.id()));
return null;
if (!serverConnection) {
if (channelHandler != null) {
channelHandler.active = true;
} else {
ch.close().awaitUninterruptibly();
ActiveMQClientLogger.LOGGER.errorCreatingNettyConnection(new IllegalStateException("No ActiveMQChannelHandler has been found while connecting to " + remoteDestination + " from Channel with id = " + ch.id()));
return null;
}
}
} else {
ch.close().awaitUninterruptibly();
@ -915,7 +945,7 @@ public class NettyConnector extends AbstractConnector {
ActiveMQChannelHandler channelHandler = channelPipeline.get(ActiveMQChannelHandler.class);
if (channelHandler != null) {
channelHandler.active = true;
} else {
} else if (!serverConnection) {
ch.close().awaitUninterruptibly();
ActiveMQClientLogger.LOGGER.errorCreatingNettyConnection(
new IllegalStateException("No ActiveMQChannelHandler has been found while connecting to " +

View File

@ -28,6 +28,17 @@ import org.apache.activemq.artemis.spi.core.remoting.ConnectorFactory;
public class NettyConnectorFactory implements ConnectorFactory {
boolean serverConnector;
public boolean isServerConnector() {
return serverConnector;
}
public NettyConnectorFactory setServerConnector(boolean serverConnector) {
this.serverConnector = serverConnector;
return this;
}
@Override
public Connector createConnector(final Map<String, Object> configuration,
final BufferHandler handler,
@ -36,7 +47,7 @@ public class NettyConnectorFactory implements ConnectorFactory {
final Executor threadPool,
final ScheduledExecutorService scheduledThreadPool,
final ClientProtocolManager protocolManager) {
return new NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool);
return new NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager, serverConnector);
}
@Override

View File

@ -27,7 +27,13 @@ import org.apache.activemq.artemis.utils.uri.URIFactory;
public class ConnectorTransportConfigurationParser extends URIFactory<List<TransportConfiguration>, String> {
public ConnectorTransportConfigurationParser() {
this(true);
}
public ConnectorTransportConfigurationParser(boolean invm) {
registerSchema(new TCPTransportConfigurationSchema(TransportConstants.ALLOWABLE_CONNECTOR_KEYS));
registerSchema(new InVMTransportConfigurationSchema());
if (invm) {
registerSchema(new InVMTransportConfigurationSchema());
}
}
}

View File

@ -319,6 +319,10 @@ public final class XMLUtil {
public static int parseInt(final Node elem) {
String value = elem.getTextContent().trim();
return parseInt(elem, value);
}
public static int parseInt(Node elem, String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {

View File

@ -34,6 +34,7 @@ import org.apache.activemq.artemis.core.persistence.impl.journal.LargeBody;
import org.apache.activemq.artemis.core.persistence.impl.journal.LargeServerMessageImpl;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.LargeServerMessage;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
import org.apache.activemq.artemis.utils.collections.TypedProperties;
import org.apache.qpid.proton.amqp.messaging.Header;
@ -153,6 +154,22 @@ public class AMQPLargeMessage extends AMQPMessage implements LargeServerMessage
internalReleaseBuffer(2);
}
/**
* This method check the reference for specifics on protocolData.
*
* It was written to check the deliveryAnnotationsForSendBuffer and eventually move it to the protocolData.
*/
public void checkReference(MessageReference reference) {
if (reference.getProtocolData() == null && deliveryAnnotationsForSendBuffer != null) {
reference.setProtocolData(deliveryAnnotationsForSendBuffer);
}
}
/** during large message deliver, we need this calculation to place a new delivery annotation */
public int getPositionAfterDeliveryAnnotations() {
return encodedHeaderSize + encodedDeliveryAnnotationsSize;
}
private void internalReleaseBuffer(int releases) {
synchronized (largeBody) {
for (int i = 0; i < releases; i++) {
@ -268,7 +285,7 @@ public class AMQPLargeMessage extends AMQPMessage implements LargeServerMessage
}
@Override
public ReadableBuffer getSendBuffer(int deliveryCount) {
public ReadableBuffer getSendBuffer(int deliveryCount, MessageReference reference) {
return getData().rewind();
}

View File

@ -36,6 +36,7 @@ import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.persistence.CoreMessageObjectPools;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageIdHelper;
import org.apache.activemq.artemis.protocol.amqp.converter.AMQPMessageSupport;
import org.apache.activemq.artemis.protocol.amqp.converter.AmqpCoreConverter;
@ -206,6 +207,7 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
protected final CoreMessageObjectPools coreMessageObjectPools;
protected Set<Object> rejectedConsumers;
protected DeliveryAnnotations deliveryAnnotationsForSendBuffer;
protected DeliveryAnnotations deliveryAnnotations;
// These are properties set at the broker level and carried only internally by broker storage.
protected volatile TypedProperties extraProperties;
@ -307,11 +309,34 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
return scanForMessageSection(headerPosition, Header.class);
}
/** Returns the current already scanned header. */
final Header getCurrentHeader() {
return header;
}
/** Return the current already scanned properties.*/
final Properties getCurrentProperties() {
return properties;
}
protected void ensureScanning() {
ensureDataIsValid();
ensureMessageDataScanned();
}
Object getDeliveryAnnotationProperty(Symbol symbol) {
DeliveryAnnotations daToUse = deliveryAnnotations;
if (daToUse == null) {
daToUse = getDeliveryAnnotations();
}
if (daToUse == null) {
return null;
} else {
return daToUse.getValue().get(symbol);
}
}
/**
* Returns a copy of the MessageAnnotations in the message if present or null. Changes to the
* returned DeliveryAnnotations instance do not affect the original Message.
@ -332,8 +357,10 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
*
* http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-messaging-v1.0-os.html#type-annotations
*
* @deprecated use MessageReference.setProtocolData(deliveryAnnotations)
* @param deliveryAnnotations delivery annotations used in the sendBuffer() method
*/
@Deprecated
public final void setDeliveryAnnotationsForSendBuffer(DeliveryAnnotations deliveryAnnotations) {
this.deliveryAnnotationsForSendBuffer = deliveryAnnotations;
}
@ -640,10 +667,8 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
expiration = System.currentTimeMillis() + header.getTtl().intValue();
}
} else if (DeliveryAnnotations.class.equals(constructor.getTypeClass())) {
// Don't decode these as they are not used by the broker at all and are
// discarded on send, mark for lazy decode if ever needed.
constructor.skipValue();
deliveryAnnotationsPosition = constructorPos;
this.deliveryAnnotations = (DeliveryAnnotations) constructor.readValue();
encodedDeliveryAnnotationsSize = data.position() - constructorPos;
} else if (MessageAnnotations.class.equals(constructor.getTypeClass())) {
messageAnnotationsPosition = constructorPos;
@ -687,13 +712,13 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
* this method is not currently used by the AMQP protocol head and will not be
* called for out-bound sends.
*
* @see #getSendBuffer(int) for the actual method used for message sends.
* @see #getSendBuffer(int, MessageReference) for the actual method used for message sends.
*/
@Override
public final void sendBuffer(ByteBuf buffer, int deliveryCount) {
ensureDataIsValid();
NettyWritable writable = new NettyWritable(buffer);
writable.put(getSendBuffer(deliveryCount));
writable.put(getSendBuffer(deliveryCount, null));
}
/**
@ -709,14 +734,20 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
*
* @return a Netty ByteBuf containing the encoded bytes of this Message instance.
*/
public ReadableBuffer getSendBuffer(int deliveryCount) {
public ReadableBuffer getSendBuffer(int deliveryCount, MessageReference reference) {
ensureDataIsValid();
if (deliveryCount > 1) {
return createCopyWithNewDeliveryCount(deliveryCount);
} else if (deliveryAnnotationsPosition != VALUE_NOT_PRESENT
|| (deliveryAnnotationsForSendBuffer != null && !deliveryAnnotationsForSendBuffer.getValue().isEmpty())) {
return createCopyWithSkippedOrExplicitlySetDeliveryAnnotations();
DeliveryAnnotations daToWrite;
if (reference != null && reference.getProtocolData() instanceof DeliveryAnnotations) {
daToWrite = (DeliveryAnnotations) reference.getProtocolData();
} else {
// deliveryAnnotationsForSendBuffer was an old API form where a deliver could set it before deliver
daToWrite = deliveryAnnotationsForSendBuffer;
}
if (deliveryCount > 1 || daToWrite != null || deliveryAnnotationsPosition != VALUE_NOT_PRESENT) {
return createDeliveryCopy(deliveryCount, daToWrite);
} else {
// Common case message has no delivery annotations, no delivery annotations for the send buffer were set
// and this is the first delivery so no re-encoding or section skipping needed.
@ -724,26 +755,10 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
}
}
protected ReadableBuffer createCopyWithSkippedOrExplicitlySetDeliveryAnnotations() {
// The original message had delivery annotations, or delivery annotations for the send buffer are set.
// That means we must copy into a new buffer skipping the original delivery annotations section
// (not meant to survive beyond this hop) and including the delivery annotations for the send buffer if set.
/** it will create a copy with the relevant delivery annotation and its copy */
protected ReadableBuffer createDeliveryCopy(int deliveryCount, DeliveryAnnotations deliveryAnnotations) {
ReadableBuffer duplicate = getData().duplicate();
final ByteBuf result = PooledByteBufAllocator.DEFAULT.heapBuffer(getEncodeSize());
result.writeBytes(duplicate.limit(encodedHeaderSize).byteBuffer());
writeDeliveryAnnotationsForSendBuffer(result);
duplicate.clear();
// skip existing delivery annotations of the original message
duplicate.position(encodedHeaderSize + encodedDeliveryAnnotationsSize);
result.writeBytes(duplicate.byteBuffer());
return new NettyReadable(result);
}
protected ReadableBuffer createCopyWithNewDeliveryCount(int deliveryCount) {
assert deliveryCount > 1;
final int amqpDeliveryCount = deliveryCount - 1;
final ByteBuf result = PooledByteBufAllocator.DEFAULT.heapBuffer(getEncodeSize());
@ -752,31 +767,34 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
// otherwise we want to write the original header if present. When a
// Header is present we need to copy it as we are updating the re-delivered
// message and not the stored version which we don't want to invalidate here.
Header header = this.header;
if (header == null) {
header = new Header();
Header localHeader = this.header;
if (localHeader == null) {
if (deliveryCount > 1) {
localHeader = new Header();
}
} else {
header = new Header(header);
localHeader = new Header(header);
}
header.setDeliveryCount(UnsignedInteger.valueOf(amqpDeliveryCount));
TLSEncode.getEncoder().setByteBuffer(new NettyWritable(result));
TLSEncode.getEncoder().writeObject(header);
TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
if (localHeader != null) {
localHeader.setDeliveryCount(UnsignedInteger.valueOf(amqpDeliveryCount));
TLSEncode.getEncoder().setByteBuffer(new NettyWritable(result));
TLSEncode.getEncoder().writeObject(localHeader);
TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
}
writeDeliveryAnnotationsForSendBuffer(result);
writeDeliveryAnnotationsForSendBuffer(result, deliveryAnnotations);
// skip existing delivery annotations of the original message
getData().position(encodedHeaderSize + encodedDeliveryAnnotationsSize);
result.writeBytes(getData().byteBuffer());
getData().position(0);
duplicate.position(encodedHeaderSize + encodedDeliveryAnnotationsSize);
result.writeBytes(duplicate.byteBuffer());
return new NettyReadable(result);
}
protected void writeDeliveryAnnotationsForSendBuffer(ByteBuf result) {
if (deliveryAnnotationsForSendBuffer != null && !deliveryAnnotationsForSendBuffer.getValue().isEmpty()) {
protected void writeDeliveryAnnotationsForSendBuffer(ByteBuf result, DeliveryAnnotations deliveryAnnotations) {
if (deliveryAnnotations != null && !deliveryAnnotations.getValue().isEmpty()) {
TLSEncode.getEncoder().setByteBuffer(new NettyWritable(result));
TLSEncode.getEncoder().writeObject(deliveryAnnotationsForSendBuffer);
TLSEncode.getEncoder().writeObject(deliveryAnnotations);
TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
}
}
@ -1069,6 +1087,10 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
return this;
}
final void reloadAddress(SimpleString address) {
this.address = address;
}
@Override
public final SimpleString getAddressSimpleString() {
if (address == null) {
@ -1638,7 +1660,7 @@ public abstract class AMQPMessage extends RefCountMessage implements org.apache.
@Override
public String toString() {
return "AMQPMessage [durable=" + isDurable() +
return this.getClass().getSimpleName() + "( [durable=" + isDurable() +
", messageID=" + getMessageID() +
", address=" + getAddress() +
", size=" + getEncodeSize() +

View File

@ -0,0 +1,48 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.broker;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.Properties;
/** <b>Warning:</b> do not use this class outside of the broker implementation.
* This is exposing package methods on this package that are not meant to be used on user's application. */
public class AMQPMessageBrokerAccessor {
/** Warning: this is a method specific to the broker. Do not use it on user's application. */
public static Object getDeliveryAnnotationProperty(AMQPMessage message, Symbol symbol) {
return message.getDeliveryAnnotationProperty(symbol);
}
/** Warning: this is a method specific to the broker. Do not use it on user's application. */
public static Object getMessageAnnotationProperty(AMQPMessage message, Symbol symbol) {
return message.getMessageAnnotation(symbol);
}
/** Warning: this is a method specific to the broker. Do not use it on user's application. */
public static Header getCurrentHeader(AMQPMessage message) {
return message.getCurrentHeader();
}
/** Warning: this is a method specific to the broker. Do not use it on user's application. */
public static Properties getCurrentProperties(AMQPMessage message) {
return message.getCurrentProperties();
}
}

View File

@ -17,8 +17,6 @@
package org.apache.activemq.artemis.protocol.amqp.broker;
import java.util.Objects;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.SimpleString;
@ -97,13 +95,8 @@ public class AMQPMessagePersisterV2 extends AMQPMessagePersister {
if (size != 0) {
final TypedProperties extraProperties = record.createExtraProperties();
extraProperties.decode(buffer.byteBuf(), pool != null ? pool.getPropertiesDecoderPools() : null);
assert Objects.equals(address, extraProperties.getSimpleStringProperty(AMQPMessage.ADDRESS_PROPERTY)) :
"AMQPMessage address and extraProperties address should match";
} else if (address != null) {
// this shouldn't really happen: this code path has been preserved
// because of the behaviour before "ARTEMIS-2617 Improve AMQP Journal loading"
record.setAddress(address);
}
record.reloadAddress(address);
return record;
}

View File

@ -466,8 +466,10 @@ public class AMQPSessionCallback implements SessionCallback {
RoutingType routingType = null;
if (address != null) {
// Fixed-address producer
message.setAddress(address);
if (!address.toString().equals(message.getAddress())) {
// set Fixed-address producer if the message.properties.to address differs from the producer
message.setAddress(address);
}
routingType = context.getDefRoutingType();
} else {
// Anonymous-relay producer, message must carry a To value

View File

@ -53,6 +53,10 @@ public class ActiveMQProtonRemotingConnection extends AbstractRemotingConnection
transportConnection.setProtocolConnection(this);
}
public AMQPConnectionContext getAmqpConnection() {
return amqpConnection;
}
public ProtonProtocolManager getManager() {
return manager;
}

View File

@ -33,9 +33,11 @@ import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.management.Notification;
import org.apache.activemq.artemis.core.server.management.NotificationListener;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientProtocolManager;
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext;
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConstants;
import org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport;
import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
import org.apache.activemq.artemis.protocol.amqp.sasl.MechanismFinder;
import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry;
@ -56,6 +58,8 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
private static final List<String> websocketRegistryNames = Arrays.asList("amqp");
public static final String MIRROR_ADDRESS = "$ACTIVEMQ_ARTEMIS_MIRROR";
private final List<AmqpInterceptor> incomingInterceptors = new ArrayList<>();
private final List<AmqpInterceptor> outgoingInterceptors = new ArrayList<>();
@ -173,8 +177,35 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
return this;
}
/** for outgoing */
public ProtonClientProtocolManager createClientManager() {
ProtonClientProtocolManager clientOutgoing = new ProtonClientProtocolManager(factory, server);
return clientOutgoing;
}
@Override
public ConnectionEntry createConnectionEntry(Acceptor acceptorUsed, Connection remotingConnection) {
return internalConnectionEntry(remotingConnection, false, null);
}
/** This method is not part of the ProtocolManager interface because it only makes sense on AMQP.
* More specifically on AMQP Bridges */
public ConnectionEntry createOutgoingConnectionEntry(Connection remotingConnection) {
return internalConnectionEntry(remotingConnection, true, null);
}
public ConnectionEntry createOutgoingConnectionEntry(Connection remotingConnection, ClientSASLFactory saslFactory) {
return internalConnectionEntry(remotingConnection, true, saslFactory);
}
/**
* AMQP is an agnostic protocol, client and server.
* This method is used also by the AMQPConenctionBridge where there is no acceptor in place.
* So, this method is to be used by the AMQPConnectionBridge
* @param remotingConnection
* @return
*/
private ConnectionEntry internalConnectionEntry(Connection remotingConnection, boolean outgoing, ClientSASLFactory saslFactory) {
AMQPConnectionCallback connectionCallback = new AMQPConnectionCallback(this, remotingConnection, server.getExecutorFactory().getExecutor(), server);
long ttl = ActiveMQClient.DEFAULT_CONNECTION_TTL;
@ -192,18 +223,18 @@ public class ProtonProtocolManager extends AbstractProtocolManager<AMQPMessage,
String id = server.getConfiguration().getName();
boolean useCoreSubscriptionNaming = server.getConfiguration().isAmqpUseCoreSubscriptionNaming();
AMQPConnectionContext amqpConnection = new AMQPConnectionContext(this, connectionCallback, id, (int) ttl, getMaxFrameSize(), AMQPConstants.Connection.DEFAULT_CHANNEL_MAX, useCoreSubscriptionNaming, server.getScheduledPool(), true, null, null);
AMQPConnectionContext amqpConnection = new AMQPConnectionContext(this, connectionCallback, id, (int) ttl, getMaxFrameSize(), AMQPConstants.Connection.DEFAULT_CHANNEL_MAX, useCoreSubscriptionNaming, server.getScheduledPool(), true, saslFactory, null, outgoing);
Executor executor = server.getExecutorFactory().getExecutor();
ActiveMQProtonRemotingConnection delegate = new ActiveMQProtonRemotingConnection(this, amqpConnection, remotingConnection, executor);
delegate.addFailureListener(connectionCallback);
delegate.addCloseListener(connectionCallback);
ActiveMQProtonRemotingConnection protonRemotingConnection = new ActiveMQProtonRemotingConnection(this, amqpConnection, remotingConnection, executor);
protonRemotingConnection.addFailureListener(connectionCallback);
protonRemotingConnection.addCloseListener(connectionCallback);
connectionCallback.setProtonConnectionDelegate(delegate);
connectionCallback.setProtonConnectionDelegate(protonRemotingConnection);
// connection entry only understands -1 otherwise we would see disconnects for no reason
ConnectionEntry entry = new ConnectionEntry(delegate, executor, System.currentTimeMillis(), ttl <= 0 ? -1 : ttl);
ConnectionEntry entry = new ConnectionEntry(protonRemotingConnection, executor, System.currentTimeMillis(), ttl <= 0 ? -1 : ttl);
return entry;
}

View File

@ -21,8 +21,11 @@ import java.util.Map;
import org.apache.activemq.artemis.api.core.BaseInterceptor;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.connect.AMQPBrokerConnectionManager;
import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManagerFactory;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManager;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
@ -68,4 +71,15 @@ public class ProtonProtocolManagerFactory extends AbstractProtocolManagerFactory
public String getModuleName() {
return MODULE_NAME;
}
/** AMQP integration with the broker on this case needs to be soft.
* As the broker may choose to not load the AMQP Protocol */
@Override
public void loadProtocolServices(ActiveMQServer server, List<ActiveMQComponent> services) {
List<AMQPBrokerConnectConfiguration> amqpServicesConfiguration = server.getConfiguration().getAMQPConnection();
if (amqpServicesConfiguration != null && amqpServicesConfiguration.size() > 0) {
AMQPBrokerConnectionManager bridgeService = new AMQPBrokerConnectionManager(this, amqpServicesConfiguration, server);
services.add(bridgeService);
}
}
}

View File

@ -0,0 +1,556 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.protocol.amqp.connect;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.QueueBinding;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.BrokerConnection;
import org.apache.activemq.artemis.core.server.Consumer;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.broker.ActiveMQProtonRemotingConnection;
import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
import org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerAggregation;
import org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource;
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolLogger;
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPSessionContext;
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext;
import org.apache.activemq.artemis.protocol.amqp.proton.SenderController;
import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry;
import org.apache.activemq.artemis.spi.core.remoting.ClientConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.utils.ConfigurationHelper;
import org.apache.activemq.artemis.utils.UUIDGenerator;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Source;
import org.apache.qpid.proton.amqp.messaging.Target;
import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.apache.qpid.proton.engine.Receiver;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.engine.Session;
import org.jboss.logging.Logger;
public class AMQPBrokerConnection implements ClientConnectionLifeCycleListener, ActiveMQServerQueuePlugin, BrokerConnection {
private static final Logger logger = Logger.getLogger(AMQPBrokerConnection.class);
private final AMQPBrokerConnectConfiguration brokerConnectConfiguration;
private final ProtonProtocolManager protonProtocolManager;
private final ActiveMQServer server;
private final NettyConnector bridgesConnector;
private NettyConnection connection;
private Session session;
private AMQPSessionContext sessionContext;
private ActiveMQProtonRemotingConnection protonRemotingConnection;
private volatile boolean started = false;
private final AMQPBrokerConnectionManager bridgeManager;
private int retryCounter = 0;
private volatile ScheduledFuture reconnectFuture;
private Set<Queue> senders = new HashSet<>();
private Set<Queue> receivers = new HashSet<>();
final Executor connectExecutor;
final ScheduledExecutorService scheduledExecutorService;
/** This is just for logging.
* the actual connection will come from the amqpConnection configuration*/
String host;
/** This is just for logging.
* the actual connection will come from the amqpConnection configuration*/
int port;
public AMQPBrokerConnection(AMQPBrokerConnectionManager bridgeManager, AMQPBrokerConnectConfiguration brokerConnectConfiguration,
ProtonProtocolManager protonProtocolManager,
ActiveMQServer server,
NettyConnector bridgesConnector) {
this.bridgeManager = bridgeManager;
this.brokerConnectConfiguration = brokerConnectConfiguration;
this.protonProtocolManager = protonProtocolManager;
this.server = server;
this.bridgesConnector = bridgesConnector;
connectExecutor = server.getExecutorFactory().getExecutor();
scheduledExecutorService = server.getScheduledPool();
}
@Override
public String getName() {
return brokerConnectConfiguration.getName();
}
@Override
public String getProtocol() {
return "AMQP";
}
@Override
public boolean isStarted() {
return started;
}
@Override
public void stop() {
if (connection != null) {
connection.close();
}
ScheduledFuture scheduledFuture = reconnectFuture;
reconnectFuture = null;
if (scheduledFuture != null) {
scheduledFuture.cancel(true);
}
started = false;
}
@Override
public void start() throws Exception {
started = true;
server.getConfiguration().registerBrokerPlugin(this);
try {
for (AMQPBrokerConnectionElement connectionElement : brokerConnectConfiguration.getConnectionElements()) {
if (connectionElement.getType() == AMQPBrokerConnectionAddressType.MIRROR) {
installMirrorController((AMQPMirrorBrokerConnectionElement)connectionElement, server);
}
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
return;
}
connectExecutor.execute(() -> doConnect());
}
public NettyConnection getConnection() {
return connection;
}
@Override
public void afterCreateQueue(Queue queue) {
connectExecutor.execute(() -> {
for (AMQPBrokerConnectionElement connectionElement : brokerConnectConfiguration.getConnectionElements()) {
validateMatching(queue, connectionElement);
}
});
}
public void validateMatching(Queue queue, AMQPBrokerConnectionElement connectionElement) {
if (connectionElement.getType() != AMQPBrokerConnectionAddressType.MIRROR) {
if (connectionElement.getQueueName() != null) {
if (queue.getName().equals(connectionElement.getQueueName())) {
createLink(queue, connectionElement);
}
} else if (connectionElement.match(queue.getAddress(), server.getConfiguration().getWildcardConfiguration())) {
createLink(queue, connectionElement);
}
}
}
public void createLink(Queue queue, AMQPBrokerConnectionElement connectionElement) {
if (connectionElement.getType() == AMQPBrokerConnectionAddressType.PEER) {
connectSender(queue, queue.getAddress().toString(), Symbol.valueOf("qd.waypoint"));
connectReceiver(protonRemotingConnection, session, sessionContext, queue, Symbol.valueOf("qd.waypoint"));
} else {
if (connectionElement.getType() == AMQPBrokerConnectionAddressType.SENDER) {
connectSender(queue, queue.getAddress().toString());
}
if (connectionElement.getType() == AMQPBrokerConnectionAddressType.RECEIVER) {
connectReceiver(protonRemotingConnection, session, sessionContext, queue);
}
}
}
private void doConnect() {
try {
List<TransportConfiguration> configurationList = brokerConnectConfiguration.getTransportConfigurations();
TransportConfiguration tpConfig = configurationList.get(0);
String hostOnParameter = ConfigurationHelper.getStringProperty(TransportConstants.HOST_PROP_NAME, TransportConstants.DEFAULT_HOST, tpConfig.getParams());
int portOnParameter = ConfigurationHelper.getIntProperty(TransportConstants.PORT_PROP_NAME, TransportConstants.DEFAULT_PORT, tpConfig.getParams());
this.host = hostOnParameter;
this.port = portOnParameter;
connection = bridgesConnector.createConnection(null, hostOnParameter, portOnParameter);
if (connection == null) {
retryConnection();
return;
}
reconnectFuture = null;
retryCounter = 0;
// before we retry the connection we need to remove any previous links
// as they will need to be recreated
senders.clear();
receivers.clear();
ClientSASLFactory saslFactory = null;
if (brokerConnectConfiguration.getUser() != null && brokerConnectConfiguration.getPassword() != null) {
saslFactory = availableMechanims -> {
if (availableMechanims != null && Arrays.asList(availableMechanims).contains("PLAIN")) {
return new PlainSASLMechanism(brokerConnectConfiguration.getUser(), brokerConnectConfiguration.getPassword());
} else {
return null;
}
};
}
ConnectionEntry entry = protonProtocolManager.createOutgoingConnectionEntry(connection, saslFactory);
protonRemotingConnection = (ActiveMQProtonRemotingConnection) entry.connection;
connection.getChannel().pipeline().addLast(new AMQPBrokerConnectionChannelHandler(bridgesConnector.getChannelGroup(), protonRemotingConnection.getAmqpConnection().getHandler()));
protonRemotingConnection.getAmqpConnection().runLater(() -> {
protonRemotingConnection.getAmqpConnection().open();
protonRemotingConnection.getAmqpConnection().flush();
});
session = protonRemotingConnection.getAmqpConnection().getHandler().getConnection().session();
sessionContext = protonRemotingConnection.getAmqpConnection().getSessionExtension(session);
protonRemotingConnection.getAmqpConnection().runLater(() -> {
session.open();
protonRemotingConnection.getAmqpConnection().flush();
});
if (brokerConnectConfiguration.getConnectionElements() != null) {
Stream<Binding> bindingStream = server.getPostOffice().getAllBindings();
bindingStream.forEach(binding -> {
if (binding instanceof QueueBinding) {
Queue queue = ((QueueBinding) binding).getQueue();
for (AMQPBrokerConnectionElement connectionElement : brokerConnectConfiguration.getConnectionElements()) {
validateMatching(queue, connectionElement);
}
}
});
for (AMQPBrokerConnectionElement connectionElement : brokerConnectConfiguration.getConnectionElements()) {
if (connectionElement.getType() == AMQPBrokerConnectionAddressType.MIRROR) {
AMQPMirrorBrokerConnectionElement replica = (AMQPMirrorBrokerConnectionElement)connectionElement;
Queue queue = server.locateQueue(replica.getSourceMirrorAddress());
connectSender(queue, ProtonProtocolManager.MIRROR_ADDRESS);
}
}
}
protonRemotingConnection.getAmqpConnection().flush();
bridgeManager.connected(connection, this);
} catch (Throwable e) {
error(e);
}
}
public void retryConnection() {
if (bridgeManager.isStarted() && started) {
if (brokerConnectConfiguration.getReconnectAttempts() < 0 || retryCounter < brokerConnectConfiguration.getReconnectAttempts()) {
retryCounter++;
ActiveMQAMQPProtocolLogger.LOGGER.retryConnection(brokerConnectConfiguration.getName(), host, port, retryCounter, brokerConnectConfiguration.getReconnectAttempts());
if (logger.isDebugEnabled()) {
logger.debug("Reconnecting in " + brokerConnectConfiguration.getRetryInterval() + ", this is the " + retryCounter + " of " + brokerConnectConfiguration.getReconnectAttempts());
}
reconnectFuture = scheduledExecutorService.schedule(() -> connectExecutor.execute(() -> doConnect()), brokerConnectConfiguration.getRetryInterval(), TimeUnit.MILLISECONDS);
} else {
ActiveMQAMQPProtocolLogger.LOGGER.retryConnectionFailed(brokerConnectConfiguration.getName(), host, port, retryCounter, brokerConnectConfiguration.getReconnectAttempts());
if (logger.isDebugEnabled()) {
logger.debug("no more reconnections as the retry counter reached " + retryCounter + " out of " + brokerConnectConfiguration.getReconnectAttempts());
}
}
}
}
/** The reason this method is static is the following:
*
* It is returning the snfQueue to the replica, and I needed isolation from the actual instance.
* During development I had a mistake where I used a property from the Object,
* so, I needed this isolation for my organization and making sure nothing would be shared. */
private static QueueBinding installMirrorController(AMQPMirrorBrokerConnectionElement replicaConfig, ActiveMQServer server) throws Exception {
AddressInfo addressInfo = server.getAddressInfo(replicaConfig.getSourceMirrorAddress());
if (addressInfo == null) {
addressInfo = new AddressInfo(replicaConfig.getSourceMirrorAddress()).addRoutingType(RoutingType.ANYCAST).setAutoCreated(false).setTemporary(!replicaConfig.isDurable());
server.addAddressInfo(addressInfo);
}
if (addressInfo.getRoutingType() != RoutingType.ANYCAST) {
throw new IllegalArgumentException("sourceMirrorAddress is not ANYCAST");
}
Queue mirrorControlQueue = server.locateQueue(replicaConfig.getSourceMirrorAddress());
if (mirrorControlQueue == null) {
mirrorControlQueue = server.createQueue(new QueueConfiguration(replicaConfig.getSourceMirrorAddress()).setAddress(replicaConfig.getSourceMirrorAddress()).setRoutingType(RoutingType.ANYCAST).setDurable(replicaConfig.isDurable()), true);
}
mirrorControlQueue.setMirrorController(true);
QueueBinding snfReplicaQueueBinding = (QueueBinding)server.getPostOffice().getBinding(replicaConfig.getSourceMirrorAddress());
if (snfReplicaQueueBinding == null) {
logger.warn("Queue does not exist even after creation! " + replicaConfig);
throw new IllegalAccessException("Cannot start replica");
}
Queue snfQueue = snfReplicaQueueBinding.getQueue();
if (!snfQueue.getAddress().equals(replicaConfig.getSourceMirrorAddress())) {
logger.warn("Queue " + snfQueue + " belong to a different address (" + snfQueue.getAddress() + "), while we expected it to be " + addressInfo.getName());
throw new IllegalAccessException("Cannot start replica");
}
AMQPMirrorControllerSource newPartition = new AMQPMirrorControllerSource(snfQueue, server, replicaConfig.isMessageAcknowledgements());
server.scanAddresses(newPartition);
MirrorController currentMirrorController = server.getMirrorController();
if (currentMirrorController == null) {
server.installMirrorController(newPartition);
} else {
// Replace a standard implementation by an aggregated supporting multiple targets
if (currentMirrorController instanceof AMQPMirrorControllerSource) {
// replacing the simple mirror control for an aggregator
AMQPMirrorControllerAggregation remoteAggregation = new AMQPMirrorControllerAggregation();
remoteAggregation.addPartition((AMQPMirrorControllerSource) currentMirrorController);
currentMirrorController = remoteAggregation;
server.installMirrorController(remoteAggregation);
}
((AMQPMirrorControllerAggregation) currentMirrorController).addPartition(newPartition);
}
return snfReplicaQueueBinding;
}
private void connectReceiver(ActiveMQProtonRemotingConnection protonRemotingConnection,
Session session,
AMQPSessionContext sessionContext,
Queue queue,
Symbol... capabilities) {
if (logger.isDebugEnabled()) {
logger.debug("Connecting inbound for " + queue);
}
if (session == null) {
logger.debug("session is null");
return;
}
protonRemotingConnection.getAmqpConnection().runLater(() -> {
if (receivers.contains(queue)) {
logger.debug("Receiver for queue " + queue + " already exists, just giving up");
return;
}
receivers.add(queue);
Receiver receiver = session.receiver(queue.getName().toString() + UUIDGenerator.getInstance().generateStringUUID());
receiver.setSenderSettleMode(SenderSettleMode.UNSETTLED);
receiver.setReceiverSettleMode(ReceiverSettleMode.FIRST);
Target target = new Target();
target.setAddress(queue.getAddress().toString());
receiver.setTarget(target);
Source source = new Source();
source.setAddress(queue.getAddress().toString());
receiver.setSource(source);
if (capabilities != null) {
source.setCapabilities(capabilities);
}
receiver.open();
protonRemotingConnection.getAmqpConnection().flush();
try {
sessionContext.addReceiver(receiver);
} catch (Exception e) {
error(e);
}
});
}
private void connectSender(Queue queue,
String targetName,
Symbol... capabilities) {
if (logger.isDebugEnabled()) {
logger.debug("Connecting outbound for " + queue);
}
if (session == null) {
logger.debug("Session is null");
return;
}
protonRemotingConnection.getAmqpConnection().runLater(() -> {
try {
if (senders.contains(queue)) {
logger.debug("Sender for queue " + queue + " already exists, just giving up");
return;
}
senders.add(queue);
Sender sender = session.sender(targetName + UUIDGenerator.getInstance().generateStringUUID());
sender.setSenderSettleMode(SenderSettleMode.UNSETTLED);
sender.setReceiverSettleMode(ReceiverSettleMode.FIRST);
Target target = new Target();
target.setAddress(queue.getAddress().toString());
if (capabilities != null) {
target.setCapabilities(capabilities);
}
sender.setTarget(target);
Source source = new Source();
source.setAddress(queue.getAddress().toString());
sender.setSource(source);
AMQPOutgoingController outgoingInitializer = new AMQPOutgoingController(queue, sender, sessionContext.getSessionSPI());
ProtonServerSenderContext senderContext = new ProtonServerSenderContext(protonRemotingConnection.getAmqpConnection(), sender, sessionContext, sessionContext.getSessionSPI(), outgoingInitializer);
sessionContext.addSender(sender, senderContext);
} catch (Exception e) {
error(e);
}
protonRemotingConnection.getAmqpConnection().flush();
});
}
protected void error(Throwable e) {
logger.warn(e.getMessage(), e);
redoConnection();
}
private class AMQPOutgoingController implements SenderController {
final Queue queue;
final Sender sender;
final AMQPSessionCallback sessionSPI;
AMQPOutgoingController(Queue queue, Sender sender, AMQPSessionCallback sessionSPI) {
this.queue = queue;
this.sessionSPI = sessionSPI;
this.sender = sender;
}
@Override
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
SimpleString queueName = queue.getName();
return (Consumer) sessionSPI.createSender(senderContext, queueName, null, false);
}
@Override
public void close() throws Exception {
// TODO implement close
}
}
public void disconnect() throws Exception {
redoConnection();
}
@Override
public void connectionCreated(ActiveMQComponent component, Connection connection, ClientProtocolManager protocol) {
}
@Override
public void connectionDestroyed(Object connectionID) {
redoConnection();
}
@Override
public void connectionException(Object connectionID, ActiveMQException me) {
redoConnection();
}
private void redoConnection() {
try {
if (connection != null) {
connection.close();
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
retryConnection();
}
@Override
public void connectionReadyForWrites(Object connectionID, boolean ready) {
protonRemotingConnection.flush();
}
private static class PlainSASLMechanism implements ClientSASL {
private final byte[] initialResponse;
PlainSASLMechanism(String username, String password) {
byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
byte[] encoded = new byte[usernameBytes.length + passwordBytes.length + 2];
System.arraycopy(usernameBytes, 0, encoded, 1, usernameBytes.length);
System.arraycopy(passwordBytes, 0, encoded, usernameBytes.length + 2, passwordBytes.length);
initialResponse = encoded;
}
@Override
public String getName() {
return "PLAIN";
}
@Override
public byte[] getInitialResponse() {
return initialResponse;
}
@Override
public byte[] getResponse(byte[] challenge) {
return new byte[0];
}
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.connect;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.ProtonHandler;
/**
* Common handler implementation for client and server side handler.
*/
public class AMQPBrokerConnectionChannelHandler extends ChannelDuplexHandler {
private final ChannelGroup group;
private final ProtonHandler handler;
volatile boolean active;
protected AMQPBrokerConnectionChannelHandler(final ChannelGroup group, final ProtonHandler handler) {
this.group = group;
this.handler = handler;
}
protected static Object channelId(Channel channel) {
return channel.id();
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
group.add(ctx.channel());
ctx.fireChannelActive();
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception {
ByteBuf buffer = (ByteBuf) msg;
try {
handler.inputBuffer(buffer);
} finally {
buffer.release();
}
}
}

View File

@ -0,0 +1,248 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.protocol.amqp.connect;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import io.netty.channel.ChannelPipeline;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManagerFactory;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.ClientConnectionLifeCycleListener;
import org.apache.activemq.artemis.spi.core.remoting.ClientProtocolManager;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.SessionContext;
import org.apache.activemq.artemis.spi.core.remoting.TopologyResponseHandler;
import org.jboss.logging.Logger;
public class AMQPBrokerConnectionManager implements ActiveMQComponent, ClientConnectionLifeCycleListener {
private static final Logger logger = Logger.getLogger(AMQPBrokerConnectionManager.class);
private final ProtonProtocolManagerFactory protonProtocolManagerFactory;
private final ActiveMQServer server;
private volatile boolean started = false;
List<AMQPBrokerConnectConfiguration> amqpConnectionsConfig;
List<AMQPBrokerConnection> amqpOutgoingConnections;
ProtonProtocolManager protonProtocolManager;
public AMQPBrokerConnectionManager(ProtonProtocolManagerFactory factory, List<AMQPBrokerConnectConfiguration> amqpConnectionsConfig, ActiveMQServer server) {
this.amqpConnectionsConfig = amqpConnectionsConfig;
this.server = server;
this.protonProtocolManagerFactory = factory;
}
@Override
public void start() throws Exception {
if (!started) {
started = true;
}
for (AMQPBrokerConnectConfiguration config : amqpConnectionsConfig) {
NettyConnectorFactory factory = new NettyConnectorFactory().setServerConnector(true);
protonProtocolManager = (ProtonProtocolManager)protonProtocolManagerFactory.createProtocolManager(server, null, null, null);
NettyConnector bridgesConnector = (NettyConnector)factory.createConnector(config.getTransportConfigurations().get(0).getParams(), null, this, server.getExecutorFactory().getExecutor(), server.getThreadPool(), server.getScheduledPool(), new ClientProtocolManagerWithAMQP(protonProtocolManager));
bridgesConnector.start();
if (logger.isDebugEnabled()) {
logger.debug("Connecting " + config);
}
AMQPBrokerConnection amqpBrokerConnection = new AMQPBrokerConnection(this, config, protonProtocolManager, server, bridgesConnector);
server.registerBrokerConnection(amqpBrokerConnection);
if (config.isAutostart()) {
amqpBrokerConnection.start();
}
}
}
public void connected(NettyConnection nettyConnection, AMQPBrokerConnection bridgeConnection) {
}
@Override
public void stop() throws Exception {
if (started) {
started = false;
if (amqpOutgoingConnections != null) {
for (AMQPBrokerConnection connection : amqpOutgoingConnections) {
connection.stop();
}
}
}
}
@Override
public boolean isStarted() {
return started;
}
@Override
public void connectionCreated(ActiveMQComponent component, Connection connection, ClientProtocolManager protocol) {
}
@Override
public void connectionDestroyed(Object connectionID) {
for (AMQPBrokerConnection connection : amqpOutgoingConnections) {
if (connection.getConnection().getID().equals(connectionID)) {
connection.connectionDestroyed(connectionID);
}
}
}
@Override
public void connectionException(Object connectionID, ActiveMQException me) {
for (AMQPBrokerConnection connection : amqpOutgoingConnections) {
if (connection.getConnection().getID().equals(connectionID)) {
connection.connectionException(connectionID, me);
}
}
}
@Override
public void connectionReadyForWrites(Object connectionID, boolean ready) {
for (AMQPBrokerConnection connection : amqpOutgoingConnections) {
if (connection.getConnection().getID().equals(connectionID)) {
connection.connectionReadyForWrites(connectionID, ready);
}
}
}
/** The Client Protocol Manager is used for Core Clients.
* As we are reusing the NettyConnector the API requires a ClientProtocolManager.
* This is to give us the reference for the AMQPConnection used. */
public static class ClientProtocolManagerWithAMQP implements ClientProtocolManager {
public final ProtonProtocolManager protonPM;
public ClientProtocolManagerWithAMQP(ProtonProtocolManager protonPM) {
this.protonPM = protonPM;
}
public ProtonProtocolManager getProtonPM() {
return protonPM;
}
@Override
public ClientProtocolManager setExecutor(Executor executor) {
return null;
}
@Override
public RemotingConnection connect(Connection transportConnection,
long callTimeout,
long callFailoverTimeout,
List<Interceptor> incomingInterceptors,
List<Interceptor> outgoingInterceptors,
TopologyResponseHandler topologyResponseHandler) {
return null;
}
@Override
public RemotingConnection getCurrentConnection() {
return null;
}
@Override
public Lock lockSessionCreation() {
return null;
}
@Override
public boolean waitOnLatch(long milliseconds) throws InterruptedException {
return false;
}
@Override
public void stop() {
}
@Override
public boolean isAlive() {
return false;
}
@Override
public void addChannelHandlers(ChannelPipeline pipeline) {
}
@Override
public void sendSubscribeTopology(boolean isServer) {
}
@Override
public void ping(long connectionTTL) {
}
@Override
public SessionContext createSessionContext(String name,
String username,
String password,
boolean xa,
boolean autoCommitSends,
boolean autoCommitAcks,
boolean preAcknowledge,
int minLargeMessageSize,
int confirmationWindowSize) throws ActiveMQException {
return null;
}
@Override
public boolean cleanupBeforeFailover(ActiveMQException cause) {
return false;
}
@Override
public boolean checkForFailover(String liveNodeID) throws ActiveMQException {
return false;
}
@Override
public void setSessionFactory(ClientSessionFactory factory) {
}
@Override
public ClientSessionFactory getSessionFactory() {
return null;
}
@Override
public String getName() {
return null;
}
}
}

View File

@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.connect.mirror;
import java.util.ArrayList;
import java.util.List;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
/** this will be used when there are multiple replicas in use. */
public class AMQPMirrorControllerAggregation implements MirrorController, ActiveMQComponent {
List<AMQPMirrorControllerSource> partitions = new ArrayList<>();
public void addPartition(AMQPMirrorControllerSource partition) {
this.partitions.add(partition);
}
public void removeParition(AMQPMirrorControllerSource partition) {
this.partitions.remove(partition);
}
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
}
@Override
public boolean isStarted() {
return false;
}
@Override
public void addAddress(AddressInfo addressInfo) throws Exception {
for (MirrorController partition : partitions) {
partition.addAddress(addressInfo);
}
}
@Override
public void deleteAddress(AddressInfo addressInfo) throws Exception {
for (MirrorController partition : partitions) {
partition.deleteAddress(addressInfo);
}
}
@Override
public void createQueue(QueueConfiguration queueConfiguration) throws Exception {
for (MirrorController partition : partitions) {
partition.createQueue(queueConfiguration);
}
}
@Override
public void deleteQueue(SimpleString addressName, SimpleString queueName) throws Exception {
for (MirrorController partition : partitions) {
partition.deleteQueue(addressName, queueName);
}
}
@Override
public void sendMessage(Message message, RoutingContext context, List<MessageReference> refs) {
for (MirrorController partition : partitions) {
partition.sendMessage(message, context, refs);
}
}
@Override
public void postAcknowledge(MessageReference ref, AckReason reason) throws Exception {
for (MirrorController partition : partitions) {
partition.postAcknowledge(ref, reason);
}
}
@Override
public void startAddressScan() throws Exception {
for (MirrorController partition : partitions) {
partition.startAddressScan();
}
}
@Override
public void endAddressScan() throws Exception {
for (MirrorController partition : partitions) {
partition.endAddressScan();
}
}
}

View File

@ -0,0 +1,203 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.connect.mirror;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.paging.PagingStore;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessageBrokerAccessor;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations;
import org.apache.qpid.proton.amqp.messaging.Properties;
import org.jboss.logging.Logger;
public class AMQPMirrorControllerSource implements MirrorController, ActiveMQComponent {
private static final Logger logger = Logger.getLogger(AMQPMirrorControllerSource.class);
public static final Symbol EVENT_TYPE = Symbol.getSymbol("x-opt-amq-mr-ev-type");
public static final Symbol ADDRESS = Symbol.getSymbol("x-opt-amq-mr-adr");
public static final Symbol QUEUE = Symbol.getSymbol("x-opt-amq-mr-qu");
// Events:
public static final Symbol ADD_ADDRESS = Symbol.getSymbol("addAddress");
public static final Symbol DELETE_ADDRESS = Symbol.getSymbol("deleteAddress");
public static final Symbol CREATE_QUEUE = Symbol.getSymbol("createQueue");
public static final Symbol DELETE_QUEUE = Symbol.getSymbol("deleteQueue");
public static final Symbol ADDRESS_SCAN_START = Symbol.getSymbol("AddressCanStart");
public static final Symbol ADDRESS_SCAN_END = Symbol.getSymbol("AddressScanEnd");
public static final Symbol POST_ACK = Symbol.getSymbol("postAck");
// Delivery annotation property used on mirror control routing and Ack
public static final Symbol INTERNAL_ID = Symbol.getSymbol("x-opt-amq-mr-id");
public static final Symbol INTERNAL_DESTINATION = Symbol.getSymbol("x-opt-amq-mr-dst");
private static final ThreadLocal<MirrorControlRouting> mirrorControlRouting = ThreadLocal.withInitial(() -> new MirrorControlRouting(null));
final Queue snfQueue;
final ActiveMQServer server;
final boolean acks;
boolean started;
@Override
public void start() throws Exception {
}
@Override
public void stop() throws Exception {
}
@Override
public boolean isStarted() {
return started;
}
public AMQPMirrorControllerSource(Queue snfQueue, ActiveMQServer server, boolean acks) {
this.snfQueue = snfQueue;
this.server = server;
this.acks = acks;
}
@Override
public void startAddressScan() throws Exception {
Message message = createMessage(null, null, ADDRESS_SCAN_START, null);
route(server, message);
}
@Override
public void endAddressScan() throws Exception {
Message message = createMessage(null, null, ADDRESS_SCAN_END, null);
route(server, message);
}
@Override
public void addAddress(AddressInfo addressInfo) throws Exception {
Message message = createMessage(addressInfo.getName(), null, ADD_ADDRESS, addressInfo.toJSON());
route(server, message);
}
@Override
public void deleteAddress(AddressInfo addressInfo) throws Exception {
Message message = createMessage(addressInfo.getName(), null, DELETE_ADDRESS, addressInfo.toJSON());
route(server, message);
}
@Override
public void createQueue(QueueConfiguration queueConfiguration) throws Exception {
Message message = createMessage(queueConfiguration.getAddress(), queueConfiguration.getName(), CREATE_QUEUE, queueConfiguration.toJSON());
route(server, message);
}
@Override
public void deleteQueue(SimpleString address, SimpleString queue) throws Exception {
Message message = createMessage(address, queue, DELETE_QUEUE, queue.toString());
route(server, message);
}
@Override
public void sendMessage(Message message, RoutingContext context, List<MessageReference> refs) {
try {
context.setReusable(false);
PagingStore storeOwner = null;
if (refs.size() > 0) {
storeOwner = refs.get(0).getOwner();
}
if (storeOwner != null && !storeOwner.getAddress().equals(message.getAddressSimpleString())) {
storeOwner = server.getPagingManager().getPageStore(message.getAddressSimpleString());
}
MessageReference ref = MessageReference.Factory.createReference(message, snfQueue, storeOwner);
snfQueue.refUp(ref);
Map<Symbol, Object> symbolObjectMap = new HashMap<>();
DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(symbolObjectMap);
symbolObjectMap.put(INTERNAL_ID, message.getMessageID());
String address = message.getAddress();
if (address != null) { // this is the message that was set through routing
Properties amqpProperties = getProperties(message);
if (amqpProperties == null || !address.equals(amqpProperties.getTo())) {
// We set the internal destination property only if we need to
// otherwise we just use the one already set over Properties
symbolObjectMap.put(INTERNAL_DESTINATION, message.getAddress());
}
}
ref.setProtocolData(deliveryAnnotations);
refs.add(ref);
message.usageUp();
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
}
}
private static Properties getProperties(Message message) {
if (message instanceof AMQPMessage) {
return AMQPMessageBrokerAccessor.getCurrentProperties((AMQPMessage)message);
} else {
return null;
}
}
@Override
public void postAcknowledge(MessageReference ref, AckReason reason) throws Exception {
if (acks && !ref.getQueue().isMirrorController()) { // we don't call postACK on snfqueues, otherwise we would get infinite loop because of this feedback
Message message = createMessage(ref.getQueue().getAddress(), ref.getQueue().getName(), POST_ACK, ref.getMessage().getMessageID());
route(server, message);
ref.getMessage().usageDown();
}
}
private Message createMessage(SimpleString address, SimpleString queue, Object event, Object body) {
return AMQPMirrorMessageFactory.createMessage(snfQueue.getAddress().toString(), address, queue, event, body);
}
public static void route(ActiveMQServer server, Message message) throws Exception {
message.setMessageID(server.getStorageManager().generateID());
MirrorControlRouting ctx = mirrorControlRouting.get();
ctx.clear();
server.getPostOffice().route(message, ctx, false);
}
private static class MirrorControlRouting extends RoutingContextImpl {
MirrorControlRouting(Transaction transaction) {
super(transaction);
}
@Override
public boolean isMirrorController() {
return true;
}
}
}

View File

@ -0,0 +1,361 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.connect.mirror;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.ToLongFunction;
import java.util.stream.Stream;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessageBrokerAccessor;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPConnectionContext;
import org.apache.activemq.artemis.protocol.amqp.proton.AMQPSessionContext;
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonAbstractReceiver;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Receiver;
import org.jboss.logging.Logger;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.EVENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.ADDRESS;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.INTERNAL_DESTINATION;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.POST_ACK;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.QUEUE;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.ADD_ADDRESS;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.DELETE_ADDRESS;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.CREATE_QUEUE;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.DELETE_QUEUE;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.INTERNAL_ID;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.ADDRESS_SCAN_START;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.ADDRESS_SCAN_END;
public class AMQPMirrorControllerTarget extends ProtonAbstractReceiver implements MirrorController {
public static final SimpleString INTERNAL_ID_EXTRA_PROPERTY = SimpleString.toSimpleString(INTERNAL_ID.toString());
private static final Logger logger = Logger.getLogger(AMQPMirrorControllerTarget.class);
final ActiveMQServer server;
final RoutingContextImpl routingContext = new RoutingContextImpl(null);
Map<SimpleString, Map<SimpleString, QueueConfiguration>> scanAddresses;
public AMQPMirrorControllerTarget(AMQPSessionCallback sessionSPI,
AMQPConnectionContext connection,
AMQPSessionContext protonSession,
Receiver receiver,
ActiveMQServer server) {
super(sessionSPI, connection, protonSession, receiver);
this.server = server;
}
@Override
public void flow() {
creditRunnable.run();
}
@Override
protected void actualDelivery(AMQPMessage message, Delivery delivery, Receiver receiver, Transaction tx) {
incrementSettle();
if (logger.isDebugEnabled()) {
logger.debug(server.getIdentity() + "::Received " + message);
}
try {
/** We use message annotations, because on the same link we will receive control messages
* coming from mirror events,
* and the actual messages that need to be replicated.
* Using anything from the body would force us to parse the body on regular messages.
* The body of the message may still be used on control messages, on cases where a JSON string is sent. */
Object eventType = AMQPMessageBrokerAccessor.getMessageAnnotationProperty(message, EVENT_TYPE);
if (eventType != null) {
if (eventType.equals(ADDRESS_SCAN_START)) {
logger.debug("Starting scan for removed queues");
startAddressScan();
} else if (eventType.equals(ADDRESS_SCAN_END)) {
logger.debug("Ending scan for removed queues");
endAddressScan();
} else if (eventType.equals(ADD_ADDRESS)) {
AddressInfo addressInfo = parseAddress(message);
if (logger.isDebugEnabled()) {
logger.debug("Adding Address " + addressInfo);
}
addAddress(addressInfo);
} else if (eventType.equals(DELETE_ADDRESS)) {
AddressInfo addressInfo = parseAddress(message);
if (logger.isDebugEnabled()) {
logger.debug("Removing Address " + addressInfo);
}
deleteAddress(addressInfo);
} else if (eventType.equals(CREATE_QUEUE)) {
QueueConfiguration queueConfiguration = parseQueue(message);
if (logger.isDebugEnabled()) {
logger.debug("Creating queue " + queueConfiguration);
}
createQueue(queueConfiguration);
} else if (eventType.equals(DELETE_QUEUE)) {
String address = (String) AMQPMessageBrokerAccessor.getMessageAnnotationProperty(message, ADDRESS);
String queueName = (String) AMQPMessageBrokerAccessor.getMessageAnnotationProperty(message, QUEUE);
if (logger.isDebugEnabled()) {
logger.debug("Deleting queue " + queueName + " on address " + address);
}
deleteQueue(SimpleString.toSimpleString(address), SimpleString.toSimpleString(queueName));
} else if (eventType.equals(POST_ACK)) {
String address = (String) AMQPMessageBrokerAccessor.getMessageAnnotationProperty(message, ADDRESS);
String queueName = (String) AMQPMessageBrokerAccessor.getMessageAnnotationProperty(message, QUEUE);
AmqpValue value = (AmqpValue) message.getBody();
Long messageID = (Long) value.getValue();
if (logger.isDebugEnabled()) {
logger.debug("Post ack address=" + address + " queueName = " + queueName + " messageID=" + messageID);
}
postAcknowledge(address, queueName, messageID);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Sending message " + message);
}
sendMessage(message);
}
} catch (Throwable e) {
logger.warn(e.getMessage(), e);
} finally {
delivery.disposition(Accepted.getInstance());
settle(delivery);
connection.flush();
}
}
@Override
public void initialize() throws Exception {
super.initialize();
org.apache.qpid.proton.amqp.messaging.Target target = (org.apache.qpid.proton.amqp.messaging.Target) receiver.getRemoteTarget();
// Match the settlement mode of the remote instead of relying on the default of MIXED.
receiver.setSenderSettleMode(receiver.getRemoteSenderSettleMode());
// We don't currently support SECOND so enforce that the answer is anlways FIRST
receiver.setReceiverSettleMode(ReceiverSettleMode.FIRST);
flow();
}
private QueueConfiguration parseQueue(AMQPMessage message) throws Exception {
AmqpValue bodyvalue = (AmqpValue) message.getBody();
String body = (String) bodyvalue.getValue();
QueueConfiguration queueConfiguration = QueueConfiguration.fromJSON(body);
return queueConfiguration;
}
private AddressInfo parseAddress(AMQPMessage message) throws Exception {
AmqpValue bodyvalue = (AmqpValue) message.getBody();
String body = (String) bodyvalue.getValue();
AddressInfo addressInfo = AddressInfo.fromJSON(body);
return addressInfo;
}
@Override
public void startAddressScan() throws Exception {
scanAddresses = new HashMap<>();
}
@Override
public void endAddressScan() throws Exception {
Map<SimpleString, Map<SimpleString, QueueConfiguration>> scannedAddresses = scanAddresses;
this.scanAddresses = null;
Stream<Binding> bindings = server.getPostOffice().getAllBindings();
bindings.forEach((binding) -> {
if (binding instanceof LocalQueueBinding) {
LocalQueueBinding localQueueBinding = (LocalQueueBinding) binding;
Map<SimpleString, QueueConfiguration> scannedQueues = scannedAddresses.get(localQueueBinding.getQueue().getAddress());
if (scannedQueues == null) {
if (logger.isDebugEnabled()) {
logger.debug("There's no address " + localQueueBinding.getQueue().getAddress() + " so, removing queue");
}
try {
deleteQueue(localQueueBinding.getQueue().getAddress(), localQueueBinding.getQueue().getName());
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
} else {
QueueConfiguration queueConfg = scannedQueues.get(localQueueBinding.getQueue().getName());
if (queueConfg == null) {
if (logger.isDebugEnabled()) {
logger.debug("There no queue for " + localQueueBinding.getQueue().getName() + " so, removing queue");
}
try {
deleteQueue(localQueueBinding.getQueue().getAddress(), localQueueBinding.getQueue().getName());
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
}
});
}
private Map<SimpleString, QueueConfiguration> getQueueScanMap(SimpleString address) {
Map<SimpleString, QueueConfiguration> queueMap = scanAddresses.get(address);
if (queueMap == null) {
queueMap = new HashMap<>();
scanAddresses.put(address, queueMap);
}
return queueMap;
}
@Override
public void addAddress(AddressInfo addressInfo) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Adding address " + addressInfo);
}
server.addAddressInfo(addressInfo);
}
@Override
public void deleteAddress(AddressInfo addressInfo) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("delete address " + addressInfo);
}
try {
server.removeAddressInfo(addressInfo.getName(), null, true);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
@Override
public void createQueue(QueueConfiguration queueConfiguration) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Adding queue " + queueConfiguration);
}
server.createQueue(queueConfiguration, true);
if (scanAddresses != null) {
getQueueScanMap(queueConfiguration.getAddress()).put(queueConfiguration.getName(), queueConfiguration);
}
}
@Override
public void deleteQueue(SimpleString addressName, SimpleString queueName) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("destroy queue " + queueName + " on address = " + addressName);
}
server.destroyQueue(queueName);
}
private static ToLongFunction<MessageReference> referenceIDSupplier = (source) -> {
Long id = (Long) source.getMessage().getBrokerProperty(INTERNAL_ID_EXTRA_PROPERTY);
if (id == null) {
return -1;
} else {
return id;
}
};
public void postAcknowledge(String address, String queue, long messageID) {
if (logger.isDebugEnabled()) {
logger.debug("post acking " + address + ", queue = " + queue + ", messageID = " + messageID);
}
Queue targetQueue = server.locateQueue(queue);
if (targetQueue != null) {
MessageReference reference = targetQueue.removeWithSuppliedID(messageID, referenceIDSupplier);
if (reference != null) {
if (logger.isDebugEnabled()) {
logger.debug("Acking reference " + reference);
}
try {
targetQueue.acknowledge(reference);
} catch (Exception e) {
// TODO anything else I can do here?
// such as close the connection with error?
logger.warn(e.getMessage(), e);
}
} else {
if (logger.isTraceEnabled()) {
logger.trace("There is no reference to ack on " + messageID);
}
}
}
}
private void sendMessage(AMQPMessage message) throws Exception {
if (message.getMessageID() <= 0) {
message.setMessageID(server.getStorageManager().generateID());
}
Long internalID = (Long) AMQPMessageBrokerAccessor.getDeliveryAnnotationProperty(message, INTERNAL_ID);
String internalAddress = (String) AMQPMessageBrokerAccessor.getDeliveryAnnotationProperty(message, INTERNAL_DESTINATION);
if (internalID != null) {
message.setBrokerProperty(INTERNAL_ID_EXTRA_PROPERTY, internalID);
}
if (internalAddress != null) {
message.setAddress(internalAddress);
}
routingContext.clear();
server.getPostOffice().route(message, routingContext, false);
flow();
}
/**
* not implemented on the target, treated at {@link #postAcknowledge(String, String, long)}
*
* @param ref
* @param reason
*/
@Override
public void postAcknowledge(MessageReference ref, AckReason reason) {
}
/**
* not implemented on the target, treated at {@link #sendMessage(AMQPMessage)}
*
* @param message
* @param context
* @param refs
*/
@Override
public void sendMessage(Message message, RoutingContext context, List<MessageReference> refs) {
}
}

View File

@ -0,0 +1,91 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.connect.mirror;
import java.util.HashMap;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPStandardMessage;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.WritableBuffer;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.ADDRESS;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.EVENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource.QUEUE;
/**
* This class is responsible for creating the internal message types used on controlling the mirror on the AMQP module.
*/
public class AMQPMirrorMessageFactory {
/**
* This method is open to make it testable,
* do not use on your applications.
*/
public static Message createMessage(String to, SimpleString address, SimpleString queue, Object event, Object body) {
Header header = new Header();
header.setDurable(true);
Map<Symbol, Object> annotations = new HashMap<>();
annotations.put(EVENT_TYPE, event);
if (address != null) {
annotations.put(ADDRESS, address.toString());
}
if (queue != null) {
annotations.put(QUEUE, queue.toString());
}
MessageAnnotations messageAnnotations = new MessageAnnotations(annotations);
Section sectionBody = body != null ? new AmqpValue(body) : null;
ByteBuf buffer = PooledByteBufAllocator.DEFAULT.heapBuffer(1024);
try {
EncoderImpl encoder = TLSEncode.getEncoder();
encoder.setByteBuffer(new NettyWritable(buffer));
encoder.writeObject(header);
encoder.writeObject(messageAnnotations);
if (sectionBody != null) {
encoder.writeObject(sectionBody);
}
byte[] data = new byte[buffer.writerIndex()];
buffer.readBytes(data);
AMQPMessage amqpMessage = new AMQPStandardMessage(0, data, null);
amqpMessage.setAddress(to);
return amqpMessage;
} finally {
TLSEncode.getEncoder().setByteBuffer((WritableBuffer) null);
buffer.release();
}
}
}

View File

@ -48,4 +48,16 @@ public interface ActiveMQAMQPProtocolLogger extends BasicLogger {
@LogMessage(level = Logger.Level.WARN)
@Message(id = 111000, value = "Scheduled task can't be removed from scheduledPool.", format = Message.Format.MESSAGE_FORMAT)
void cantRemovingScheduledTask();
@LogMessage(level = Logger.Level.WARN)
@Message(id = 111001, value = "\n*******************************************************************************************************************************" +
"\nCould not re-establish AMQP Server Connection {0} on {1}:{2} after {3} retries with a total configured of {4}" +
"\n*******************************************************************************************************************************\n", format = Message.Format.MESSAGE_FORMAT)
void retryConnectionFailed(String name, String host, int port, int currentRetry, int maxRetry);
@LogMessage(level = Logger.Level.INFO)
@Message(id = 111002, value = "\n*******************************************************************************************************************************" +
"\nRetrying Server AMQP Connection {0} on {1}:{2} retry {3} of {4}" +
"\n*******************************************************************************************************************************\n", format = Message.Format.MESSAGE_FORMAT)
void retryConnection(String name, String host, int port, int currentRetry, int maxRetry);
}

View File

@ -32,20 +32,27 @@ import java.util.function.UnaryOperator;
import io.netty.buffer.ByteBuf;
import io.netty.channel.EventLoop;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnection;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.SecurityAuth;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPConnectionCallback;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.broker.ProtonProtocolManager;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolLogger;
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.EventHandler;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.ExecutorNettyAdapter;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.ExtCapability;
import org.apache.activemq.artemis.protocol.amqp.proton.handler.ProtonHandler;
import org.apache.activemq.artemis.protocol.amqp.sasl.AnonymousServerSASL;
import org.apache.activemq.artemis.protocol.amqp.sasl.ClientSASLFactory;
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
import org.apache.activemq.artemis.utils.ByteUtil;
import org.apache.activemq.artemis.utils.VersionLoader;
@ -56,6 +63,7 @@ import org.apache.qpid.proton.amqp.transaction.Coordinator;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Link;
import org.apache.qpid.proton.engine.Receiver;
import org.apache.qpid.proton.engine.Sender;
@ -79,7 +87,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
protected final ProtonHandler handler;
protected AMQPConnectionCallback connectionCallback;
private AMQPConnectionCallback connectionCallback;
private final String containerId;
private final boolean isIncomingConnection;
private final ClientSASLFactory saslClientFactory;
@ -92,6 +100,9 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
private final boolean useCoreSubscriptionNaming;
/** Outgoing means created by the AMQP Bridge */
private final boolean bridgeConnection;
private final ScheduleOperator scheduleOp = new ScheduleOperator(new ScheduleRunnable());
private final AtomicReference<Future<?>> scheduledFutureRef = new AtomicReference(VOID_FUTURE);
@ -106,8 +117,24 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
boolean isIncomingConnection,
ClientSASLFactory saslClientFactory,
Map<Symbol, Object> connectionProperties) {
this(protocolManager, connectionSP, containerId, idleTimeout, maxFrameSize, channelMax, useCoreSubscriptionNaming, scheduledPool, isIncomingConnection, saslClientFactory, connectionProperties, false);
}
public AMQPConnectionContext(ProtonProtocolManager protocolManager,
AMQPConnectionCallback connectionSP,
String containerId,
int idleTimeout,
int maxFrameSize,
int channelMax,
boolean useCoreSubscriptionNaming,
ScheduledExecutorService scheduledPool,
boolean isIncomingConnection,
ClientSASLFactory saslClientFactory,
Map<Symbol, Object> connectionProperties,
boolean bridgeConnection) {
this.protocolManager = protocolManager;
this.bridgeConnection = bridgeConnection;
this.connectionCallback = connectionSP;
this.useCoreSubscriptionNaming = useCoreSubscriptionNaming;
this.containerId = (containerId != null) ? containerId : UUID.randomUUID().toString();
@ -129,7 +156,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
} else {
nettyExecutor = new ExecutorNettyAdapter(protocolManager.getServer().getExecutorFactory().getExecutor());
}
this.handler = new ProtonHandler(nettyExecutor, protocolManager.getServer().getExecutorFactory().getExecutor(), isIncomingConnection);
this.handler = new ProtonHandler(nettyExecutor, protocolManager.getServer().getExecutorFactory().getExecutor(), isIncomingConnection && saslClientFactory == null);
handler.addEventHandler(this);
Transport transport = handler.getTransport();
transport.setEmitFlowEventOnSend(false);
@ -140,7 +167,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
transport.setInitialRemoteMaxFrameSize(protocolManager.getInitialRemoteMaxFrameSize());
transport.setMaxFrameSize(maxFrameSize);
transport.setOutboundFrameSizeLimit(maxFrameSize);
if (!isIncomingConnection && saslClientFactory != null) {
if (saslClientFactory != null) {
handler.createClientSASL();
}
}
@ -167,11 +194,15 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
protected AMQPSessionContext newSessionExtension(Session realSession) throws ActiveMQAMQPException {
AMQPSessionCallback sessionSPI = connectionCallback.createSessionCallback(this);
AMQPSessionContext protonSession = new AMQPSessionContext(sessionSPI, this, realSession);
AMQPSessionContext protonSession = new AMQPSessionContext(sessionSPI, this, realSession, protocolManager.getServer());
return protonSession;
}
public SecurityAuth getSecurityAuth() {
return new LocalSecurity();
}
public SASLResult getSASLResult() {
return handler.getSASLResult();
}
@ -184,6 +215,10 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
handler.inputBuffer(buffer);
}
public ProtonHandler getHandler() {
return handler;
}
public void destroy() {
handler.runLater(() -> connectionCallback.close());
}
@ -217,7 +252,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
handler.close(errorCondition, this);
}
protected AMQPSessionContext getSessionExtension(Session realSession) throws ActiveMQAMQPException {
public AMQPSessionContext getSessionExtension(Session realSession) throws ActiveMQAMQPException {
AMQPSessionContext sessionExtension = sessions.get(realSession);
if (sessionExtension == null) {
// how this is possible? Log a warn here
@ -271,6 +306,10 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
AMQPSessionContext protonSession = getSessionExtension(link.getSession());
if (link.getLocalState() == EndpointState.ACTIVE) { // if already active it's probably from the AMQP bridge and hence we just ignore it
return;
}
link.setSource(link.getRemoteSource());
link.setTarget(link.getRemoteTarget());
if (link instanceof Receiver) {
@ -279,7 +318,16 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
Coordinator coordinator = (Coordinator) link.getRemoteTarget();
protonSession.addTransactionHandler(coordinator, receiver);
} else {
protonSession.addReceiver(receiver);
if (isReplicaTarget(receiver)) {
try {
protonSession.getSessionSPI().check(SimpleString.toSimpleString(ProtonProtocolManager.MIRROR_ADDRESS), CheckType.SEND, getSecurityAuth());
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingProducer(e.getMessage());
}
protonSession.addReplicaTarget(receiver);
} else {
protonSession.addReceiver(receiver);
}
}
} else {
Sender sender = (Sender) link;
@ -287,6 +335,10 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
}
}
private boolean isReplicaTarget(Link link) {
return link != null && link.getTarget() != null && link.getTarget().getAddress() != null && link.getTarget().getAddress().equals(ProtonProtocolManager.MIRROR_ADDRESS);
}
public Symbol[] getConnectionCapabilitiesOffered() {
URI tc = connectionCallback.getFailoverList();
if (tc != null) {
@ -422,7 +474,7 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
connection.setOfferedCapabilities(getConnectionCapabilitiesOffered());
connection.open();
}
initialise();
initialize();
/*
* This can be null which is in effect an empty map, also we really don't need to check this for in bound connections
@ -509,13 +561,17 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
@Override
public void onLocalOpen(Session session) throws Exception {
getSessionExtension(session);
AMQPSessionContext sessionContext = getSessionExtension(session);
if (bridgeConnection) {
sessionContext.initialize();
}
}
@Override
public void onRemoteOpen(Session session) throws Exception {
handler.requireHandler();
getSessionExtension(session).initialise();
getSessionExtension(session).initialize();
session.open();
}
@ -607,4 +663,42 @@ public class AMQPConnectionContext extends ProtonInitializable implements EventH
log.warn("Handler is null, can't delivery " + delivery, new Exception("tracing location"));
}
}
private class LocalSecurity implements SecurityAuth {
@Override
public String getUsername() {
String username = null;
SASLResult saslResult = getSASLResult();
if (saslResult != null) {
username = saslResult.getUser();
}
return username;
}
@Override
public String getPassword() {
String password = null;
SASLResult saslResult = getSASLResult();
if (saslResult != null) {
if (saslResult instanceof PlainSASLResult) {
password = ((PlainSASLResult) saslResult).getPassword();
}
}
return password;
}
@Override
public RemotingConnection getRemotingConnection() {
return connectionCallback.getProtonConnectionDelegate();
}
@Override
public String getSecurityDomain() {
return getProtocolManager().getSecurityDomain();
}
}
}

View File

@ -22,8 +22,10 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ServerProducer;
import org.apache.activemq.artemis.core.server.impl.ServerProducerImpl;
import org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerTarget;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.client.ProtonClientSenderContext;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
@ -46,7 +48,7 @@ public class AMQPSessionContext extends ProtonInitializable {
protected final Session session;
protected Map<Receiver, ProtonServerReceiverContext> receivers = new ConcurrentHashMap<>();
protected Map<Receiver, ProtonAbstractReceiver> receivers = new ConcurrentHashMap<>();
protected Map<Sender, ProtonServerSenderContext> senders = new ConcurrentHashMap<>();
@ -54,18 +56,25 @@ public class AMQPSessionContext extends ProtonInitializable {
protected final AmqpTransferTagGenerator tagCache = new AmqpTransferTagGenerator();
public AMQPSessionContext(AMQPSessionCallback sessionSPI, AMQPConnectionContext connection, Session session) {
protected final ActiveMQServer server;
public AMQPSessionContext(AMQPSessionCallback sessionSPI, AMQPConnectionContext connection, Session session, ActiveMQServer server) {
this.connection = connection;
this.sessionSPI = sessionSPI;
this.session = session;
this.server = server;
}
protected Map<Object, ProtonServerSenderContext> serverSenders = new ConcurrentHashMap<>();
public AMQPSessionCallback getSessionSPI() {
return sessionSPI;
}
@Override
public void initialise() throws Exception {
public void initialize() throws Exception {
if (!isInitialized()) {
super.initialise();
super.initialize();
if (sessionSPI != null) {
try {
@ -111,10 +120,10 @@ public class AMQPSessionContext extends ProtonInitializable {
}
// Making a copy to avoid ConcurrentModificationException during the iteration
Set<ProtonServerReceiverContext> receiversCopy = new HashSet<>();
Set<ProtonAbstractReceiver> receiversCopy = new HashSet<>();
receiversCopy.addAll(receivers.values());
for (ProtonServerReceiverContext protonProducer : receiversCopy) {
for (ProtonAbstractReceiver protonProducer : receiversCopy) {
try {
protonProducer.close(false);
} catch (Exception e) {
@ -164,12 +173,19 @@ public class AMQPSessionContext extends ProtonInitializable {
}
public void addSender(Sender sender) throws Exception {
addSender(sender, (SenderController)null);
}
public void addSender(Sender sender, SenderController senderController) throws Exception {
// TODO: Remove this check when we have support for global link names
boolean outgoing = (sender.getContext() != null && sender.getContext().equals(true));
ProtonServerSenderContext protonSender = outgoing ? new ProtonClientSenderContext(connection, sender, this, sessionSPI) : new ProtonServerSenderContext(connection, sender, this, sessionSPI);
ProtonServerSenderContext protonSender = outgoing ? new ProtonClientSenderContext(connection, sender, this, sessionSPI) : new ProtonServerSenderContext(connection, sender, this, sessionSPI, senderController);
addSender(sender, protonSender);
}
public void addSender(Sender sender, ProtonServerSenderContext protonSender) throws Exception {
try {
protonSender.initialise();
protonSender.initialize();
senders.put(sender, protonSender);
serverSenders.put(protonSender.getBrokerConsumer(), protonSender);
sender.setContext(protonSender);
@ -200,10 +216,33 @@ public class AMQPSessionContext extends ProtonInitializable {
}
}
public void addReplicaTarget(Receiver receiver) throws Exception {
try {
AMQPMirrorControllerTarget protonReceiver = new AMQPMirrorControllerTarget(sessionSPI, connection, this, receiver, server);
protonReceiver.initialize();
receivers.put(receiver, protonReceiver);
ServerProducer serverProducer = new ServerProducerImpl(receiver.getName(), "AMQP", receiver.getTarget().getAddress());
sessionSPI.addProducer(serverProducer);
receiver.setContext(protonReceiver);
connection.runNow(() -> {
receiver.open();
connection.flush();
});
} catch (ActiveMQAMQPException e) {
receivers.remove(receiver);
receiver.setTarget(null);
receiver.setCondition(new ErrorCondition(e.getAmqpError(), e.getMessage()));
connection.runNow(() -> {
receiver.close();
connection.flush();
});
}
}
public void addReceiver(Receiver receiver) throws Exception {
try {
ProtonServerReceiverContext protonReceiver = new ProtonServerReceiverContext(sessionSPI, connection, this, receiver);
protonReceiver.initialise();
protonReceiver.initialize();
receivers.put(receiver, protonReceiver);
ServerProducer serverProducer = new ServerProducerImpl(receiver.getName(), "AMQP", receiver.getTarget().getAddress());
sessionSPI.addProducer(serverProducer);

View File

@ -0,0 +1,303 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.protocol.amqp.proton;
import org.apache.activemq.artemis.core.persistence.impl.nullpm.NullStorageManager;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPLargeMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInternalErrorException;
import org.apache.qpid.proton.amqp.transaction.TransactionalState;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Receiver;
public abstract class ProtonAbstractReceiver extends ProtonInitializable implements ProtonDeliveryHandler {
protected final AMQPConnectionContext connection;
protected final AMQPSessionContext protonSession;
protected final Receiver receiver;
/*
The maximum number of credits we will allocate to clients.
This number is also used by the broker when refresh client credits.
*/
protected final int amqpCredits;
// Used by the broker to decide when to refresh clients credit. This is not used when client requests credit.
protected final int minCreditRefresh;
protected final int minLargeMessageSize;
final RoutingContext routingContext;
protected final AMQPSessionCallback sessionSPI;
protected volatile AMQPLargeMessage currentLargeMessage;
/**
* We create this AtomicRunnable with setRan.
* This is because we always reuse the same instance.
* In case the creditRunnable was run, we reset and send it over.
* We set it as ran as the first one should always go through
*/
protected final Runnable creditRunnable;
protected final boolean useModified;
protected int pendingSettles = 0;
public static boolean isBellowThreshold(int credit, int pending, int threshold) {
return credit <= threshold - pending;
}
public static int calculatedUpdateRefill(int refill, int credits, int pending) {
return refill - credits - pending;
}
public ProtonAbstractReceiver(AMQPSessionCallback sessionSPI,
AMQPConnectionContext connection,
AMQPSessionContext protonSession,
Receiver receiver) {
this.sessionSPI = sessionSPI;
this.connection = connection;
this.protonSession = protonSession;
this.receiver = receiver;
this.amqpCredits = connection.getAmqpCredits();
this.minCreditRefresh = connection.getAmqpLowCredits();
this.minLargeMessageSize = connection.getProtocolManager().getAmqpMinLargeMessageSize();
this.creditRunnable = createCreditRunnable(amqpCredits, minCreditRefresh, receiver, connection, this);
useModified = this.connection.getProtocolManager().isUseModifiedForTransientDeliveryErrors();
this.routingContext = new RoutingContextImpl(null).setDuplicateDetection(connection.getProtocolManager().isAmqpDuplicateDetection());
if (sessionSPI != null) {
sessionSPI.addCloseable((boolean failed) -> clearLargeMessage());
}
}
protected void clearLargeMessage() {
connection.runNow(() -> {
if (currentLargeMessage != null) {
try {
currentLargeMessage.deleteFile();
} catch (Throwable error) {
ActiveMQServerLogger.LOGGER.errorDeletingLargeMessageFile(error);
} finally {
currentLargeMessage = null;
}
}
});
}
/**
* This Credit Runnable may be used in Mock tests to simulate the credit semantic here
*/
public static Runnable createCreditRunnable(int refill,
int threshold,
Receiver receiver,
AMQPConnectionContext connection,
ProtonAbstractReceiver context) {
return new FlowControlRunner(refill, threshold, receiver, connection, context);
}
/**
* This Credit Runnable may be used in Mock tests to simulate the credit semantic here
*/
public static Runnable createCreditRunnable(int refill,
int threshold,
Receiver receiver,
AMQPConnectionContext connection) {
return new FlowControlRunner(refill, threshold, receiver, connection, null);
}
/**
* The reason why we use the AtomicRunnable here
* is because PagingManager will call Runnables in case it was blocked.
* however it could call many Runnables
* and this serves as a control to avoid duplicated calls
* */
static class FlowControlRunner implements Runnable {
final int refill;
final int threshold;
final Receiver receiver;
final AMQPConnectionContext connection;
final ProtonAbstractReceiver context;
FlowControlRunner(int refill, int threshold, Receiver receiver, AMQPConnectionContext connection, ProtonAbstractReceiver context) {
this.refill = refill;
this.threshold = threshold;
this.receiver = receiver;
this.connection = connection;
this.context = context;
}
@Override
public void run() {
if (!connection.isHandler()) {
// for the case where the paging manager is resuming flow due to blockage
// this should then move back to the connection thread.
connection.runLater(this);
return;
}
connection.requireInHandler();
int pending = context != null ? context.pendingSettles : 0;
if (isBellowThreshold(receiver.getCredit(), pending, threshold)) {
int topUp = calculatedUpdateRefill(refill, receiver.getCredit(), pending);
if (topUp > 0) {
receiver.flow(topUp);
connection.instantFlush();
}
}
}
}
public int incrementSettle() {
assert pendingSettles >= 0;
connection.requireInHandler();
return pendingSettles++;
}
public void settle(Delivery settlement) {
connection.requireInHandler();
pendingSettles--;
assert pendingSettles >= 0;
settlement.settle();
flow();
}
@Override
public void onFlow(int credits, boolean drain) {
flow();
}
/*
* called when Proton receives a message to be delivered via a Delivery.
*
* This may be called more than once per deliver so we have to cache the buffer until we have received it all.
*/
@Override
public void onMessage(Delivery delivery) throws ActiveMQAMQPException {
connection.requireInHandler();
Receiver receiver = ((Receiver) delivery.getLink());
if (receiver.current() != delivery) {
return;
}
try {
if (delivery.isAborted()) {
clearLargeMessage();
// Aborting implicitly remotely settles, so advance
// receiver to the next delivery and settle locally.
receiver.advance();
delivery.settle();
// Replenish the credit if not doing a drain
if (!receiver.getDrain()) {
receiver.flow(1);
}
return;
} else if (delivery.isPartial()) {
if (sessionSPI.getStorageManager() instanceof NullStorageManager) {
// if we are dealing with the NullStorageManager we should just make it a regular message anyways
return;
}
if (currentLargeMessage == null) {
// minLargeMessageSize < 0 means no large message treatment, make it disabled
if (minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
initializeCurrentLargeMessage(delivery, receiver);
}
} else {
currentLargeMessage.addBytes(receiver.recv());
}
return;
}
AMQPMessage message;
// this is treating the case where the frameSize > minLargeMessage and the message is still large enough
if (!(sessionSPI.getStorageManager() instanceof NullStorageManager) && currentLargeMessage == null && minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
initializeCurrentLargeMessage(delivery, receiver);
}
if (currentLargeMessage != null) {
currentLargeMessage.addBytes(receiver.recv());
receiver.advance();
currentLargeMessage.finishParse();
message = currentLargeMessage;
currentLargeMessage = null;
} else {
ReadableBuffer data = receiver.recv();
receiver.advance();
message = sessionSPI.createStandardMessage(delivery, data);
}
Transaction tx = null;
if (delivery.getRemoteState() instanceof TransactionalState) {
TransactionalState txState = (TransactionalState) delivery.getRemoteState();
tx = this.sessionSPI.getTransaction(txState.getTxnId(), false);
}
actualDelivery(message, delivery, receiver, tx);
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
}
protected void initializeCurrentLargeMessage(Delivery delivery, Receiver receiver) throws Exception {
long id = sessionSPI.getStorageManager().generateID();
currentLargeMessage = new AMQPLargeMessage(id, delivery.getMessageFormat(), null, sessionSPI.getCoreMessageObjectPools(), sessionSPI.getStorageManager());
ReadableBuffer dataBuffer = receiver.recv();
currentLargeMessage.parseHeader(dataBuffer);
sessionSPI.getStorageManager().largeMessageCreated(id, currentLargeMessage);
currentLargeMessage.addBytes(dataBuffer);
}
@Override
public void close(boolean remoteLinkClose) throws ActiveMQAMQPException {
protonSession.removeReceiver(receiver);
}
@Override
public void close(ErrorCondition condition) throws ActiveMQAMQPException {
receiver.setCondition(condition);
close(false);
clearLargeMessage();
}
protected abstract void actualDelivery(AMQPMessage message, Delivery delivery, Receiver receiver, Transaction tx);
// TODO: how to implement flow here?
public abstract void flow();
}

View File

@ -24,7 +24,7 @@ public class ProtonInitializable {
return initialized;
}
public void initialise() throws Exception {
public void initialize() throws Exception {
if (!initialized) {
initialized = true;
}

View File

@ -25,37 +25,26 @@ import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.persistence.impl.nullpm.NullStorageManager;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.SecurityAuth;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPLargeMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInternalErrorException;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPNotFoundException;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPSecurityException;
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
import org.apache.activemq.artemis.protocol.amqp.sasl.PlainSASLResult;
import org.apache.activemq.artemis.protocol.amqp.sasl.SASLResult;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Modified;
import org.apache.qpid.proton.amqp.messaging.Outcome;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.messaging.Source;
import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy;
import org.apache.qpid.proton.amqp.transaction.TransactionalState;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.DeliveryState;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode;
import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Receiver;
import org.jboss.logging.Logger;
@ -63,121 +52,14 @@ import org.jboss.logging.Logger;
/**
* This is the equivalent for the ServerProducer
*/
public class ProtonServerReceiverContext extends ProtonInitializable implements ProtonDeliveryHandler {
public class ProtonServerReceiverContext extends ProtonAbstractReceiver {
private static final Logger log = Logger.getLogger(ProtonServerReceiverContext.class);
protected final AMQPConnectionContext connection;
protected final AMQPSessionContext protonSession;
protected final Receiver receiver;
protected SimpleString address;
protected final AMQPSessionCallback sessionSPI;
final RoutingContext routingContext;
/**
* We create this AtomicRunnable with setRan.
* This is because we always reuse the same instance.
* In case the creditRunnable was run, we reset and send it over.
* We set it as ran as the first one should always go through
*/
protected final Runnable creditRunnable;
protected final Runnable spiFlow = this::sessionSPIFlow;
private final boolean useModified;
/** no need to synchronize this as we only update it while in handler
* @see #incrementSettle()
*/
private int pendingSettles = 0;
/**
* This Credit Runnable may be used in Mock tests to simulate the credit semantic here
*/
public static Runnable createCreditRunnable(int refill,
int threshold,
Receiver receiver,
AMQPConnectionContext connection,
ProtonServerReceiverContext context) {
return new FlowControlRunner(refill, threshold, receiver, connection, context);
}
/**
* This Credit Runnable may be used in Mock tests to simulate the credit semantic here
*/
public static Runnable createCreditRunnable(int refill,
int threshold,
Receiver receiver,
AMQPConnectionContext connection) {
return new FlowControlRunner(refill, threshold, receiver, connection, null);
}
/**
* The reason why we use the AtomicRunnable here
* is because PagingManager will call Runnables in case it was blocked.
* however it could call many Runnables
* and this serves as a control to avoid duplicated calls
* */
static class FlowControlRunner implements Runnable {
final int refill;
final int threshold;
final Receiver receiver;
final AMQPConnectionContext connection;
final ProtonServerReceiverContext context;
FlowControlRunner(int refill, int threshold, Receiver receiver, AMQPConnectionContext connection, ProtonServerReceiverContext context) {
this.refill = refill;
this.threshold = threshold;
this.receiver = receiver;
this.connection = connection;
this.context = context;
}
@Override
public void run() {
if (!connection.isHandler()) {
// for the case where the paging manager is resuming flow due to blockage
// this should then move back to the connection thread.
connection.runLater(this);
return;
}
connection.requireInHandler();
int pending = context != null ? context.pendingSettles : 0;
if (isBellowThreshold(receiver.getCredit(), pending, threshold)) {
int topUp = calculatedUpdateRefill(refill, receiver.getCredit(), pending);
if (topUp > 0) {
receiver.flow(topUp);
connection.instantFlush();
}
}
}
}
public static boolean isBellowThreshold(int credit, int pending, int threshold) {
return credit <= threshold - pending;
}
public static int calculatedUpdateRefill(int refill, int credits, int pending) {
return refill - credits - pending;
}
/*
The maximum number of credits we will allocate to clients.
This number is also used by the broker when refresh client credits.
*/
private final int amqpCredits;
// Used by the broker to decide when to refresh clients credit. This is not used when client requests credit.
private final int minCreditRefresh;
private final int minLargeMessageSize;
private RoutingType defRoutingType;
@ -185,44 +67,13 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
AMQPConnectionContext connection,
AMQPSessionContext protonSession,
Receiver receiver) {
this.connection = connection;
this.routingContext = new RoutingContextImpl(null).setDuplicateDetection(connection.getProtocolManager().isAmqpDuplicateDetection());
this.protonSession = protonSession;
this.receiver = receiver;
this.sessionSPI = sessionSPI;
this.amqpCredits = connection.getAmqpCredits();
this.minCreditRefresh = connection.getAmqpLowCredits();
this.creditRunnable = createCreditRunnable(amqpCredits, minCreditRefresh, receiver, connection, this);
useModified = this.connection.getProtocolManager().isUseModifiedForTransientDeliveryErrors();
this.minLargeMessageSize = connection.getProtocolManager().getAmqpMinLargeMessageSize();
super(sessionSPI, connection, protonSession, receiver);
if (sessionSPI != null) {
sessionSPI.addCloseable((boolean failed) -> clearLargeMessage());
}
}
protected void clearLargeMessage() {
connection.runNow(() -> {
if (currentLargeMessage != null) {
try {
currentLargeMessage.deleteFile();
} catch (Throwable error) {
ActiveMQServerLogger.LOGGER.errorDeletingLargeMessageFile(error);
} finally {
currentLargeMessage = null;
}
}
});
}
@Override
public void onFlow(int credits, boolean drain) {
flow();
}
@Override
public void initialise() throws Exception {
super.initialise();
public void initialize() throws Exception {
super.initialize();
org.apache.qpid.proton.amqp.messaging.Target target = (org.apache.qpid.proton.amqp.messaging.Target) receiver.getRemoteTarget();
// Match the settlement mode of the remote instead of relying on the default of MIXED.
@ -271,41 +122,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
}
try {
sessionSPI.check(address, CheckType.SEND, new SecurityAuth() {
@Override
public String getUsername() {
String username = null;
SASLResult saslResult = connection.getSASLResult();
if (saslResult != null) {
username = saslResult.getUser();
}
return username;
}
@Override
public String getPassword() {
String password = null;
SASLResult saslResult = connection.getSASLResult();
if (saslResult != null) {
if (saslResult instanceof PlainSASLResult) {
password = ((PlainSASLResult) saslResult).getPassword();
}
}
return password;
}
@Override
public RemotingConnection getRemotingConnection() {
return connection.connectionCallback.getProtonConnectionDelegate();
}
@Override
public String getSecurityDomain() {
return connection.getProtocolManager().getSecurityDomain();
}
});
sessionSPI.check(address, CheckType.SEND, connection.getSecurityAuth());
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingProducer(e.getMessage());
}
@ -353,101 +170,12 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
return defaultRoutingType;
}
volatile AMQPLargeMessage currentLargeMessage;
/*
* called when Proton receives a message to be delivered via a Delivery.
*
* This may be called more than once per deliver so we have to cache the buffer until we have received it all.
*/
@Override
public void onMessage(Delivery delivery) throws ActiveMQAMQPException {
connection.requireInHandler();
Receiver receiver = ((Receiver) delivery.getLink());
if (receiver.current() != delivery) {
return;
}
protected void actualDelivery(AMQPMessage message, Delivery delivery, Receiver receiver, Transaction tx) {
try {
if (delivery.isAborted()) {
clearLargeMessage();
// Aborting implicitly remotely settles, so advance
// receiver to the next delivery and settle locally.
receiver.advance();
delivery.settle();
// Replenish the credit if not doing a drain
if (!receiver.getDrain()) {
receiver.flow(1);
}
return;
} else if (delivery.isPartial()) {
if (sessionSPI.getStorageManager() instanceof NullStorageManager) {
// if we are dealing with the NullStorageManager we should just make it a regular message anyways
return;
}
if (currentLargeMessage == null) {
// minLargeMessageSize < 0 means no large message treatment, make it disabled
if (minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
initializeCurrentLargeMessage(delivery, receiver);
}
} else {
currentLargeMessage.addBytes(receiver.recv());
}
return;
if (sessionSPI != null) {
sessionSPI.serverSend(this, tx, receiver, delivery, address, routingContext, message);
}
AMQPMessage message;
// this is treating the case where the frameSize > minLargeMessage and the message is still large enough
if (!(sessionSPI.getStorageManager() instanceof NullStorageManager) && currentLargeMessage == null && minLargeMessageSize > 0 && delivery.available() >= minLargeMessageSize) {
initializeCurrentLargeMessage(delivery, receiver);
}
if (currentLargeMessage != null) {
currentLargeMessage.addBytes(receiver.recv());
receiver.advance();
currentLargeMessage.finishParse();
message = currentLargeMessage;
currentLargeMessage = null;
} else {
ReadableBuffer data = receiver.recv();
receiver.advance();
message = sessionSPI.createStandardMessage(delivery, data);
}
Transaction tx = null;
if (delivery.getRemoteState() instanceof TransactionalState) {
TransactionalState txState = (TransactionalState) delivery.getRemoteState();
tx = this.sessionSPI.getTransaction(txState.getTxnId(), false);
}
actualDelivery(message, delivery, receiver, tx);
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
}
private void initializeCurrentLargeMessage(Delivery delivery, Receiver receiver) throws Exception {
long id = sessionSPI.getStorageManager().generateID();
currentLargeMessage = new AMQPLargeMessage(id, delivery.getMessageFormat(), null, sessionSPI.getCoreMessageObjectPools(), sessionSPI.getStorageManager());
ReadableBuffer dataBuffer = receiver.recv();
currentLargeMessage.parseHeader(dataBuffer);
sessionSPI.getStorageManager().largeMessageCreated(id, currentLargeMessage);
currentLargeMessage.addBytes(dataBuffer);
}
private void actualDelivery(AMQPMessage message, Delivery delivery, Receiver receiver, Transaction tx) {
try {
sessionSPI.serverSend(this, tx, receiver, delivery, address, routingContext, message);
} catch (Exception e) {
log.warn(e.getMessage(), e);
@ -513,7 +241,7 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
@Override
public void close(boolean remoteLinkClose) throws ActiveMQAMQPException {
protonSession.removeReceiver(receiver);
super.close(remoteLinkClose);
org.apache.qpid.proton.amqp.messaging.Target target = (org.apache.qpid.proton.amqp.messaging.Target) receiver.getRemoteTarget();
if (target != null && target.getDynamic() && (target.getExpiryPolicy() == TerminusExpiryPolicy.LINK_DETACH || target.getExpiryPolicy() == TerminusExpiryPolicy.SESSION_END)) {
try {
@ -525,32 +253,12 @@ public class ProtonServerReceiverContext extends ProtonInitializable implements
}
@Override
public void close(ErrorCondition condition) throws ActiveMQAMQPException {
receiver.setCondition(condition);
close(false);
clearLargeMessage();
}
public int incrementSettle() {
assert pendingSettles >= 0;
connection.requireInHandler();
return pendingSettles++;
}
public void settle(Delivery settlement) {
connection.requireInHandler();
pendingSettles--;
assert pendingSettles >= 0;
settlement.settle();
flow();
}
public void flow() {
// this will mark flow control to happen once after the event loop
connection.afterFlush(spiFlow);
}
private void sessionSPIFlow() {
protected void sessionSPIFlow() {
connection.requireInHandler();
// Use the SessionSPI to allocate producer credits, or default, always allocate credit.
if (sessionSPI != null) {

View File

@ -24,7 +24,9 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.activemq.artemis.api.core.ActiveMQQueueMaxConsumerLimitReached;
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.RoutingType;
@ -41,6 +43,7 @@ import org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPLargeMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessage;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPMessageBrokerAccessor;
import org.apache.activemq.artemis.protocol.amqp.broker.AMQPSessionCallback;
import org.apache.activemq.artemis.protocol.amqp.broker.ActiveMQProtonRemotingConnection;
import org.apache.activemq.artemis.protocol.amqp.converter.CoreAmqpConverter;
@ -52,6 +55,7 @@ import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPResource
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
import org.apache.activemq.artemis.protocol.amqp.proton.transaction.ProtonTransactionImpl;
import org.apache.activemq.artemis.protocol.amqp.util.NettyReadable;
import org.apache.activemq.artemis.protocol.amqp.util.TLSEncode;
import org.apache.activemq.artemis.reader.MessageUtil;
import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.impl.SelectorParser;
@ -60,6 +64,8 @@ import org.apache.activemq.artemis.utils.CompositeAddress;
import org.apache.qpid.proton.amqp.DescribedType;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations;
import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.Modified;
import org.apache.qpid.proton.amqp.messaging.Outcome;
import org.apache.qpid.proton.amqp.messaging.Source;
@ -73,6 +79,7 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.apache.qpid.proton.codec.ReadableBuffer;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Link;
@ -92,6 +99,8 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
private static final Symbol SHARED = Symbol.valueOf("shared");
private static final Symbol GLOBAL = Symbol.valueOf("global");
SenderController controller;
private final ConnectionFlushIOCallback connectionFlusher = new ConnectionFlushIOCallback();
private Consumer brokerConsumer;
@ -101,15 +110,9 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
protected final AMQPConnectionContext connection;
protected boolean closed = false;
protected final AMQPSessionCallback sessionSPI;
private boolean multicast;
//todo get this from somewhere
private RoutingType defaultRoutingType = RoutingType.ANYCAST;
private RoutingType routingTypeToUse = defaultRoutingType;
private boolean shared = false;
private boolean global = false;
private boolean isVolatile = false;
private boolean preSettle;
private SimpleString tempQueueName;
private final AtomicBoolean draining = new AtomicBoolean(false);
// once a large message is accepted, we shouldn't accept any further messages
@ -133,7 +136,16 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
Sender sender,
AMQPSessionContext protonSession,
AMQPSessionCallback server) {
this(connection, sender, protonSession, server, null);
}
public ProtonServerSenderContext(AMQPConnectionContext connection,
Sender sender,
AMQPSessionContext protonSession,
AMQPSessionCallback server,
SenderController senderController) {
super();
this.controller = senderController;
this.connection = connection;
this.sender = sender;
this.protonSession = protonSession;
@ -225,318 +237,34 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
}
}
/**
* create the actual underlying ActiveMQ Artemis Server Consumer
*/
@SuppressWarnings("unchecked")
@Override
public void initialise() throws Exception {
super.initialise();
public void initialize() throws Exception {
super.initialize();
Source source = (Source) sender.getRemoteSource();
SimpleString queue = null;
String selector = null;
final Map<Symbol, Object> supportedFilters = new HashMap<>();
// Match the settlement mode of the remote instead of relying on the default of MIXED.
sender.setSenderSettleMode(sender.getRemoteSenderSettleMode());
// We don't currently support SECOND so enforce that the answer is anlways FIRST
sender.setReceiverSettleMode(ReceiverSettleMode.FIRST);
if (source != null) {
// We look for message selectors on every receiver, while in other cases we might only
// consume the filter depending on the subscription type.
Map.Entry<Symbol, DescribedType> filter = AmqpSupport.findFilter(source.getFilter(), AmqpSupport.JMS_SELECTOR_FILTER_IDS);
if (filter != null) {
selector = filter.getValue().getDescribed().toString();
// Validate the Selector.
try {
SelectorParser.parse(selector);
} catch (FilterException e) {
throw new ActiveMQAMQPException(AmqpError.INVALID_FIELD, "Invalid filter", ActiveMQExceptionType.INVALID_FILTER_EXPRESSION);
}
supportedFilters.put(filter.getKey(), filter.getValue());
}
if (controller == null) {
controller = new DefaultController(sessionSPI);
}
if (source == null) {
// Attempt to recover a previous subscription happens when a link reattach happens on a
// subscription queue
String clientId = getClientId();
String pubId = sender.getName();
global = hasRemoteDesiredCapability(sender, GLOBAL);
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, true, global, false);
QueueQueryResult result = sessionSPI.queueQuery(queue, RoutingType.MULTICAST, false);
multicast = true;
routingTypeToUse = RoutingType.MULTICAST;
// Once confirmed that the address exists we need to return a Source that reflects
// the lifetime policy and capabilities of the new subscription.
if (result.isExists()) {
source = new org.apache.qpid.proton.amqp.messaging.Source();
source.setAddress(queue.toString());
source.setDurable(TerminusDurability.UNSETTLED_STATE);
source.setExpiryPolicy(TerminusExpiryPolicy.NEVER);
source.setDistributionMode(COPY);
source.setCapabilities(TOPIC);
SimpleString filterString = result.getFilterString();
if (filterString != null) {
selector = filterString.toString();
boolean noLocal = false;
String remoteContainerId = sender.getSession().getConnection().getRemoteContainer();
String noLocalFilter = MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString() + "<>'" + remoteContainerId + "'";
if (selector.endsWith(noLocalFilter)) {
if (selector.length() > noLocalFilter.length()) {
noLocalFilter = " AND " + noLocalFilter;
selector = selector.substring(0, selector.length() - noLocalFilter.length());
} else {
selector = null;
}
noLocal = true;
}
if (noLocal) {
supportedFilters.put(AmqpSupport.NO_LOCAL_NAME, AmqpNoLocalFilter.NO_LOCAL);
}
if (selector != null && !selector.trim().isEmpty()) {
supportedFilters.put(AmqpSupport.JMS_SELECTOR_NAME, new AmqpJmsSelectorFilter(selector));
}
}
sender.setSource(source);
} else {
throw new ActiveMQAMQPNotFoundException("Unknown subscription link: " + sender.getName());
}
} else if (source.getDynamic()) {
// if dynamic we have to create the node (queue) and set the address on the target, the
// node is temporary and will be deleted on closing of the session
queue = SimpleString.toSimpleString(java.util.UUID.randomUUID().toString());
tempQueueName = queue;
try {
sessionSPI.createTemporaryQueue(queue, RoutingType.ANYCAST);
// protonSession.getServerSession().createQueue(queue, queue, null, true, false);
} catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
source.setAddress(queue.toString());
} else {
SimpleString addressToUse;
SimpleString queueNameToUse = null;
shared = hasCapabilities(SHARED, source);
global = hasCapabilities(GLOBAL, source);
//find out if we have an address made up of the address and queue name, if yes then set queue name
if (CompositeAddress.isFullyQualified(source.getAddress())) {
addressToUse = SimpleString.toSimpleString(CompositeAddress.extractAddressName(source.getAddress()));
queueNameToUse = SimpleString.toSimpleString(CompositeAddress.extractQueueName(source.getAddress()));
} else {
addressToUse = SimpleString.toSimpleString(source.getAddress());
}
//check to see if the client has defined how we act
boolean clientDefined = hasCapabilities(TOPIC, source) || hasCapabilities(QUEUE, source);
if (clientDefined) {
multicast = hasCapabilities(TOPIC, source);
AddressQueryResult addressQueryResult = null;
try {
addressQueryResult = sessionSPI.addressQuery(addressToUse, multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST, true);
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingConsumer(e.getMessage());
} catch (ActiveMQAMQPException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
if (!addressQueryResult.isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
Set<RoutingType> routingTypes = addressQueryResult.getRoutingTypes();
//if the client defines 1 routing type and the broker another then throw an exception
if (multicast && !routingTypes.contains(RoutingType.MULTICAST)) {
throw new ActiveMQAMQPIllegalStateException("Address " + addressToUse + " is not configured for topic support");
} else if (!multicast && !routingTypes.contains(RoutingType.ANYCAST)) {
//if client specifies fully qualified name that's allowed, don't throw exception.
if (queueNameToUse == null) {
throw new ActiveMQAMQPIllegalStateException("Address " + addressToUse + " is not configured for queue support");
}
}
} else {
// if not we look up the address
AddressQueryResult addressQueryResult = null;
try {
addressQueryResult = sessionSPI.addressQuery(addressToUse, defaultRoutingType, true);
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingConsumer(e.getMessage());
} catch (ActiveMQAMQPException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
if (!addressQueryResult.isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
Set<RoutingType> routingTypes = addressQueryResult.getRoutingTypes();
if (routingTypes.contains(RoutingType.MULTICAST) && routingTypes.size() == 1) {
multicast = true;
} else {
//todo add some checks if both routing types are supported
multicast = false;
}
}
routingTypeToUse = multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST;
// if not dynamic then we use the target's address as the address to forward the
// messages to, however there has to be a queue bound to it so we need to check this.
if (multicast) {
Map.Entry<Symbol, DescribedType> filter = AmqpSupport.findFilter(source.getFilter(), AmqpSupport.NO_LOCAL_FILTER_IDS);
if (filter != null) {
String remoteContainerId = sender.getSession().getConnection().getRemoteContainer();
String noLocalFilter = MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString() + "<>'" + remoteContainerId + "'";
if (selector != null) {
selector += " AND " + noLocalFilter;
} else {
selector = noLocalFilter;
}
supportedFilters.put(filter.getKey(), filter.getValue());
}
queue = getMatchingQueue(queueNameToUse, addressToUse, RoutingType.MULTICAST);
SimpleString simpleStringSelector = SimpleString.toSimpleString(selector);
//if the address specifies a broker configured queue then we always use this, treat it as a queue
if (queue != null) {
multicast = false;
} else if (TerminusDurability.UNSETTLED_STATE.equals(source.getDurable()) || TerminusDurability.CONFIGURATION.equals(source.getDurable())) {
// if we are a subscription and durable create a durable queue using the container
// id and link name
String clientId = getClientId();
String pubId = sender.getName();
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, shared, global, false);
QueueQueryResult result = sessionSPI.queueQuery(queue, routingTypeToUse, false);
if (result.isExists()) {
// If a client reattaches to a durable subscription with a different no-local
// filter value, selector or address then we must recreate the queue (JMS semantics).
if (!Objects.equals(result.getFilterString(), simpleStringSelector) || (sender.getSource() != null && !sender.getSource().getAddress().equals(result.getAddress().toString()))) {
if (result.getConsumerCount() == 0) {
sessionSPI.deleteQueue(queue);
sessionSPI.createUnsharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
} else {
throw new ActiveMQAMQPIllegalStateException("Unable to recreate subscription, consumers already exist");
}
}
} else {
if (shared) {
sessionSPI.createSharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
} else {
sessionSPI.createUnsharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
}
}
} else {
// otherwise we are a volatile subscription
isVolatile = true;
if (shared && sender.getName() != null) {
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), getClientId(), sender.getName(), shared, global, isVolatile);
QueueQueryResult result = sessionSPI.queueQuery(queue, routingTypeToUse, false);
if (!(result.isExists() && Objects.equals(result.getAddress(), addressToUse) && Objects.equals(result.getFilterString(), simpleStringSelector))) {
sessionSPI.createSharedVolatileQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
}
} else {
queue = SimpleString.toSimpleString(java.util.UUID.randomUUID().toString());
tempQueueName = queue;
try {
sessionSPI.createTemporaryQueue(addressToUse, queue, RoutingType.MULTICAST, simpleStringSelector);
} catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
}
}
} else {
if (queueNameToUse != null) {
//a queue consumer can receive from a multicast queue if it uses a fully qualified name
//setting routingType to null means do not check the routingType against the Queue's routing type.
routingTypeToUse = null;
SimpleString matchingAnycastQueue = getMatchingQueue(queueNameToUse, addressToUse, null);
if (matchingAnycastQueue != null) {
queue = matchingAnycastQueue;
} else {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
} else {
SimpleString matchingAnycastQueue = sessionSPI.getMatchingQueue(addressToUse, RoutingType.ANYCAST);
if (matchingAnycastQueue != null) {
queue = matchingAnycastQueue;
} else {
queue = addressToUse;
}
}
}
if (queue == null) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressNotSet();
}
try {
if (!sessionSPI.queueQuery(queue, routingTypeToUse, !multicast).isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
} catch (ActiveMQAMQPNotFoundException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
}
// Detect if sender is in pre-settle mode.
preSettle = sender.getRemoteSenderSettleMode() == SenderSettleMode.SETTLED;
// We need to update the source with any filters we support otherwise the client
// is free to consider the attach as having failed if we don't send back what we
// do support or if we send something we don't support the client won't know we
// have not honored what it asked for.
source.setFilter(supportedFilters.isEmpty() ? null : supportedFilters);
boolean browseOnly = !multicast && source.getDistributionMode() != null && source.getDistributionMode().equals(COPY);
try {
brokerConsumer = (Consumer) sessionSPI.createSender(this, queue, multicast ? null : selector, browseOnly);
brokerConsumer = controller.init(this);
onflowControlReady = brokerConsumer::promptDelivery;
} catch (ActiveMQAMQPResourceLimitExceededException e1) {
throw new ActiveMQAMQPResourceLimitExceededException(e1.getMessage());
throw e1;
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingConsumer(e.getMessage());
} catch (ActiveMQQueueMaxConsumerLimitReached e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingConsumer(e.getMessage());
} catch (ActiveMQException e) {
throw e;
} catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingConsumer(e.getMessage());
}
}
private SimpleString getMatchingQueue(SimpleString queueName, SimpleString address, RoutingType routingType) throws Exception {
if (queueName != null) {
QueueQueryResult result = sessionSPI.queueQuery(CompositeAddress.toFullyQualified(address, queueName), routingType, true);
if (!result.isExists()) {
throw new ActiveMQAMQPNotFoundException("Queue: '" + queueName + "' does not exist");
} else {
if (!result.getAddress().equals(address)) {
throw new ActiveMQAMQPNotFoundException("Queue: '" + queueName + "' does not exist for address '" + address + "'");
}
return sessionSPI.getMatchingQueue(address, queueName, routingType);
}
}
return null;
}
protected String getClientId() {
return connection.getRemoteContainer();
}
@ -591,36 +319,8 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
// if this is a link close rather than a connection close or detach, we need to delete
// any durable resources for say pub subs
if (remoteLinkClose) {
Source source = (Source) sender.getSource();
if (source != null && source.getAddress() != null && multicast) {
SimpleString queueName = SimpleString.toSimpleString(source.getAddress());
QueueQueryResult result = sessionSPI.queueQuery(queueName, routingTypeToUse, false);
if (result.isExists() && source.getDynamic()) {
sessionSPI.deleteQueue(queueName);
} else {
if (source.getDurable() == TerminusDurability.NONE && tempQueueName != null && (source.getExpiryPolicy() == TerminusExpiryPolicy.LINK_DETACH || source.getExpiryPolicy() == TerminusExpiryPolicy.SESSION_END)) {
sessionSPI.removeTemporaryQueue(tempQueueName);
} else {
String clientId = getClientId();
String pubId = sender.getName();
if (pubId.contains("|")) {
pubId = pubId.split("\\|")[0];
}
SimpleString queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, shared, global, isVolatile);
result = sessionSPI.queueQuery(queue, multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST, false);
//only delete if it isn't volatile and has no consumers
if (result.isExists() && !isVolatile && result.getConsumerCount() == 0) {
sessionSPI.deleteQueue(queue);
}
}
}
} else if (source != null && source.getDynamic() && (source.getExpiryPolicy() == TerminusExpiryPolicy.LINK_DETACH || source.getExpiryPolicy() == TerminusExpiryPolicy.SESSION_END)) {
try {
sessionSPI.removeTemporaryQueue(SimpleString.toSimpleString(source.getAddress()));
} catch (Exception e) {
//ignore on close, its temp anyway and will be removed later
}
}
controller.close();
}
} catch (Exception e) {
log.warn(e.getMessage(), e);
@ -644,14 +344,7 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
// this can happen in the twice ack mode, that is the receiver accepts and settles separately
// acking again would show an exception but would have no negative effect but best to handle anyway.
if (!delivery.isSettled()) {
// we have to individual ack as we can't guarantee we will get the delivery updates
// (including acks) in order from dealer, a performance hit but a must
try {
sessionSPI.ack(null, brokerConsumer, message);
} catch (Exception e) {
log.warn(e.toString(), e);
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorAcknowledgingMessage(message.toString(), e.getMessage());
}
doAck(message);
delivery.settle();
}
@ -668,6 +361,17 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
}
}
protected void doAck(Message message) throws ActiveMQAMQPIllegalStateException {
// we have to individual ack as we can't guarantee we will get the delivery updates
// (including acks) in order from dealer, a performance hit but a must
try {
sessionSPI.ack(null, brokerConsumer, message);
} catch (Exception e) {
log.warn(e.toString(), e);
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorAcknowledgingMessage(message.toString(), e.getMessage());
}
}
private boolean handleExtendedDeliveryOutcomes(Message message, Delivery delivery, DeliveryState remoteState) throws ActiveMQAMQPException {
boolean settleImmediate = true;
boolean handled = true;
@ -857,10 +561,19 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
// This is discounting some bytes due to Transfer payload
int frameSize = protonSession.session.getConnection().getTransport().getOutboundFrameSizeLimit() - 50 - (delivery.getTag() != null ? delivery.getTag().length : 0);
DeliveryAnnotations deliveryAnnotationsToEncode;
message.checkReference(reference);
if (reference.getProtocolData() != null && reference.getProtocolData() instanceof DeliveryAnnotations) {
deliveryAnnotationsToEncode = (DeliveryAnnotations)reference.getProtocolData();
} else {
deliveryAnnotationsToEncode = null;
}
// Let the Message decide how to present the message bytes
LargeBodyReader context = message.getLargeBodyReader();
try {
context.open();
try {
context.position(position);
@ -876,6 +589,12 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
return;
}
buf.clear();
if (position == 0) {
writeHeaderAndAnnotations(context, buf, deliveryAnnotationsToEncode);
}
int size = context.readInto(buf);
sender.send(new ReadableBuffer.ByteBufferReader(buf));
@ -914,6 +633,25 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
brokerConsumer.errorProcessing(e, reference);
}
}
private void writeHeaderAndAnnotations(LargeBodyReader context,
ByteBuffer buf,
DeliveryAnnotations deliveryAnnotationsToEncode) throws ActiveMQException {
TLSEncode.getEncoder().setByteBuffer(WritableBuffer.ByteBufferWrapper.wrap(buf));
try {
Header header = AMQPMessageBrokerAccessor.getCurrentHeader(message);
if (header != null) {
TLSEncode.getEncoder().writeObject(header);
}
if (deliveryAnnotationsToEncode != null) {
TLSEncode.getEncoder().writeObject(deliveryAnnotationsToEncode);
}
context.position(message.getPositionAfterDeliveryAnnotations());
position = message.getPositionAfterDeliveryAnnotations();
} finally {
TLSEncode.getEncoder().setByteBuffer((WritableBuffer)null);
}
}
}
private void finishLargeMessage() {
@ -939,7 +677,7 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
private void deliverStandard(MessageReference messageReference, AMQPMessage message) {
// Let the Message decide how to present the message bytes
ReadableBuffer sendBuffer = message.getSendBuffer(messageReference.getDeliveryCount());
ReadableBuffer sendBuffer = message.getSendBuffer(messageReference.getDeliveryCount(), messageReference);
// we only need a tag if we are going to settle later
byte[] tag = preSettle ? new byte[0] : protonSession.getTag();
@ -1049,4 +787,359 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
public AMQPSessionContext getSessionContext() {
return protonSession;
}
class DefaultController implements SenderController {
private boolean shared = false;
boolean global = false;
boolean multicast;
final AMQPSessionCallback sessionSPI;
SimpleString queue = null;
SimpleString tempQueueName;
String selector;
private final RoutingType defaultRoutingType = RoutingType.ANYCAST;
private RoutingType routingTypeToUse = RoutingType.ANYCAST;
private boolean isVolatile = false;
DefaultController(AMQPSessionCallback sessionSPI) {
this.sessionSPI = sessionSPI;
}
@Override
public Consumer init(ProtonServerSenderContext senderContext) throws Exception {
Source source = (Source) sender.getRemoteSource();
final Map<Symbol, Object> supportedFilters = new HashMap<>();
// Match the settlement mode of the remote instead of relying on the default of MIXED.
sender.setSenderSettleMode(sender.getRemoteSenderSettleMode());
// We don't currently support SECOND so enforce that the answer is anlways FIRST
sender.setReceiverSettleMode(ReceiverSettleMode.FIRST);
if (source != null) {
// We look for message selectors on every receiver, while in other cases we might only
// consume the filter depending on the subscription type.
Map.Entry<Symbol, DescribedType> filter = AmqpSupport.findFilter(source.getFilter(), AmqpSupport.JMS_SELECTOR_FILTER_IDS);
if (filter != null) {
selector = filter.getValue().getDescribed().toString();
// Validate the Selector.
try {
SelectorParser.parse(selector);
} catch (FilterException e) {
throw new ActiveMQAMQPException(AmqpError.INVALID_FIELD, "Invalid filter", ActiveMQExceptionType.INVALID_FILTER_EXPRESSION);
}
supportedFilters.put(filter.getKey(), filter.getValue());
}
}
if (source == null) {
// Attempt to recover a previous subscription happens when a link reattach happens on a
// subscription queue
String clientId = getClientId();
String pubId = sender.getName();
global = hasRemoteDesiredCapability(sender, GLOBAL);
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, true, global, false);
QueueQueryResult result = sessionSPI.queueQuery(queue, RoutingType.MULTICAST, false);
multicast = true;
routingTypeToUse = RoutingType.MULTICAST;
// Once confirmed that the address exists we need to return a Source that reflects
// the lifetime policy and capabilities of the new subscription.
if (result.isExists()) {
source = new org.apache.qpid.proton.amqp.messaging.Source();
source.setAddress(queue.toString());
source.setDurable(TerminusDurability.UNSETTLED_STATE);
source.setExpiryPolicy(TerminusExpiryPolicy.NEVER);
source.setDistributionMode(COPY);
source.setCapabilities(TOPIC);
SimpleString filterString = result.getFilterString();
if (filterString != null) {
selector = filterString.toString();
boolean noLocal = false;
String remoteContainerId = sender.getSession().getConnection().getRemoteContainer();
String noLocalFilter = MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString() + "<>'" + remoteContainerId + "'";
if (selector.endsWith(noLocalFilter)) {
if (selector.length() > noLocalFilter.length()) {
noLocalFilter = " AND " + noLocalFilter;
selector = selector.substring(0, selector.length() - noLocalFilter.length());
} else {
selector = null;
}
noLocal = true;
}
if (noLocal) {
supportedFilters.put(AmqpSupport.NO_LOCAL_NAME, AmqpNoLocalFilter.NO_LOCAL);
}
if (selector != null && !selector.trim().isEmpty()) {
supportedFilters.put(AmqpSupport.JMS_SELECTOR_NAME, new AmqpJmsSelectorFilter(selector));
}
}
sender.setSource(source);
} else {
throw new ActiveMQAMQPNotFoundException("Unknown subscription link: " + sender.getName());
}
} else if (source.getDynamic()) {
// if dynamic we have to create the node (queue) and set the address on the target, the
// node is temporary and will be deleted on closing of the session
queue = SimpleString.toSimpleString(java.util.UUID.randomUUID().toString());
tempQueueName = queue;
try {
sessionSPI.createTemporaryQueue(queue, RoutingType.ANYCAST);
// protonSession.getServerSession().createQueue(queue, queue, null, true, false);
} catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
source.setAddress(queue.toString());
} else {
SimpleString addressToUse;
SimpleString queueNameToUse = null;
shared = hasCapabilities(SHARED, source);
global = hasCapabilities(GLOBAL, source);
//find out if we have an address made up of the address and queue name, if yes then set queue name
if (CompositeAddress.isFullyQualified(source.getAddress())) {
addressToUse = SimpleString.toSimpleString(CompositeAddress.extractAddressName(source.getAddress()));
queueNameToUse = SimpleString.toSimpleString(CompositeAddress.extractQueueName(source.getAddress()));
} else {
addressToUse = SimpleString.toSimpleString(source.getAddress());
}
//check to see if the client has defined how we act
boolean clientDefined = hasCapabilities(TOPIC, source) || hasCapabilities(QUEUE, source);
if (clientDefined) {
multicast = hasCapabilities(TOPIC, source);
AddressQueryResult addressQueryResult = null;
try {
addressQueryResult = sessionSPI.addressQuery(addressToUse, multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST, true);
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingConsumer(e.getMessage());
} catch (ActiveMQAMQPException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
if (!addressQueryResult.isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
Set<RoutingType> routingTypes = addressQueryResult.getRoutingTypes();
//if the client defines 1 routing type and the broker another then throw an exception
if (multicast && !routingTypes.contains(RoutingType.MULTICAST)) {
throw new ActiveMQAMQPIllegalStateException("Address " + addressToUse + " is not configured for topic support");
} else if (!multicast && !routingTypes.contains(RoutingType.ANYCAST)) {
//if client specifies fully qualified name that's allowed, don't throw exception.
if (queueNameToUse == null) {
throw new ActiveMQAMQPIllegalStateException("Address " + addressToUse + " is not configured for queue support");
}
}
} else {
// if not we look up the address
AddressQueryResult addressQueryResult = null;
try {
addressQueryResult = sessionSPI.addressQuery(addressToUse, defaultRoutingType, true);
} catch (ActiveMQSecurityException e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.securityErrorCreatingConsumer(e.getMessage());
} catch (ActiveMQAMQPException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
if (!addressQueryResult.isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
Set<RoutingType> routingTypes = addressQueryResult.getRoutingTypes();
if (routingTypes.contains(RoutingType.MULTICAST) && routingTypes.size() == 1) {
multicast = true;
} else {
//todo add some checks if both routing types are supported
multicast = false;
}
}
routingTypeToUse = multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST;
// if not dynamic then we use the target's address as the address to forward the
// messages to, however there has to be a queue bound to it so we need to check this.
if (multicast) {
Map.Entry<Symbol, DescribedType> filter = AmqpSupport.findFilter(source.getFilter(), AmqpSupport.NO_LOCAL_FILTER_IDS);
if (filter != null) {
String remoteContainerId = sender.getSession().getConnection().getRemoteContainer();
String noLocalFilter = MessageUtil.CONNECTION_ID_PROPERTY_NAME.toString() + "<>'" + remoteContainerId + "'";
if (selector != null) {
selector += " AND " + noLocalFilter;
} else {
selector = noLocalFilter;
}
supportedFilters.put(filter.getKey(), filter.getValue());
}
queue = getMatchingQueue(queueNameToUse, addressToUse, RoutingType.MULTICAST);
SimpleString simpleStringSelector = SimpleString.toSimpleString(selector);
//if the address specifies a broker configured queue then we always use this, treat it as a queue
if (queue != null) {
multicast = false;
} else if (TerminusDurability.UNSETTLED_STATE.equals(source.getDurable()) || TerminusDurability.CONFIGURATION.equals(source.getDurable())) {
// if we are a subscription and durable create a durable queue using the container
// id and link name
String clientId = getClientId();
String pubId = sender.getName();
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, shared, global, false);
QueueQueryResult result = sessionSPI.queueQuery(queue, routingTypeToUse, false);
if (result.isExists()) {
// If a client reattaches to a durable subscription with a different no-local
// filter value, selector or address then we must recreate the queue (JMS semantics).
if (!Objects.equals(result.getFilterString(), simpleStringSelector) || (sender.getSource() != null && !sender.getSource().getAddress().equals(result.getAddress().toString()))) {
if (result.getConsumerCount() == 0) {
sessionSPI.deleteQueue(queue);
sessionSPI.createUnsharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
} else {
throw new ActiveMQAMQPIllegalStateException("Unable to recreate subscription, consumers already exist");
}
}
} else {
if (shared) {
sessionSPI.createSharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
} else {
sessionSPI.createUnsharedDurableQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
}
}
} else {
// otherwise we are a volatile subscription
isVolatile = true;
if (shared && sender.getName() != null) {
queue = createQueueName(connection.isUseCoreSubscriptionNaming(), getClientId(), sender.getName(), shared, global, isVolatile);
QueueQueryResult result = sessionSPI.queueQuery(queue, routingTypeToUse, false);
if (!(result.isExists() && Objects.equals(result.getAddress(), addressToUse) && Objects.equals(result.getFilterString(), simpleStringSelector))) {
sessionSPI.createSharedVolatileQueue(addressToUse, RoutingType.MULTICAST, queue, simpleStringSelector);
}
} else {
queue = SimpleString.toSimpleString(java.util.UUID.randomUUID().toString());
tempQueueName = queue;
try {
sessionSPI.createTemporaryQueue(addressToUse, queue, RoutingType.MULTICAST, simpleStringSelector);
} catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
}
}
} else {
if (queueNameToUse != null) {
//a queue consumer can receive from a multicast queue if it uses a fully qualified name
//setting routingType to null means do not check the routingType against the Queue's routing type.
routingTypeToUse = null;
SimpleString matchingAnycastQueue = getMatchingQueue(queueNameToUse, addressToUse, null);
if (matchingAnycastQueue != null) {
queue = matchingAnycastQueue;
} else {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
} else {
SimpleString matchingAnycastQueue = sessionSPI.getMatchingQueue(addressToUse, RoutingType.ANYCAST);
if (matchingAnycastQueue != null) {
queue = matchingAnycastQueue;
} else {
queue = addressToUse;
}
}
}
if (queue == null) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressNotSet();
}
try {
if (!sessionSPI.queueQuery(queue, routingTypeToUse, !multicast).isExists()) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
} catch (ActiveMQAMQPNotFoundException e) {
throw e;
} catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
}
// Detect if sender is in pre-settle mode.
preSettle = sender.getRemoteSenderSettleMode() == SenderSettleMode.SETTLED;
// We need to update the source with any filters we support otherwise the client
// is free to consider the attach as having failed if we don't send back what we
// do support or if we send something we don't support the client won't know we
// have not honored what it asked for.
source.setFilter(supportedFilters.isEmpty() ? null : supportedFilters);
boolean browseOnly = !multicast && source.getDistributionMode() != null && source.getDistributionMode().equals(COPY);
return (Consumer) sessionSPI.createSender(senderContext, queue, multicast ? null : selector, browseOnly);
}
private SimpleString getMatchingQueue(SimpleString queueName, SimpleString address, RoutingType routingType) throws Exception {
if (queueName != null) {
QueueQueryResult result = sessionSPI.queueQuery(CompositeAddress.toFullyQualified(address, queueName), routingType, true);
if (!result.isExists()) {
throw new ActiveMQAMQPNotFoundException("Queue: '" + queueName + "' does not exist");
} else {
if (!result.getAddress().equals(address)) {
throw new ActiveMQAMQPNotFoundException("Queue: '" + queueName + "' does not exist for address '" + address + "'");
}
return sessionSPI.getMatchingQueue(address, queueName, routingType);
}
}
return null;
}
@Override
public void close() throws Exception {
Source source = (Source) sender.getSource();
if (source != null && source.getAddress() != null && multicast) {
SimpleString queueName = SimpleString.toSimpleString(source.getAddress());
QueueQueryResult result = sessionSPI.queueQuery(queueName, routingTypeToUse, false);
if (result.isExists() && source.getDynamic()) {
sessionSPI.deleteQueue(queueName);
} else {
if (source.getDurable() == TerminusDurability.NONE && tempQueueName != null && (source.getExpiryPolicy() == TerminusExpiryPolicy.LINK_DETACH || source.getExpiryPolicy() == TerminusExpiryPolicy.SESSION_END)) {
sessionSPI.removeTemporaryQueue(tempQueueName);
} else {
String clientId = getClientId();
String pubId = sender.getName();
if (pubId.contains("|")) {
pubId = pubId.split("\\|")[0];
}
SimpleString queue = createQueueName(connection.isUseCoreSubscriptionNaming(), clientId, pubId, shared, global, isVolatile);
result = sessionSPI.queueQuery(queue, multicast ? RoutingType.MULTICAST : RoutingType.ANYCAST, false);
//only delete if it isn't volatile and has no consumers
if (result.isExists() && !isVolatile && result.getConsumerCount() == 0) {
sessionSPI.deleteQueue(queue);
}
}
}
} else if (source != null && source.getDynamic() && (source.getExpiryPolicy() == TerminusExpiryPolicy.LINK_DETACH || source.getExpiryPolicy() == TerminusExpiryPolicy.SESSION_END)) {
try {
sessionSPI.removeTemporaryQueue(SimpleString.toSimpleString(source.getAddress()));
} catch (Exception e) {
//ignore on close, its temp anyway and will be removed later
}
}
}
}
}

View File

@ -0,0 +1,24 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.protocol.amqp.proton;
import org.apache.activemq.artemis.core.server.Consumer;
public interface SenderController {
Consumer init(ProtonServerSenderContext senderContext) throws Exception;
void close() throws Exception;
}

View File

@ -1581,48 +1581,6 @@ public class AMQPMessageTest {
private static final UnsignedLong AMQPVALUE_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000077L);
private static final UnsignedLong APPLICATION_PROPERTIES_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000074L);
private static final UnsignedLong DELIVERY_ANNOTATIONS_DESCRIPTOR = UnsignedLong.valueOf(0x0000000000000071L);
@Test
public void testPartialDecodeIgnoresDeliveryAnnotationsByDefault() {
Header header = new Header();
header.setDurable(true);
header.setPriority(UnsignedByte.valueOf((byte) 6));
ByteBuf encodedBytes = Unpooled.buffer(1024);
NettyWritable writable = new NettyWritable(encodedBytes);
EncoderImpl encoder = TLSEncode.getEncoder();
encoder.setByteBuffer(writable);
encoder.writeObject(header);
// Signal body of AmqpValue but write corrupt underlying type info
encodedBytes.writeByte(EncodingCodes.DESCRIBED_TYPE_INDICATOR);
encodedBytes.writeByte(EncodingCodes.SMALLULONG);
encodedBytes.writeByte(DELIVERY_ANNOTATIONS_DESCRIPTOR.byteValue());
encodedBytes.writeByte(EncodingCodes.MAP8);
encodedBytes.writeByte(2); // Size
encodedBytes.writeByte(2); // Elements
// Use bad encoding code on underlying type of map key which will fail the decode if run
encodedBytes.writeByte(255);
ReadableBuffer readable = new NettyReadable(encodedBytes);
AMQPStandardMessage message = null;
try {
message = new AMQPStandardMessage(0, readable, null, null);
} catch (Exception decodeError) {
fail("Should not have encountered an exception on partial decode: " + decodeError.getMessage());
}
try {
// This should perform the lazy decode of the DeliveryAnnotations portion of the message
message.getDeliveryAnnotations();
fail("Should have thrown an error when attempting to decode the ApplicationProperties which are malformed.");
} catch (Exception ex) {
// Expected decode to fail when building full message.
}
}
@Test
public void testPartialDecodeIgnoresApplicationPropertiesByDefault() {
@ -1795,7 +1753,7 @@ public class AMQPMessageTest {
public void testGetSendBuffer() {
AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
ReadableBuffer buffer = message.getSendBuffer(1);
ReadableBuffer buffer = message.getSendBuffer(1, null);
assertNotNull(buffer);
assertTrue(buffer.hasArray());
@ -1810,7 +1768,7 @@ public class AMQPMessageTest {
public void testGetSendBufferAddsDeliveryCountOnlyToSendMessage() {
AMQPStandardMessage message = new AMQPStandardMessage(0, encodedProtonMessage, null, null);
ReadableBuffer buffer = message.getSendBuffer(7);
ReadableBuffer buffer = message.getSendBuffer(7, null);
assertNotNull(buffer);
message.reencode(); // Ensures Header is current if accidentally updated
@ -1829,7 +1787,7 @@ public class AMQPMessageTest {
MessageImpl protonMessage = (MessageImpl) Proton.message();
AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
ReadableBuffer buffer = message.getSendBuffer(7);
ReadableBuffer buffer = message.getSendBuffer(7, null);
assertNotNull(buffer);
message.reencode(); // Ensures Header is current if accidentally updated
@ -1851,7 +1809,7 @@ public class AMQPMessageTest {
protonMessage.setDeliveryAnnotations(deliveryAnnotations);
AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
ReadableBuffer buffer = message.getSendBuffer(1);
ReadableBuffer buffer = message.getSendBuffer(1, null);
assertNotNull(buffer);
AMQPStandardMessage copy = new AMQPStandardMessage(0, buffer, null, null);
@ -1869,7 +1827,7 @@ public class AMQPMessageTest {
protonMessage.setDeliveryAnnotations(deliveryAnnotations);
AMQPStandardMessage message = new AMQPStandardMessage(0, encodeMessage(protonMessage), null, null);
ReadableBuffer buffer = message.getSendBuffer(7);
ReadableBuffer buffer = message.getSendBuffer(7, null);
assertNotNull(buffer);
message.reencode(); // Ensures Header is current if accidentally updated
@ -2036,14 +1994,14 @@ public class AMQPMessageTest {
AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
ReadableBuffer sendBuffer = decoded.getSendBuffer(1, null);
assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
AMQPStandardMessage msgFromSendBuffer = new AMQPStandardMessage(0, sendBuffer, null, null);
assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
assertNull(msgFromSendBuffer.getDeliveryAnnotations());
// again with higher deliveryCount
ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5, null);
assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
AMQPStandardMessage msgFromSendBuffer2 = new AMQPStandardMessage(0, sendBuffer2, null, null);
assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
@ -2069,7 +2027,7 @@ public class AMQPMessageTest {
newDeliveryAnnotations.getValue().put(Symbol.getSymbol(annotationKey), annotationValue);
decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations);
ReadableBuffer sendBuffer = decoded.getSendBuffer(1);
ReadableBuffer sendBuffer = decoded.getSendBuffer(1, null);
assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
AMQPStandardMessage msgFromSendBuffer = new AMQPStandardMessage(0, sendBuffer, null, null);
assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
@ -2084,7 +2042,7 @@ public class AMQPMessageTest {
newDeliveryAnnotations2.getValue().put(Symbol.getSymbol(annotationKey2), annotationValue2);
decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations2);
ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5);
ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5, null);
assertEquals(decoded.getEncodeSize(), sendBuffer2.capacity());
AMQPStandardMessage msgFromSendBuffer2 = new AMQPStandardMessage(0, sendBuffer2, null, null);
assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
@ -2093,6 +2051,49 @@ public class AMQPMessageTest {
assertEquals(annotationValue2, msgFromSendBuffer2.getDeliveryAnnotations().getValue().get(Symbol.getSymbol(annotationKey2)));
}
/** It validates we are not adding a header if we don't need to */
@Test
public void testGetSendBufferWithDeliveryAnnotationsAndNoHeader() {
MessageImpl protonMessage = (MessageImpl) Message.Factory.create();
Properties properties = new Properties();
properties.setTo("someNiceLocal");
protonMessage.setProperties(properties);
protonMessage.setBody(new AmqpValue("Sample payload"));
AMQPStandardMessage decoded = encodeAndDecodeMessage(protonMessage);
DeliveryAnnotations newDeliveryAnnotations = new DeliveryAnnotations(new HashMap<>());
final String annotationKey = "annotationKey";
final String annotationValue = "annotationValue";
newDeliveryAnnotations.getValue().put(Symbol.getSymbol(annotationKey), annotationValue);
decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations);
ReadableBuffer sendBuffer = decoded.getSendBuffer(1, null);
assertEquals(decoded.getEncodeSize(), sendBuffer.capacity());
AMQPStandardMessage msgFromSendBuffer = new AMQPStandardMessage(0, sendBuffer, null, null);
assertEquals("someNiceLocal", msgFromSendBuffer.getAddress());
assertNull(msgFromSendBuffer.getProtonMessage().getHeader());
assertNotNull(msgFromSendBuffer.getDeliveryAnnotations());
assertEquals(1, msgFromSendBuffer.getDeliveryAnnotations().getValue().size());
assertEquals(annotationValue, msgFromSendBuffer.getDeliveryAnnotations().getValue().get(Symbol.getSymbol(annotationKey)));
// again with higher deliveryCount
DeliveryAnnotations newDeliveryAnnotations2 = new DeliveryAnnotations(new HashMap<>());
final String annotationKey2 = "annotationKey2";
final String annotationValue2 = "annotationValue2";
newDeliveryAnnotations2.getValue().put(Symbol.getSymbol(annotationKey2), annotationValue2);
decoded.setDeliveryAnnotationsForSendBuffer(newDeliveryAnnotations2);
ReadableBuffer sendBuffer2 = decoded.getSendBuffer(5, null);
AMQPStandardMessage msgFromSendBuffer2 = new AMQPStandardMessage(0, sendBuffer2, null, null);
assertEquals(4, msgFromSendBuffer2.getProtonMessage().getHeader().getDeliveryCount().intValue());
assertEquals("someNiceLocal", msgFromSendBuffer2.getAddress());
assertNotNull(msgFromSendBuffer2.getDeliveryAnnotations());
assertEquals(1, msgFromSendBuffer2.getDeliveryAnnotations().getValue().size());
assertEquals(annotationValue2, msgFromSendBuffer2.getDeliveryAnnotations().getValue().get(Symbol.getSymbol(annotationKey2)));
}
//----- Test Support ------------------------------------------------------//
private MessageImpl createProtonMessage() {

View File

@ -54,7 +54,7 @@ public class ProtonServerSenderContextTest {
when(mockSender.getRemoteSource()).thenReturn(source);
sc.initialise();
sc.initialize();
}
}

View File

@ -28,6 +28,7 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.apache.activemq.advisory.AdvisorySupport;
import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
@ -288,17 +289,20 @@ public class AMQConsumer {
/**
* The acknowledgement in openwire is done based on intervals.
* We will iterate through the list of delivering messages at {@link ServerConsumer#getDeliveringReferencesBasedOnProtocol(boolean, Object, Object)}
* We will iterate through the list of delivering messages at {@link ServerConsumer#scanDeliveringReferences(boolean, Function, Function)}
* and add those to the Transaction.
* Notice that we will start a new transaction on the cases where there is no transaction.
*/
public void acknowledge(MessageAck ack) throws Exception {
MessageId first = ack.getFirstMessageId();
MessageId last = ack.getLastMessageId();
final MessageId startID, lastID;
if (first == null) {
first = last;
if (ack.getFirstMessageId() == null) {
startID = ack.getLastMessageId();
lastID = ack.getLastMessageId();
} else {
startID = ack.getFirstMessageId();
lastID = ack.getLastMessageId();
}
boolean removeReferences = !serverConsumer.isBrowseOnly(); // if it's browse only, nothing to be acked, we just remove the lists
@ -309,7 +313,7 @@ public class AMQConsumer {
removeReferences = false;
}
List<MessageReference> ackList = serverConsumer.getDeliveringReferencesBasedOnProtocol(removeReferences, first, last);
List<MessageReference> ackList = serverConsumer.scanDeliveringReferences(removeReferences, reference -> startID.equals(reference.getProtocolData()), reference -> lastID.equals(reference.getProtocolData()));
if (removeReferences && (ack.isIndividualAck() || ack.isStandardAck() || ack.isPoisonAck())) {
if (deliveredAcks < ackList.size()) {

View File

@ -24,6 +24,7 @@ import java.util.Properties;
import java.util.Set;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.server.metrics.ActiveMQMetricsPlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerFederationPlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerAddressPlugin;
@ -65,7 +66,6 @@ public interface Configuration {
*/
Configuration setName(String name);
/**
* We use Bean-utils to pass in System.properties that start with {@link #setSystemPropertyPrefix(String)}.
* The default should be 'brokerconfig.' (Including the ".").
@ -484,6 +484,10 @@ public interface Configuration {
Configuration clearClusterConfigurations();
Configuration addAMQPConnection(AMQPBrokerConnectConfiguration amqpBrokerConnectConfiguration);
List<AMQPBrokerConnectConfiguration> getAMQPConnection();
/**
* Returns the queues configured for this server.
*/

View File

@ -0,0 +1,116 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.core.config.amqpBrokerConnectivity;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.config.brokerConnectivity.BrokerConnectConfiguration;
import org.apache.activemq.artemis.uri.ConnectorTransportConfigurationParser;
/**
* This is a specific AMQP Broker Connection Configuration
* */
public class AMQPBrokerConnectConfiguration extends BrokerConnectConfiguration {
List<TransportConfiguration> transportConfigurations;
List<AMQPBrokerConnectionElement> connectionElements;
public AMQPBrokerConnectConfiguration(String name, String uri) {
super(name, uri);
}
public AMQPBrokerConnectConfiguration addElement(AMQPBrokerConnectionElement amqpBrokerConnectionElement) {
if (connectionElements == null) {
connectionElements = new ArrayList<>();
}
amqpBrokerConnectionElement.setParent(this);
if (amqpBrokerConnectionElement.getType() == AMQPBrokerConnectionAddressType.MIRROR && !(amqpBrokerConnectionElement instanceof AMQPMirrorBrokerConnectionElement)) {
throw new IllegalArgumentException("must be an AMQPMirrorConnectionElement");
}
connectionElements.add(amqpBrokerConnectionElement);
return this;
}
public List<AMQPBrokerConnectionElement> getConnectionElements() {
return connectionElements;
}
@Override
public void parseURI() throws Exception {
ConnectorTransportConfigurationParser parser = new ConnectorTransportConfigurationParser(false);
URI transportURI = parser.expandURI(getUri());
this.transportConfigurations = parser.newObject(transportURI, getName());
}
public List<TransportConfiguration> getTransportConfigurations() throws Exception {
if (transportConfigurations == null) {
parseURI();
}
return transportConfigurations;
}
@Override
public AMQPBrokerConnectConfiguration setReconnectAttempts(int reconnectAttempts) {
super.setReconnectAttempts(reconnectAttempts);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setUser(String user) {
super.setUser(user);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setRetryInterval(int retryInterval) {
super.setRetryInterval(retryInterval);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setPassword(String password) {
super.setPassword(password);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setUri(String uri) {
super.setUri(uri);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setName(String name) {
super.setName(name);
return this;
}
@Override
public AMQPBrokerConnectConfiguration setAutostart(boolean autostart) {
super.setAutostart(autostart);
return this;
}
}

View File

@ -0,0 +1,21 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.core.config.amqpBrokerConnectivity;
public enum AMQPBrokerConnectionAddressType {
SENDER, RECEIVER, PEER, MIRROR
}

View File

@ -0,0 +1,87 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.core.config.amqpBrokerConnectivity;
import java.io.Serializable;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.postoffice.impl.AddressImpl;
public class AMQPBrokerConnectionElement implements Serializable {
SimpleString matchAddress;
SimpleString queueName;
AMQPBrokerConnectionAddressType type;
AMQPBrokerConnectConfiguration parent;
public AMQPBrokerConnectionElement() {
}
public AMQPBrokerConnectConfiguration getParent() {
return parent;
}
public AMQPBrokerConnectionElement setParent(AMQPBrokerConnectConfiguration parent) {
this.parent = parent;
return this;
}
public SimpleString getQueueName() {
return queueName;
}
public AMQPBrokerConnectionElement setQueueName(String queueName) {
return setQueueName(SimpleString.toSimpleString(queueName));
}
public AMQPBrokerConnectionElement setQueueName(SimpleString queueName) {
this.queueName = queueName;
return this;
}
public SimpleString getMatchAddress() {
return matchAddress;
}
public boolean match(SimpleString checkAddress, WildcardConfiguration wildcardConfig) {
return match(matchAddress, checkAddress, wildcardConfig);
}
public static boolean match(SimpleString matchAddressString, SimpleString checkAddressString, WildcardConfiguration wildcardConfig) {
AddressImpl matchAddress = new AddressImpl(matchAddressString, wildcardConfig);
AddressImpl checkAddress = new AddressImpl(checkAddressString, wildcardConfig);
return checkAddress.matches(matchAddress);
}
public AMQPBrokerConnectionElement setMatchAddress(String matchAddress) {
return this.setMatchAddress(SimpleString.toSimpleString(matchAddress));
}
public AMQPBrokerConnectionElement setMatchAddress(SimpleString matchAddress) {
this.matchAddress = matchAddress;
return this;
}
public AMQPBrokerConnectionAddressType getType() {
return type;
}
public AMQPBrokerConnectionElement setType(AMQPBrokerConnectionAddressType type) {
this.type = type;
return this;
}
}

View File

@ -0,0 +1,92 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.core.config.amqpBrokerConnectivity;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.utils.RandomUtil;
public class AMQPMirrorBrokerConnectionElement extends AMQPBrokerConnectionElement {
SimpleString sourceMirrorAddress;
boolean durable;
boolean queueCreation = true;
boolean queueRemoval = true;
boolean messageAcknowledgements = true;
public AMQPMirrorBrokerConnectionElement() {
this.setType(AMQPBrokerConnectionAddressType.MIRROR);
}
/** There is no setter for this property.
* Basically by setting a sourceMirrorAddress we are automatically setting this to true. */
public boolean isDurable() {
return durable;
}
public AMQPMirrorBrokerConnectionElement setSourceMirrorAddress(String mirrorAddress) {
return this.setSourceMirrorAddress(SimpleString.toSimpleString(mirrorAddress));
}
public AMQPMirrorBrokerConnectionElement setSourceMirrorAddress(SimpleString souceMirrorAddress) {
this.sourceMirrorAddress = souceMirrorAddress;
this.durable = sourceMirrorAddress != null;
return this;
}
public SimpleString getSourceMirrorAddress() {
if (sourceMirrorAddress == null) {
sourceMirrorAddress = SimpleString.toSimpleString(parent.getName() + RandomUtil.randomString());
}
return sourceMirrorAddress;
}
public boolean isQueueCreation() {
return queueCreation;
}
public AMQPMirrorBrokerConnectionElement setQueueCreation(boolean queueCreation) {
this.queueCreation = queueCreation;
return this;
}
public boolean isQueueRemoval() {
return queueRemoval;
}
public AMQPMirrorBrokerConnectionElement setQueueRemoval(boolean queueRemoval) {
this.queueRemoval = queueRemoval;
return this;
}
@Override
public AMQPMirrorBrokerConnectionElement setType(AMQPBrokerConnectionAddressType type) {
super.setType(type);
return this;
}
public boolean isMessageAcknowledgements() {
return messageAcknowledgements;
}
public AMQPMirrorBrokerConnectionElement setMessageAcknowledgements(boolean messageAcknowledgements) {
this.messageAcknowledgements = messageAcknowledgements;
return this;
}
}

View File

@ -0,0 +1,110 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.core.config.brokerConnectivity;
import java.io.Serializable;
/** This is an extension point for outgoing broker configuration.
* This is a new feature that at the time we introduced, is only being used for AMQP.
* Where the broker will create a connection towards another broker using a specific protocol.
* */
public abstract class BrokerConnectConfiguration implements Serializable {
private static final long serialVersionUID = 8026604526022462048L;
private String name;
private String uri;
private String user;
private String password;
private int reconnectAttempts = -1;
private int retryInterval = 5000;
private boolean autostart = true;
public BrokerConnectConfiguration(String name, String uri) {
this.name = name;
this.uri = uri;
}
public abstract void parseURI() throws Exception;
public int getReconnectAttempts() {
return reconnectAttempts;
}
public BrokerConnectConfiguration setReconnectAttempts(int reconnectAttempts) {
this.reconnectAttempts = reconnectAttempts;
return this;
}
public String getUser() {
return user;
}
public BrokerConnectConfiguration setUser(String user) {
this.user = user;
return this;
}
public String getPassword() {
return password;
}
public BrokerConnectConfiguration setPassword(String password) {
this.password = password;
return this;
}
public int getRetryInterval() {
return retryInterval;
}
public BrokerConnectConfiguration setRetryInterval(int retryInterval) {
this.retryInterval = retryInterval;
return this;
}
public String getUri() {
return uri;
}
public BrokerConnectConfiguration setUri(String uri) {
this.uri = uri;
return this;
}
public String getName() {
return name;
}
public BrokerConnectConfiguration setName(String name) {
this.name = name;
return this;
}
public boolean isAutostart() {
return autostart;
}
public BrokerConnectConfiguration setAutostart(boolean autostart) {
this.autostart = autostart;
return this;
}
}

View File

@ -46,6 +46,7 @@ import org.apache.activemq.artemis.api.core.DiscoveryGroupConfiguration;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.BridgeConfiguration;
import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration;
import org.apache.activemq.artemis.core.config.Configuration;
@ -166,6 +167,8 @@ public class ConfigurationImpl implements Configuration, Serializable {
protected List<ClusterConnectionConfiguration> clusterConfigurations = new ArrayList<>();
protected List<AMQPBrokerConnectConfiguration> amqpBrokerConnectConfigurations = new ArrayList<>();
protected List<FederationConfiguration> federationConfigurations = new ArrayList<>();
@Deprecated
@ -356,6 +359,7 @@ public class ConfigurationImpl implements Configuration, Serializable {
private String temporaryQueueNamespace = ActiveMQDefaultConfiguration.getDefaultTemporaryQueueNamespace();
/**
* Parent folder for all data folders.
*/
@ -729,6 +733,17 @@ public class ConfigurationImpl implements Configuration, Serializable {
return newConfig;
}
@Override
public ConfigurationImpl addAMQPConnection(AMQPBrokerConnectConfiguration amqpBrokerConnectConfiguration) {
this.amqpBrokerConnectConfigurations.add(amqpBrokerConnectConfiguration);
return this;
}
@Override
public List<AMQPBrokerConnectConfiguration> getAMQPConnection() {
return this.amqpBrokerConnectConfigurations;
}
@Override
public ConfigurationImpl clearClusterConfigurations() {
clusterConfigurations.clear();

View File

@ -45,6 +45,7 @@ import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.UDPBroadcastEndpointFactory;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.BridgeConfiguration;
import org.apache.activemq.artemis.core.config.ClusterConnectionConfiguration;
import org.apache.activemq.artemis.core.config.Configuration;
@ -57,6 +58,9 @@ import org.apache.activemq.artemis.core.config.MetricsConfiguration;
import org.apache.activemq.artemis.core.config.ScaleDownConfiguration;
import org.apache.activemq.artemis.core.config.TransformerConfiguration;
import org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.federation.FederationAddressPolicyConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationDownstreamConfiguration;
import org.apache.activemq.artemis.core.config.federation.FederationPolicySet;
@ -96,6 +100,7 @@ import org.apache.activemq.artemis.utils.PasswordMaskingUtil;
import org.apache.activemq.artemis.utils.XMLConfigurationUtil;
import org.apache.activemq.artemis.utils.XMLUtil;
import org.apache.activemq.artemis.utils.critical.CriticalAnalyzerPolicy;
import org.jboss.logging.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
@ -106,6 +111,8 @@ import org.w3c.dom.NodeList;
*/
public final class FileConfigurationParser extends XMLConfigurationUtil {
private static final Logger logger = Logger.getLogger(FileConfigurationParser.class);
// Security Parsing
public static final String SECURITY_ELEMENT_NAME = "security-setting";
@ -583,6 +590,21 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
parseClusterConnectionConfigurationURI(ccNode, config);
}
NodeList ccAMQPConnections = e.getElementsByTagName("broker-connections");
if (ccAMQPConnections != null) {
NodeList ccAMQConnectionsURI = e.getElementsByTagName("amqp-connection");
if (ccAMQConnectionsURI != null) {
for (int i = 0; i < ccAMQConnectionsURI.getLength(); i++) {
Element ccNode = (Element) ccAMQConnectionsURI.item(i);
parseAMQPBrokerConnections(ccNode, config);
}
}
}
NodeList dvNodes = e.getElementsByTagName("divert");
for (int i = 0; i < dvNodes.getLength(); i++) {
@ -1845,7 +1867,64 @@ public final class FileConfigurationParser extends XMLConfigurationUtil {
ClusterConnectionConfiguration config = mainConfig.addClusterConfiguration(name, uri);
System.out.println("Adding cluster connection :: " + config);
if (logger.isDebugEnabled()) {
logger.debug("Adding cluster connection :: " + config);
}
}
private void parseAMQPBrokerConnections(final Element e,
final Configuration mainConfig) throws Exception {
String name = e.getAttribute("name");
String uri = e.getAttribute("uri");
int retryInterval = getAttributeInteger(e, "retry-interval", 5000, Validators.GT_ZERO);
int reconnectAttemps = getAttributeInteger(e, "reconnect-attempts", -1, Validators.MINUS_ONE_OR_GT_ZERO);
String user = getAttributeValue(e, "user");
String password = getAttributeValue(e, "password");
boolean autoStart = getBooleanAttribute(e, "auto-start", true);
getInteger(e, "local-bind-port", -1, Validators.MINUS_ONE_OR_GT_ZERO);
AMQPBrokerConnectConfiguration config = new AMQPBrokerConnectConfiguration(name, uri);
config.parseURI();
config.setRetryInterval(retryInterval).setReconnectAttempts(reconnectAttemps).setUser(user).setPassword(password).setAutostart(autoStart);
mainConfig.addAMQPConnection(config);
NodeList senderList = e.getChildNodes();
for (int i = 0; i < senderList.getLength(); i++) {
if (senderList.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element e2 = (Element)senderList.item(i);
AMQPBrokerConnectionAddressType nodeType = AMQPBrokerConnectionAddressType.valueOf(e2.getTagName().toUpperCase());
AMQPBrokerConnectionElement connectionElement;
if (nodeType == AMQPBrokerConnectionAddressType.MIRROR) {
boolean messageAcks = getBooleanAttribute(e2, "message-acknowledgements", true);
boolean queueCreation = getBooleanAttribute(e2,"queue-creation", true);
boolean queueRemoval = getBooleanAttribute(e2, "queue-removal", true);
String sourceMirrorAddress = getAttributeValue(e2, "source-mirror-address");
AMQPMirrorBrokerConnectionElement amqpMirrorConnectionElement = new AMQPMirrorBrokerConnectionElement();
amqpMirrorConnectionElement.setMessageAcknowledgements(messageAcks).setQueueCreation(queueCreation).setQueueRemoval(queueRemoval).
setSourceMirrorAddress(sourceMirrorAddress);
connectionElement = amqpMirrorConnectionElement;
connectionElement.setType(AMQPBrokerConnectionAddressType.MIRROR);
} else {
String match = getAttributeValue(e2, "match");
String queue = getAttributeValue(e2, "queue-name");
connectionElement = new AMQPBrokerConnectionElement();
connectionElement.setMatchAddress(SimpleString.toSimpleString(match)).setType(nodeType);
connectionElement.setQueueName(SimpleString.toSimpleString(queue));
}
config.addElement(connectionElement);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Adding AMQP connection :: " + config);
}
}
private void parseClusterConnectionConfiguration(final Element e, final Configuration mainConfig) throws Exception {

View File

@ -96,6 +96,7 @@ import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.BrokerConnection;
import org.apache.activemq.artemis.core.server.ComponentConfigurationRoutingType;
import org.apache.activemq.artemis.core.server.ConnectorServiceFactory;
import org.apache.activemq.artemis.core.server.Consumer;
@ -3789,6 +3790,60 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
}
}
@Override
public String listBrokerConnections() {
if (AuditLogger.isEnabled()) {
AuditLogger.listBrokerConnections();
}
checkStarted();
clearIO();
try {
JsonArrayBuilder connections = JsonLoader.createArrayBuilder();
for (BrokerConnection brokerConnection : server.getBrokerConnections()) {
JsonObjectBuilder obj = JsonLoader.createObjectBuilder();
obj.add("name", brokerConnection.getName());
obj.add("protocol", brokerConnection.getProtocol());
obj.add("started", brokerConnection.isStarted());
connections.add(obj.build());
}
return connections.build().toString();
} finally {
blockOnIO();
}
}
@Override
public void startBrokerConnection(String name) throws Exception {
if (AuditLogger.isEnabled()) {
AuditLogger.startBrokerConnection(name);
}
checkStarted();
clearIO();
try {
server.startBrokerConnection(name);
} finally {
blockOnIO();
}
}
@Override
public void stopBrokerConnection(String name) throws Exception {
if (AuditLogger.isEnabled()) {
AuditLogger.stopBrokerConnection(name);
}
checkStarted();
clearIO();
try {
server.stopBrokerConnection(name);
} finally {
blockOnIO();
}
}
@Override
public void destroyBridge(final String name) throws Exception {
if (AuditLogger.isEnabled()) {

View File

@ -933,33 +933,35 @@ public final class PageSubscriptionImpl implements PageSubscription {
}
PageCursorInfo info = getPageInfo(position);
PageCache cache = info.getCache();
if (cache != null) {
final long size;
if (persistentSize < 0) {
//cache.getMessage is potentially expensive depending
//on the current cache size and which message is queried
size = getPersistentSize(cache.getMessage(position));
} else {
size = persistentSize;
if (info != null) {
PageCache cache = info.getCache();
if (cache != null) {
final long size;
if (persistentSize < 0) {
//cache.getMessage is potentially expensive depending
//on the current cache size and which message is queried
size = getPersistentSize(cache.getMessage(position));
} else {
size = persistentSize;
}
position.setPersistentSize(size);
}
position.setPersistentSize(size);
logger.tracef("InstallTXCallback looking up pagePosition %s, result=%s", position, info);
info.remove(position);
PageCursorTX cursorTX = (PageCursorTX) tx.getProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS);
if (cursorTX == null) {
cursorTX = new PageCursorTX();
tx.putProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS, cursorTX);
tx.addOperation(cursorTX);
}
cursorTX.addPositionConfirmation(this, position);
}
logger.tracef("InstallTXCallback looking up pagePosition %s, result=%s", position, info);
info.remove(position);
PageCursorTX cursorTX = (PageCursorTX) tx.getProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS);
if (cursorTX == null) {
cursorTX = new PageCursorTX();
tx.putProperty(TransactionPropertyIndexes.PAGE_CURSOR_POSITIONS, cursorTX);
tx.addOperation(cursorTX);
}
cursorTX.addPositionConfirmation(this, position);
}
private PageTransactionInfo getPageTransaction(final PagedReference reference) throws ActiveMQException {

View File

@ -196,6 +196,10 @@ public abstract class AbstractJournalStorageManager extends CriticalComponentImp
protected final Configuration config;
public Configuration getConfig() {
return config;
}
// Persisted core configuration
protected final Map<SimpleString, PersistedSecuritySetting> mapPersistedSecuritySettings = new ConcurrentHashMap<>();

View File

@ -25,6 +25,7 @@ import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
/**
@ -80,4 +81,6 @@ public interface AddressManager {
void updateMessageLoadBalancingTypeForAddress(SimpleString address, MessageLoadBalancingType messageLoadBalancingType) throws Exception;
void scanAddresses(MirrorController mirrorController) throws Exception;
}

View File

@ -33,7 +33,9 @@ import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
/**
@ -210,4 +212,13 @@ public interface PostOffice extends ActiveMQComponent {
Set<SimpleString> getAddresses();
void updateMessageLoadBalancingTypeForAddress(SimpleString address, MessageLoadBalancingType messageLoadBalancingType) throws Exception;
MirrorController getMirrorControlSource();
PostOffice setMirrorControlSource(MirrorController mirrorControllerSource);
void postAcknowledge(MessageReference ref, AckReason reason);
default void scanAddresses(MirrorController mirrorController) throws Exception {
}
}

View File

@ -62,12 +62,14 @@ import org.apache.activemq.artemis.core.server.RouteContextList;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.group.GroupingHandler;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.QueueManagerImpl;
import org.apache.activemq.artemis.core.server.impl.RoutingContextImpl;
import org.apache.activemq.artemis.core.server.management.ManagementService;
import org.apache.activemq.artemis.core.server.management.Notification;
import org.apache.activemq.artemis.core.server.management.NotificationListener;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.HierarchicalRepositoryChangeListener;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
@ -150,6 +152,8 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
private final ActiveMQServer server;
private MirrorController mirrorControllerSource;
public PostOfficeImpl(final ActiveMQServer server,
final StorageManager storageManager,
final PagingManager pagingManager,
@ -228,6 +232,34 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
return started;
}
@Override
public MirrorController getMirrorControlSource() {
return mirrorControllerSource;
}
@Override
public PostOfficeImpl setMirrorControlSource(MirrorController mirrorControllerSource) {
this.mirrorControllerSource = mirrorControllerSource;
return this;
}
@Override
public void postAcknowledge(MessageReference ref, AckReason reason) {
if (mirrorControllerSource != null) {
try {
mirrorControllerSource.postAcknowledge(ref, reason);
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
@Override
public void scanAddresses(MirrorController mirrorController) throws Exception {
addressManager.scanAddresses(mirrorController);
}
// NotificationListener implementation -------------------------------------
@Override
@ -457,6 +489,10 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
server.callBrokerAddressPlugins(plugin -> plugin.beforeAddAddress(addressInfo, reload));
}
if (mirrorControllerSource != null) {
mirrorControllerSource.addAddress(addressInfo);
}
boolean result;
if (reload) {
result = addressManager.reloadAddressInfo(addressInfo);
@ -790,6 +826,11 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
}
managementService.unregisterAddress(address);
final AddressInfo addressInfo = addressManager.removeAddressInfo(address);
if (mirrorControllerSource != null && addressInfo != null) {
mirrorControllerSource.deleteAddress(addressInfo);
}
removeRetroactiveResources(address);
if (server.hasBrokerAddressPlugins()) {
server.callBrokerAddressPlugins(plugin -> plugin.afterRemoveAddress(address, addressInfo));
@ -1540,6 +1581,12 @@ public class PostOfficeImpl implements PostOffice, NotificationListener, Binding
}
}
if (mirrorControllerSource != null && !context.isMirrorController()) {
// we check for isMirrorController as to avoid recursive loop from there
mirrorControllerSource.sendMessage(message, context, refs);
}
if (tx != null) {
tx.addOperation(new AddOperation(refs));
} else {

View File

@ -41,6 +41,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.metrics.MetricsManager;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.utils.CompositeAddress;
import org.jboss.logging.Logger;
@ -361,4 +362,22 @@ public class SimpleAddressManager implements AddressManager {
public void updateMessageLoadBalancingTypeForAddress(SimpleString address, MessageLoadBalancingType messageLoadBalancingType) throws Exception {
getBindingsForRoutingAddress(CompositeAddress.extractAddressName(address)).setMessageLoadBalancingType(messageLoadBalancingType);
}
@Override
public void scanAddresses(MirrorController mirrorController) throws Exception {
mirrorController.startAddressScan();
for (AddressInfo info : addressInfoMap.values()) {
mirrorController.addAddress(info);
Bindings bindings = mappings.get(info.getName());
if (bindings != null) {
for (Binding binding : bindings.getBindings()) {
if (binding instanceof LocalQueueBinding) {
LocalQueueBinding localQueueBinding = (LocalQueueBinding)binding;
mirrorController.createQueue(localQueueBinding.getQueue().getQueueConfiguration());
}
}
}
}
mirrorController.endAddressScan();
}
}

View File

@ -24,6 +24,7 @@ import org.apache.activemq.artemis.api.core.BaseInterceptor;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.protocol.core.CoreRemotingConnection;
import org.apache.activemq.artemis.core.security.ActiveMQPrincipal;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
import org.apache.activemq.artemis.spi.core.remoting.Acceptor;
@ -120,4 +121,6 @@ public interface RemotingService {
Acceptor createAcceptor(TransportConfiguration transportConfiguration);
void destroyAcceptor(String name) throws Exception;
void loadProtocolServices(List<ActiveMQComponent> protocolServices);
}

View File

@ -527,6 +527,13 @@ public class RemotingServiceImpl implements RemotingService, ServerConnectionLif
return connectionCountLatch;
}
@Override
public void loadProtocolServices(List<ActiveMQComponent> protocolServices) {
for (ProtocolManagerFactory protocolManagerFactory : protocolMap.values()) {
protocolManagerFactory.loadProtocolServices(this.server, protocolServices);
}
}
// ServerConnectionLifeCycleListener implementation -----------------------------------
private ProtocolManagerFactory getProtocolManager(String protocol) {

View File

@ -68,6 +68,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerQueuePlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerResourcePlugin;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugin;
import org.apache.activemq.artemis.core.server.reload.ReloadManager;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressSettings;
import org.apache.activemq.artemis.core.transaction.ResourceManager;
@ -87,7 +88,6 @@ import org.apache.activemq.artemis.utils.critical.CriticalAnalyzer;
*/
public interface ActiveMQServer extends ServiceComponent {
enum SERVER_STATE {
/**
* start() has been called but components are not initialized. The whole point of this state,
@ -134,6 +134,16 @@ public interface ActiveMQServer extends ServiceComponent {
Configuration getConfiguration();
void installMirrorController(MirrorController mirrorController);
/** This method will scan all queues and addresses.
* it is supposed to be called before the mirrorController is started */
void scanAddresses(MirrorController mirrorController) throws Exception;
MirrorController getMirrorController();
void removeMirrorControl();
ServiceRegistry getServiceRegistry();
RemotingService getRemotingService();
@ -691,6 +701,14 @@ public interface ActiveMQServer extends ServiceComponent {
void threadDump();
void registerBrokerConnection(BrokerConnection brokerConnection);
void startBrokerConnection(String name) throws Exception;
void stopBrokerConnection(String name) throws Exception;
Collection<BrokerConnection> getBrokerConnections();
/**
* return true if there is a binding for this address (i.e. if there is a created queue)
*

View File

@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.core.server;
public interface BrokerConnection extends ActiveMQComponent {
String getName();
String getProtocol();
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.activemq.artemis.core.server;
@Deprecated
public interface ConnectorService extends ActiveMQComponent {
String getName();

View File

@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.ToLongFunction;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.Message;
@ -72,6 +73,11 @@ public interface Queue extends Bindable,CriticalComponent {
void refDown(MessageReference messageReference);
/** Remove item with a supplied positivie (>= 0) ID.
* if the idSupplier return <0 the ID is considered a non value (null) and it will be ignored
*
* @see org.apache.activemq.artemis.utils.collections.LinkedList#setIDSupplier(ToLongFunction) */
MessageReference removeWithSuppliedID(long id, ToLongFunction<MessageReference> idSupplier);
/**
* The queue definition could be durable, but the messages could eventually be considered non durable.
@ -164,6 +170,13 @@ public interface Queue extends Bindable,CriticalComponent {
long getRingSize();
default boolean isMirrorController() {
return false;
}
default void setMirrorController(boolean mirrorController) {
}
/**
* This will set a reference counter for every consumer present on the queue.
* The ReferenceCounter will know what to do when the counter became zeroed.

View File

@ -36,6 +36,10 @@ public interface RoutingContext {
*/
boolean isReusable();
/** If the routing is from MirrorController, we don't redo mirrorController
* to avoid*/
boolean isMirrorController();
int getPreviousBindingsVersion();
SimpleString getPreviousAddress();

View File

@ -17,6 +17,7 @@
package org.apache.activemq.artemis.core.server;
import java.util.List;
import java.util.function.Function;
import org.apache.activemq.artemis.core.transaction.Transaction;
@ -90,9 +91,9 @@ public interface ServerConsumer extends Consumer, ConsumerInfo {
*/
void backToDelivering(MessageReference reference);
List<MessageReference> getDeliveringReferencesBasedOnProtocol(boolean remove,
Object protocolDataStart,
Object protocolDataEnd);
List<MessageReference> scanDeliveringReferences(boolean remove,
Function<MessageReference, Boolean> startFunction,
Function<MessageReference, Boolean> endFunction);
List<Long> acknowledge(Transaction tx, long messageID) throws Exception;

View File

@ -125,6 +125,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.server.AddressQueryResult;
import org.apache.activemq.artemis.core.server.Bindable;
import org.apache.activemq.artemis.core.server.BindingQueryResult;
import org.apache.activemq.artemis.core.server.BrokerConnection;
import org.apache.activemq.artemis.core.server.Divert;
import org.apache.activemq.artemis.core.server.JournalType;
import org.apache.activemq.artemis.core.server.LargeServerMessage;
@ -172,6 +173,7 @@ import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerSessionPlugi
import org.apache.activemq.artemis.core.server.reload.ReloadCallback;
import org.apache.activemq.artemis.core.server.reload.ReloadManager;
import org.apache.activemq.artemis.core.server.reload.ReloadManagerImpl;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.server.transformer.Transformer;
import org.apache.activemq.artemis.core.settings.HierarchicalRepository;
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy;
@ -289,8 +291,12 @@ public class ActiveMQServerImpl implements ActiveMQServer {
private final List<ProtocolManagerFactory> protocolManagerFactories = new ArrayList<>();
private final List<ActiveMQComponent> protocolServices = new ArrayList<>();
private volatile ManagementService managementService;
private volatile MirrorController mirrorControllerService;
private volatile ConnectorsService connectorsService;
private MemoryManager memoryManager;
@ -308,6 +314,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
*/
private final ReusableLatch activationLatch = new ReusableLatch(0);
private final Map<String, BrokerConnection> brokerConnectionMap = new ConcurrentHashMap<>();
private final Set<ActivateCallback> activateCallbacks = new ConcurrentHashSet<>();
private final Set<ActivationFailureListener> activationFailureListeners = new ConcurrentHashSet<>();
@ -1081,6 +1089,39 @@ public class ActiveMQServerImpl implements ActiveMQServer {
return response;
}
@Override
public void registerBrokerConnection(BrokerConnection brokerConnection) {
brokerConnectionMap.put(brokerConnection.getName(), brokerConnection);
}
@Override
public void startBrokerConnection(String name) throws Exception {
BrokerConnection connection = getBrokerConnection(name);
connection.start();
}
protected BrokerConnection getBrokerConnection(String name) {
BrokerConnection connection = brokerConnectionMap.get(name);
if (connection == null) {
throw new IllegalArgumentException("broker connection " + name + " not found");
}
return connection;
}
@Override
public void stopBrokerConnection(String name) throws Exception {
BrokerConnection connection = getBrokerConnection(name);
connection.stop();
}
@Override
public Collection<BrokerConnection> getBrokerConnections() {
HashSet<BrokerConnection> collections = new HashSet<>(brokerConnectionMap.size());
collections.addAll(brokerConnectionMap.values()); // making a copy
return collections;
}
@Override
public void threadDump() {
ActiveMQServerLogger.LOGGER.threadDump(ThreadDumpUtil.threadDump(""));
@ -1177,6 +1218,10 @@ public class ActiveMQServerImpl implements ActiveMQServer {
stopComponent(federationManager);
stopComponent(clusterManager);
for (ActiveMQComponent component : this.protocolServices) {
stopComponent(component);
}
final RemotingService remotingService = this.remotingService;
if (remotingService != null) {
remotingService.pauseAcceptors();
@ -1290,6 +1335,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
}
}
installMirrorController(null);
pagingManager = null;
securityStore = null;
resourceManager = null;
@ -2277,10 +2324,6 @@ public class ActiveMQServerImpl implements ActiveMQServer {
Queue queue = (Queue) binding.getBindable();
if (hasBrokerQueuePlugins()) {
callBrokerQueuePlugins(plugin -> plugin.beforeDestroyQueue(queue, session, checkConsumerCount, removeConsumers, autoDeleteAddress));
}
if (session != null) {
// make sure the user has privileges to delete this queue
securityStore.check(address, queueName, queue.isDurable() ? CheckType.DELETE_DURABLE_QUEUE : CheckType.DELETE_NON_DURABLE_QUEUE, session);
@ -2299,6 +2342,14 @@ public class ActiveMQServerImpl implements ActiveMQServer {
}
}
if (hasBrokerQueuePlugins()) {
callBrokerQueuePlugins(plugin -> plugin.beforeDestroyQueue(queue, session, checkConsumerCount, removeConsumers, autoDeleteAddress));
}
if (mirrorControllerService != null) {
mirrorControllerService.deleteQueue(queue.getAddress(), queue.getName());
}
queue.deleteQueue(removeConsumers);
if (hasBrokerQueuePlugins()) {
@ -3057,6 +3108,32 @@ public class ActiveMQServerImpl implements ActiveMQServer {
return true;
}
@Override
public void installMirrorController(MirrorController mirrorController) {
logger.debug("Mirror controller is being installed");
if (postOffice != null) {
postOffice.setMirrorControlSource(mirrorController);
}
this.mirrorControllerService = mirrorController;
}
@Override
public void scanAddresses(MirrorController mirrorController) throws Exception {
logger.debug("Scanning addresses to send on mirror controller");
postOffice.scanAddresses(mirrorController);
}
@Override
public MirrorController getMirrorController() {
return this.mirrorControllerService;
}
@Override
public void removeMirrorControl() {
postOffice.setMirrorControlSource(null);
}
/*
* Load the data, and start remoting service so clients can connect
*/
@ -3135,6 +3212,8 @@ public class ActiveMQServerImpl implements ActiveMQServer {
federationManager.start();
}
startProtocolServices();
if (nodeManager.getNodeId() == null) {
throw ActiveMQMessageBundle.BUNDLE.nodeIdNull();
}
@ -3154,6 +3233,19 @@ public class ActiveMQServerImpl implements ActiveMQServer {
}
}
private void startProtocolServices() throws Exception {
remotingService.loadProtocolServices(protocolServices);
for (ProtocolManagerFactory protocolManagerFactory : protocolManagerFactories) {
protocolManagerFactory.loadProtocolServices(this, protocolServices);
}
for (ActiveMQComponent protocolComponent : protocolServices) {
protocolComponent.start();
}
}
/**
* This method exists for a possibility of test cases replacing the FileStoreMonitor for an extension that would for instance pretend a disk full on certain tests.
*/
@ -3614,6 +3706,10 @@ public class ActiveMQServerImpl implements ActiveMQServer {
callBrokerQueuePlugins(plugin -> plugin.beforeCreateQueue(queueConfiguration));
}
if (mirrorControllerService != null) {
mirrorControllerService.createQueue(queueConfiguration);
}
queueConfiguration.setId(storageManager.generateID());
// preemptive check to ensure the filterString is good

View File

@ -16,6 +16,14 @@
*/
package org.apache.activemq.artemis.core.server.impl;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonString;
import javax.json.JsonValue;
import java.io.StringReader;
import java.util.EnumSet;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
@ -31,6 +39,7 @@ import org.apache.activemq.artemis.core.postoffice.QueueBinding;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.core.settings.HierarchicalRepositoryChangeListener;
import org.apache.activemq.artemis.utils.CompositeAddress;
import org.apache.activemq.artemis.utils.JsonLoader;
import org.apache.activemq.artemis.utils.PrefixUtil;
public class AddressInfo {
@ -38,7 +47,7 @@ public class AddressInfo {
private long id;
private long pauseStatusRecord = -1;
private final SimpleString name;
private SimpleString name;
private boolean autoCreated = false;
@ -66,6 +75,16 @@ public class AddressInfo {
private StorageManager storageManager;
private HierarchicalRepositoryChangeListener repositoryChangeListener;
/**
* Private constructor used on JSON decoding.
*/
private AddressInfo() {
}
public AddressInfo(String name) {
this(SimpleString.toSimpleString(name), EnumSet.noneOf(RoutingType.class));
}
public AddressInfo(SimpleString name) {
this(name, EnumSet.noneOf(RoutingType.class));
}
@ -336,4 +355,62 @@ public class AddressInfo {
this.repositoryChangeListener = repositoryChangeListener;
return this;
}
public String toJSON() {
JsonObjectBuilder builder = JsonLoader.createObjectBuilder();
builder.add("id", id);
builder.add("name", "" + name);
builder.add("auto-created", autoCreated);
builder.add("temporary", temporary);
builder.add("internal", internal);
if (firstSeen != null) {
builder.add("firstSeen", firstSeen.getType());
}
if (routingTypes != null) {
JsonArrayBuilder arrayBuilder = JsonLoader.createArrayBuilder();
for (RoutingType rt : routingTypes) {
arrayBuilder.add(rt.getType());
}
builder.add("routingTypes", arrayBuilder);
}
return builder.build().toString();
}
protected void setJson(String key, Object value) {
if (key.equals("id")) {
JsonNumber jsonLong = (JsonNumber) value;
this.id = jsonLong.longValue();
} else if (key.equals("name")) {
JsonString jasonString = (JsonString) value;
this.name = SimpleString.toSimpleString(jasonString.getString());
} else if (key.equals("auto-created")) {
this.autoCreated = Boolean.valueOf(value.toString());
} else if (key.equals("temporary")) {
this.temporary = Boolean.valueOf(value.toString());
} else if (key.equals("firstSeen")) {
JsonNumber jsonNumber = (JsonNumber)value;
this.firstSeen = RoutingType.getType((byte)jsonNumber.intValue());
} else if (key.equals("routingTypes")) {
JsonArray routingTypes = (JsonArray) value;
for (JsonValue rtValue : routingTypes) {
JsonNumber jsonNumber = (JsonNumber)rtValue;
this.addRoutingType(RoutingType.getType((byte)jsonNumber.intValue()));
}
}
}
public static AddressInfo fromJSON(String jsonString) {
JsonObject json = JsonLoader.createReader(new StringReader(jsonString)).readObject();
AddressInfo result = new AddressInfo();
for (Map.Entry<String, JsonValue> entry : json.entrySet()) {
result.setJson(entry.getKey(), entry.getValue());
}
return result;
}
}

View File

@ -564,6 +564,7 @@ public class LastValueQueue extends QueueImpl {
@Override
public void setOwner(PagingStore owner) {
ref.setOwner(owner);
}
}

View File

@ -362,6 +362,7 @@ public class MessageReferenceImpl extends LinkedListImpl.Node<MessageReferenceIm
@Override
public void setOwner(PagingStore owner) {
new Exception("Setting owner as " + owner.getStoreName()).printStackTrace();
this.owner = owner;
}
}

View File

@ -41,6 +41,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.ToLongFunction;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQException;
@ -180,6 +181,8 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
private volatile boolean printErrorExpiring = false;
private boolean mirrorController;
// Messages will first enter intermediateMessageReferences
// Before they are added to messageReferences
// This is to avoid locking the queue on the producer
@ -188,6 +191,15 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
// This is where messages are stored
private final PriorityLinkedList<MessageReference> messageReferences = new PriorityLinkedListImpl<>(QueueImpl.NUM_PRIORITIES, MessageReferenceImpl.getIDComparator());
private ToLongFunction<MessageReference> idSupplier;
private void checkIDSupplier(ToLongFunction<MessageReference> idSupplier) {
if (this.idSupplier != idSupplier) {
this.idSupplier = idSupplier;
messageReferences.setIDSupplier(idSupplier);
}
}
// The quantity of pagedReferences on messageReferences priority list
private final AtomicInteger pagedReferences = new AtomicInteger(0);
@ -707,6 +719,16 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
return !nonDestructive;
}
@Override
public boolean isMirrorController() {
return mirrorController;
}
@Override
public void setMirrorController(boolean mirrorController) {
this.mirrorController = mirrorController;
}
public SimpleString getRoutingName() {
return name;
}
@ -3183,6 +3205,12 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
}
}
@Override
public MessageReference removeWithSuppliedID(long id, ToLongFunction<MessageReference> idSupplier) {
checkIDSupplier(idSupplier);
return messageReferences.removeWithID(id);
}
private void internalAddRedistributor(final ArtemisExecutor executor) {
// create the redistributor only once if there are no local consumers
if (consumers.isEmpty() && redistributor == null) {
@ -3793,70 +3821,74 @@ public class QueueImpl extends CriticalComponentImpl implements Queue {
public void postAcknowledge(final MessageReference ref, AckReason reason) {
QueueImpl queue = (QueueImpl) ref.getQueue();
queue.decDelivering(ref);
if (nonDestructive && reason == AckReason.NORMAL) {
// this is done to tell the difference between actual acks and just a closed consumer in the non-destructive use-case
ref.setInDelivery(false);
return;
}
if (reason == AckReason.EXPIRED) {
messagesExpired.incrementAndGet();
} else if (reason == AckReason.KILLED) {
messagesKilled.incrementAndGet();
} else if (reason == AckReason.REPLACED) {
messagesReplaced.incrementAndGet();
} else {
messagesAcknowledged.incrementAndGet();
}
if (ref.isPaged()) {
// nothing to be done
return;
}
Message message;
try {
message = ref.getMessage();
} catch (Throwable e) {
ActiveMQServerLogger.LOGGER.unableToPerformPostAcknowledge(e);
message = null;
}
queue.decDelivering(ref);
if (nonDestructive && reason == AckReason.NORMAL) {
// this is done to tell the difference between actual acks and just a closed consumer in the non-destructive use-case
ref.setInDelivery(false);
return;
}
if (message == null || (nonDestructive && reason == AckReason.NORMAL))
return;
if (reason == AckReason.EXPIRED) {
messagesExpired.incrementAndGet();
} else if (reason == AckReason.KILLED) {
messagesKilled.incrementAndGet();
} else if (reason == AckReason.REPLACED) {
messagesReplaced.incrementAndGet();
} else {
messagesAcknowledged.incrementAndGet();
}
queue.refDown(ref);
if (ref.isPaged()) {
// nothing to be done
return;
}
boolean durableRef = message.isDurable() && queue.isDurable();
Message message;
if (durableRef) {
int count = queue.durableDown(message);
try {
message = ref.getMessage();
} catch (Throwable e) {
ActiveMQServerLogger.LOGGER.unableToPerformPostAcknowledge(e);
message = null;
}
if (count == 0) {
// Note - we MUST store the delete after the preceding ack has been committed to storage, we cannot combine
// the last ack and delete into a single delete.
// This is because otherwise we could have a situation where the same message is being acked concurrently
// from two different queues on different sessions.
// One decrements the ref count, then the other stores a delete, the delete gets committed, but the first
// ack isn't committed, then the server crashes and on
// recovery the message is deleted even though the other ack never committed
if (message == null || (nonDestructive && reason == AckReason.NORMAL))
return;
// also note then when this happens as part of a transaction it is the tx commit of the ack that is
// important not this
queue.refDown(ref);
// Also note that this delete shouldn't sync to disk, or else we would build up the executor's queue
// as we can't delete each messaging with sync=true while adding messages transactionally.
// There is a startup check to remove non referenced messages case these deletes fail
try {
if (!storageManager.deleteMessage(message.getMessageID())) {
ActiveMQServerLogger.LOGGER.errorRemovingMessage(new Exception(), message.getMessageID());
boolean durableRef = message.isDurable() && queue.isDurable();
if (durableRef) {
int count = queue.durableDown(message);
if (count == 0) {
// Note - we MUST store the delete after the preceding ack has been committed to storage, we cannot combine
// the last ack and delete into a single delete.
// This is because otherwise we could have a situation where the same message is being acked concurrently
// from two different queues on different sessions.
// One decrements the ref count, then the other stores a delete, the delete gets committed, but the first
// ack isn't committed, then the server crashes and on
// recovery the message is deleted even though the other ack never committed
// also note then when this happens as part of a transaction it is the tx commit of the ack that is
// important not this
// Also note that this delete shouldn't sync to disk, or else we would build up the executor's queue
// as we can't delete each messaging with sync=true while adding messages transactionally.
// There is a startup check to remove non referenced messages case these deletes fail
try {
if (!storageManager.deleteMessage(message.getMessageID())) {
ActiveMQServerLogger.LOGGER.errorRemovingMessage(new Exception(), message.getMessageID());
}
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.errorRemovingMessage(e, message.getMessageID());
}
} catch (Exception e) {
ActiveMQServerLogger.LOGGER.errorRemovingMessage(e, message.getMessageID());
}
}
} finally {
postOffice.postAcknowledge(ref, reason);
}
}

View File

@ -34,7 +34,7 @@ import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.jboss.logging.Logger;
public final class RoutingContextImpl implements RoutingContext {
public class RoutingContextImpl implements RoutingContext {
private static final Logger logger = Logger.getLogger(RoutingContextImpl.class);
@ -81,6 +81,11 @@ public final class RoutingContextImpl implements RoutingContext {
this.executor = executor;
}
@Override
public boolean isMirrorController() {
return false;
}
@Override
public boolean isReusable() {
return reusable != null && reusable;

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.ActiveMQException;
@ -848,9 +849,9 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
* This will be useful for other protocols that will need this such as openWire or MQTT.
*/
@Override
public synchronized List<MessageReference> getDeliveringReferencesBasedOnProtocol(boolean remove,
Object protocolDataStart,
Object protocolDataEnd) {
public synchronized List<MessageReference> scanDeliveringReferences(boolean remove,
Function<MessageReference, Boolean> startFunction,
Function<MessageReference, Boolean> endFunction) {
LinkedList<MessageReference> retReferences = new LinkedList<>();
boolean hit = false;
synchronized (lock) {
@ -858,24 +859,22 @@ public class ServerConsumerImpl implements ServerConsumer, ReadyListener {
while (referenceIterator.hasNext()) {
MessageReference reference = referenceIterator.next();
if (!hit) {
hit = reference.getProtocolData() != null && reference.getProtocolData().equals(protocolDataStart);
if (!hit && startFunction.apply(reference)) {
hit = true;
}
// notice: this is not an else clause, this is also valid for the first hit
if (hit) {
if (remove) {
referenceIterator.remove();
}
retReferences.add(reference);
// Whenever this is met we interrupt the loop
// even on the first hit
if (reference.getProtocolData() != null && reference.getProtocolData().equals(protocolDataEnd)) {
if (endFunction.apply(reference)) {
break;
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.core.server.mirror;
import java.util.List;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
/**
* This represents the contract we will use to send messages to replicas.
* */
public interface MirrorController {
void startAddressScan() throws Exception;
void endAddressScan() throws Exception;
void addAddress(AddressInfo addressInfo) throws Exception;
void deleteAddress(AddressInfo addressInfo) throws Exception;
void createQueue(QueueConfiguration queueConfiguration) throws Exception;
void deleteQueue(SimpleString addressName, SimpleString queueName) throws Exception;
void sendMessage(Message message, RoutingContext context, List<MessageReference> refs);
void postAcknowledge(MessageReference ref, AckReason reason) throws Exception;
}

View File

@ -23,6 +23,8 @@ import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.activemq.artemis.api.core.BaseInterceptor;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
public abstract class AbstractProtocolManagerFactory<P extends BaseInterceptor> implements ProtocolManagerFactory<P> {
@ -56,6 +58,12 @@ public abstract class AbstractProtocolManagerFactory<P extends BaseInterceptor>
* @param parameters
*/
protected void stripPasswordParameters(Map<String, Object> parameters) {
parameters.entrySet().removeIf(entries->entries.getKey().toLowerCase().contains("password"));
if (parameters != null) {
parameters.entrySet().removeIf(entries -> entries.getKey().toLowerCase().contains("password"));
}
}
@Override
public void loadProtocolServices(ActiveMQServer server, List<ActiveMQComponent> services) {
}
}

View File

@ -22,6 +22,7 @@ import java.util.Map;
import org.apache.activemq.artemis.api.core.BaseInterceptor;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.core.persistence.Persister;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
public interface ProtocolManagerFactory<P extends BaseInterceptor> {
@ -57,4 +58,6 @@ public interface ProtocolManagerFactory<P extends BaseInterceptor> {
String[] getProtocols();
String getModuleName();
void loadProtocolServices(ActiveMQServer server, List<ActiveMQComponent> services);
}

View File

@ -24,7 +24,13 @@ import org.w3c.dom.NodeList;
public class XMLConfigurationUtil {
public static final String getAttributeValue(Node element, String attribute) {
return element.getAttributes().getNamedItem(attribute).getNodeValue();
Node node = element.getAttributes().getNamedItem(attribute);
if (node == null) {
return null;
} else {
return node.getNodeValue();
}
}
public static final String getTrimmedTextContent(Node element) {
@ -90,6 +96,20 @@ public class XMLConfigurationUtil {
}
}
public static final Integer getAttributeInteger(final Element e,
final String name,
final Integer def,
final Validators.Validator validator) {
String attribute = e.getAttribute(name);
if (attribute != null && !attribute.equals("")) {
int val = XMLUtil.parseInt(e, attribute);
validator.validate(name, val);
return val;
} else {
return def;
}
}
public static final Integer getInteger(final Element e,
final String name,
final Integer def,
@ -120,6 +140,16 @@ public class XMLConfigurationUtil {
}
}
public static final Boolean getBooleanAttribute(final Element e, final String name, final Boolean def) {
String attributeValue = e.getAttribute(name);
if (attributeValue == null || attributeValue.isEmpty()) {
return def;
} else {
return Boolean.parseBoolean(attributeValue);
}
}
public static final Boolean parameterExists(final Element e, final String name) {
NodeList nl = e.getElementsByTagName(name);
if (nl.getLength() > 0) {

View File

@ -610,6 +610,15 @@
</xsd:annotation>
</xsd:element>
<xsd:element name="broker-connections" type="brokerConnectType" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
A list of connections the broker will make towards other servers.
Currently the only connection type supported is amqpConnection
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="grouping-handler" type="groupingHandlerType" maxOccurs="1" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
@ -1997,6 +2006,139 @@
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="brokerConnectType">
<xsd:sequence maxOccurs="unbounded">
<xsd:element name="amqp-connection" type="amqp-connectionUriType"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="amqp-connectionUriType">
<xsd:sequence>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="sender" type="amqp-address-match-type"/>
<xsd:element name="receiver" type="amqp-address-match-type"/>
<xsd:element name="peer" type="amqp-address-match-type"/>
<xsd:element name="mirror" type="amqp-mirror-type"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="uri" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
uri of the amqp connection
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-start" type="xsd:boolean" default="true">
<xsd:annotation>
<xsd:documentation>
should the broker connection be started when the server is started.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reconnect-attempts" type="xsd:int" default="-1">
<xsd:annotation>
<xsd:documentation>
How many attempts should be made to reconnect after failure
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="retry-interval" type="xsd:long" default="5000">
<xsd:annotation>
<xsd:documentation>
period (in ms) between successive retries
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="user" type="xsd:string" default="">
<xsd:annotation>
<xsd:documentation>
User name used to connect. If not defined it will try an anonymous connection.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" default="">
<xsd:annotation>
<xsd:documentation>
Password used to connect. If not defined it will try an anonymous connection.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
name of the amqp connection
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attributeGroup ref="xml:specialAttrs"/>
</xsd:complexType>
<xsd:complexType name="amqp-address-match-type">
<xsd:attribute name="match" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
address expression to match addresses
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue-name" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
This is the exact queue name to be used.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="amqp-mirror-type">
<xsd:annotation>
<xsd:documentation>
This will determine that queues are mirrored towards this next broker.
All events will be send towards this AMQP connection acting like a replica.
</xsd:documentation>
</xsd:annotation>
<!--
TODO: comment this out when we start supporting matching on mirror.
<xsd:attribute name="match" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
address expression to match addresses
</xsd:documentation>
</xsd:annotation>
</xsd:attribute> -->
<xsd:attribute name="message-acknowledgements" type="xsd:boolean" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
Should mirror acknowledgements towards the other server
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue-creation" type="xsd:boolean" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
Should mirror queue creation events for addresses and queues.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue-removal" type="xsd:boolean" use="optional" default="true">
<xsd:annotation>
<xsd:documentation>
Should mirror queue deletion events for addresses and queues.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="source-mirror-address" type="xsd:string" use="optional" default="">
<xsd:annotation>
<xsd:documentation>
By default the replica will use a temporary store and forward queue to store events towards the mirror / replica.
However if this is set, we will use a defined durable queue.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="cluster-connectionUriType">
<xsd:attribute name="address" type="xsd:string" use="required">
<xsd:annotation>

View File

@ -0,0 +1,36 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.core.config.impl;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
import org.junit.Assert;
import org.junit.Test;
public class AMQPConnectMatchTest {
@Test
public void testMach() throws Exception {
String match = "AD.#";
String checkAddressMatching = "AD.TEST";
String checkAddressNotMatching = "DD.TEST";
WildcardConfiguration configuration = new WildcardConfiguration();
Assert.assertTrue(AMQPBrokerConnectionElement.match(SimpleString.toSimpleString(match), SimpleString.toSimpleString(checkAddressMatching), configuration));
Assert.assertFalse(AMQPBrokerConnectionElement.match(SimpleString.toSimpleString(match), SimpleString.toSimpleString(checkAddressNotMatching), configuration));
}
}

View File

@ -16,6 +16,10 @@
*/
package org.apache.activemq.artemis.core.list;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import org.apache.activemq.artemis.utils.collections.LinkedListIterator;
import org.apache.activemq.artemis.utils.collections.PriorityLinkedListImpl;
import org.junit.Assert;
@ -87,32 +91,32 @@ public final class PriorityLinkedListTest extends Assert {
list = getList();
a = new Wibble("a");
b = new Wibble("b");
c = new Wibble("c");
d = new Wibble("d");
e = new Wibble("e");
f = new Wibble("f");
g = new Wibble("g");
h = new Wibble("h");
i = new Wibble("i");
j = new Wibble("j");
k = new Wibble("k");
l = new Wibble("l");
m = new Wibble("m");
n = new Wibble("n");
o = new Wibble("o");
p = new Wibble("p");
q = new Wibble("q");
r = new Wibble("r");
s = new Wibble("s");
t = new Wibble("t");
u = new Wibble("u");
v = new Wibble("v");
w = new Wibble("w");
x = new Wibble("x");
y = new Wibble("y");
z = new Wibble("z");
a = new Wibble("a", 1);
b = new Wibble("b", 2);
c = new Wibble("c", 3);
d = new Wibble("d", 4);
e = new Wibble("e", 5);
f = new Wibble("f", 6);
g = new Wibble("g", 7);
h = new Wibble("h", 8);
i = new Wibble("i", 9);
j = new Wibble("j", 10);
k = new Wibble("k", 11);
l = new Wibble("l", 12);
m = new Wibble("m", 13);
n = new Wibble("n", 14);
o = new Wibble("o", 15);
p = new Wibble("p", 16);
q = new Wibble("q", 17);
r = new Wibble("r", 18);
s = new Wibble("s", 19);
t = new Wibble("t", 20);
u = new Wibble("u", 21);
v = new Wibble("v", 22);
w = new Wibble("w", 23);
x = new Wibble("x", 24);
y = new Wibble("y", 25);
z = new Wibble("z", 26);
}
@Test
@ -870,18 +874,73 @@ public final class PriorityLinkedListTest extends Assert {
iter.remove();
}
@Test
public void testRemoveWithID() {
for (int i = 0; i < 3000; i++) {
list.addHead(new Wibble("" + i, i), i % 10);
}
list.setIDSupplier(source -> source.id);
// remove every 3rd
for (int i = 0; i < 3000; i += 3) {
Assert.assertEquals(new Wibble("" + i, i), list.removeWithID(i));
}
Assert.assertEquals(2000, list.size());
Iterator<Wibble> iterator = list.iterator();
HashSet<String> values = new HashSet<>();
while (iterator.hasNext()) {
values.add(iterator.next().s1);
}
Assert.assertEquals(2000, values.size());
for (int i = 0; i < 3000; i += 3) {
if (i % 3 == 0) {
Assert.assertFalse(values.contains("" + i));
} else {
Assert.assertTrue(values.contains("" + i));
}
}
}
static class Wibble {
String s1;
long id;
Wibble(final String s) {
Wibble(final String s, long id) {
this.s1 = s;
this.id = id;
}
@Override
public String toString() {
return s1;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Wibble wibble = (Wibble) o;
return Objects.equals(s1, wibble.s1);
}
@Override
public int hashCode() {
return Objects.hash(s1);
}
}
}

View File

@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.core.server.impl;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.tests.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
public class AdressInfoJSonTest {
@Test
public void testJSONparsing() {
SimpleString name = RandomUtil.randomSimpleString();
AddressInfo info = new AddressInfo(name);
info.setAutoCreated(RandomUtil.randomBoolean());
info.setId(RandomUtil.randomLong());
info.setInternal(RandomUtil.randomBoolean());
info.addRoutingType(RoutingType.ANYCAST);
AddressInfo newInfo = AddressInfo.fromJSON(info.toJSON());
Assert.assertEquals(info.toString(), newInfo.toString());
Assert.assertEquals(info.getRoutingType(), newInfo.getRoutingType());
info.addRoutingType(RoutingType.ANYCAST);
newInfo = AddressInfo.fromJSON(info.toJSON());
Assert.assertEquals(info.toString(), newInfo.toString());
Assert.assertEquals(info.getRoutingType(), newInfo.getRoutingType());
}
}

View File

@ -29,6 +29,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.ToLongFunction;
import io.netty.buffer.ByteBuf;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
@ -848,6 +849,11 @@ public class ScheduledDeliveryHandlerTest extends Assert {
}
@Override
public MessageReference removeWithSuppliedID(long id, ToLongFunction<MessageReference> idSupplier) {
return null;
}
@Override
public void refDown(MessageReference messageReference) {

View File

@ -372,6 +372,21 @@
<discovery-group-ref discovery-group-name="dg1"/>
</cluster-connection>
</cluster-connections>
<broker-connections>
<amqp-connection uri="tcp://test1:111" name="test1" retry-interval="333" reconnect-attempts="33" user="testuser" password="testpassword">
<sender match="TEST-SENDER" />
<receiver match="TEST-RECEIVER" />
<peer match="TEST-PEER"/>
<receiver queue-name="TEST-WITH-QUEUE-NAME"/>
<mirror message-acknowledgements="false" queue-creation="false" source-mirror-address="TEST-REPLICA" queue-removal="false"/>
</amqp-connection>
<amqp-connection uri="tcp://test2:222" name="test2">
<mirror/>
</amqp-connection>
<amqp-connection uri="tcp://false" name="auto-start-false" auto-start="false">
<mirror/>
</amqp-connection>
</broker-connections>
<grouping-handler name="gh1">
<type>LOCAL</type>
<address>jms</address>

View File

@ -440,6 +440,15 @@ public class AmqpMessage {
return message.getHeader().getDurable();
}
public int getDeliveryCount() {
if (message.getHeader() == null || message.getHeader().getDeliveryCount() == null) {
return 0;
}
return message.getHeader().getDeliveryCount().intValue();
}
/**
* Sets the priority header on the outgoing message.
*

View File

@ -211,8 +211,9 @@ public class AmqpClientTestSupport extends AmqpTestSupport {
params.put(TransportConstants.PROTOCOLS_PROP_NAME, getConfiguredProtocols());
HashMap<String, Object> amqpParams = new HashMap<>();
configureAMQPAcceptorParameters(amqpParams);
return new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "netty-acceptor", amqpParams);
TransportConfiguration tc = new TransportConfiguration(NETTY_ACCEPTOR_FACTORY, params, "netty-acceptor", amqpParams);
configureAMQPAcceptorParameters(tc);
return tc;
}
protected String getConfiguredProtocols() {
@ -265,37 +266,49 @@ public class AmqpClientTestSupport extends AmqpTestSupport {
protected void configureBrokerSecurity(ActiveMQServer server) {
if (isSecurityEnabled()) {
ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
// User additions
securityManager.getConfiguration().addUser(noprivUser, noprivPass);
securityManager.getConfiguration().addRole(noprivUser, "nothing");
securityManager.getConfiguration().addUser(browseUser, browsePass);
securityManager.getConfiguration().addRole(browseUser, "browser");
securityManager.getConfiguration().addUser(guestUser, guestPass);
securityManager.getConfiguration().addRole(guestUser, "guest");
securityManager.getConfiguration().addUser(fullUser, fullPass);
securityManager.getConfiguration().addRole(fullUser, "full");
// Configure roles
HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
HashSet<Role> value = new HashSet<>();
value.add(new Role("nothing", false, false, false, false, false, false, false, false, false, false));
value.add(new Role("browser", false, false, false, false, false, false, false, true, false, false));
value.add(new Role("guest", false, true, false, false, false, false, false, true, false, false));
value.add(new Role("full", true, true, true, true, true, true, true, true, true, true));
securityRepository.addMatch(getQueueName(), value);
server.getConfiguration().setSecurityEnabled(true);
enableSecurity(server);
} else {
server.getConfiguration().setSecurityEnabled(false);
}
}
protected void enableSecurity(ActiveMQServer server, String... securityMatches) {
ActiveMQJAASSecurityManager securityManager = (ActiveMQJAASSecurityManager) server.getSecurityManager();
// User additions
securityManager.getConfiguration().addUser(noprivUser, noprivPass);
securityManager.getConfiguration().addRole(noprivUser, "nothing");
securityManager.getConfiguration().addUser(browseUser, browsePass);
securityManager.getConfiguration().addRole(browseUser, "browser");
securityManager.getConfiguration().addUser(guestUser, guestPass);
securityManager.getConfiguration().addRole(guestUser, "guest");
securityManager.getConfiguration().addUser(fullUser, fullPass);
securityManager.getConfiguration().addRole(fullUser, "full");
// Configure roles
HierarchicalRepository<Set<Role>> securityRepository = server.getSecurityRepository();
HashSet<Role> value = new HashSet<>();
value.add(new Role("nothing", false, false, false, false, false, false, false, false, false, false));
value.add(new Role("browser", false, false, false, false, false, false, false, true, false, false));
value.add(new Role("guest", false, true, false, false, false, false, false, true, false, false));
value.add(new Role("full", true, true, true, true, true, true, true, true, true, true));
securityRepository.addMatch(getQueueName(), value);
for (String match : securityMatches) {
securityRepository.addMatch(match, value);
}
server.getConfiguration().setSecurityEnabled(true);
}
protected void configureAMQPAcceptorParameters(Map<String, Object> params) {
// None by default
}
protected void configureAMQPAcceptorParameters(TransportConfiguration tc) {
// None by default
}
public Queue getProxyToQueue(String queueName) {
return server.locateQueue(SimpleString.toSimpleString(queueName));
}

View File

@ -0,0 +1,144 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.tests.integration.amqp;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.plugin.ActiveMQServerMessagePlugin;
import org.apache.activemq.artemis.tests.util.RandomUtil;
import org.apache.activemq.transport.amqp.client.AmqpClient;
import org.apache.activemq.transport.amqp.client.AmqpConnection;
import org.apache.activemq.transport.amqp.client.AmqpMessage;
import org.apache.activemq.transport.amqp.client.AmqpReceiver;
import org.apache.activemq.transport.amqp.client.AmqpSender;
import org.apache.activemq.transport.amqp.client.AmqpSession;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations;
import org.junit.Assert;
import org.junit.Test;
/**
* Test broker behavior when creating AMQP senders
*/
public class AmqpReferenceDeliveryAnnotationTest extends AmqpClientTestSupport {
@Override
protected void addAdditionalAcceptors(ActiveMQServer server) throws Exception {
}
@Test
public void testReceiveAnnotations() throws Exception {
internalReceiveAnnotations(false, false);
}
@Test
public void testReceiveAnnotationsLargeMessage() throws Exception {
internalReceiveAnnotations(true, false);
}
@Test
public void testReceiveAnnotationsReboot() throws Exception {
internalReceiveAnnotations(false, true);
}
@Test
public void testReceiveAnnotationsLargeMessageReboot() throws Exception {
internalReceiveAnnotations(true, true);
}
public void internalReceiveAnnotations(boolean largeMessage, boolean reboot) throws Exception {
final String uuid = RandomUtil.randomString();
server.getConfiguration().registerBrokerPlugin(new ActiveMQServerMessagePlugin() {
@Override
public void beforeDeliver(ServerConsumer consumer, MessageReference reference) throws ActiveMQException {
Map<Symbol, Object> symbolObjectMap = new HashMap<>();
DeliveryAnnotations deliveryAnnotations = new DeliveryAnnotations(symbolObjectMap);
symbolObjectMap.put(Symbol.getSymbol("KEY"), uuid);
reference.setProtocolData(deliveryAnnotations);
}
});
AmqpClient client = createAmqpClient();
AmqpConnection connection = addConnection(client.connect());
AmqpSession session = connection.createSession();
AmqpSender sender = session.createSender(getQueueName());
AmqpMessage message = new AmqpMessage();
String body;
if (largeMessage) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 1024 * 1024; i++) {
buffer.append("*");
}
body = buffer.toString();
} else {
body = "test-message";
}
message.setMessageId("msg" + 1);
message.setText(body);
message.setDurable(true);
sender.send(message);
message = new AmqpMessage();
message.setMessageId("msg" + 2);
message.setDurable(true);
message.setText(body);
sender.send(message);
sender.close();
if (reboot) {
connection.close();
server.stop();
server.start();
client = createAmqpClient();
connection = addConnection(client.connect());
session = connection.createSession();
}
AmqpReceiver receiver = session.createReceiver(getQueueName());
receiver.flow(2);
AmqpMessage received = receiver.receive(10, TimeUnit.SECONDS);
assertNotNull("Should have read message", received);
assertEquals("msg1", received.getMessageId());
assertEquals(uuid, received.getDeliveryAnnotation("KEY"));
received.accept();
received = receiver.receive(10, TimeUnit.SECONDS);
assertNotNull("Should have read message", received);
assertEquals("msg2", received.getMessageId());
assertEquals(uuid, received.getDeliveryAnnotation("KEY"));
received.accept();
Assert.assertNull(receiver.receiveNoWait());
receiver.close();
connection.close();
}
}

View File

@ -0,0 +1,234 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.tests.integration.amqp.connect;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.CoreAddressConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
import org.apache.activemq.artemis.tests.util.CFUtil;
import org.apache.activemq.artemis.utils.Wait;
import org.junit.Assert;
import org.junit.Test;
public class AMQPBridgeTest extends AmqpClientTestSupport {
protected static final int AMQP_PORT_2 = 5673;
ActiveMQServer server_2;
@Override
protected ActiveMQServer createServer() throws Exception {
return createServer(AMQP_PORT, false);
}
@Test
public void testsSimpleConnect() throws Exception {
server.start();
server_2 = createServer(AMQP_PORT_2, false);
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server_2.start();
}
@Test
public void testSimpleTransferPush() throws Exception {
internalTransferPush("TEST", false);
}
@Test
public void testSimpleTransferPushDeferredCreation() throws Exception {
internalTransferPush("TEST", true);
}
public void internalTransferPush(String queueName, boolean deferCreation) throws Exception {
server.setIdentity("targetServer");
server.start();
server.addAddressInfo(new AddressInfo(SimpleString.toSimpleString(queueName), RoutingType.ANYCAST));
server.createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST));
server_2 = createServer(AMQP_PORT_2, false);
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPBrokerConnectionElement().setMatchAddress(queueName).setType(AMQPBrokerConnectionAddressType.SENDER));
server_2.getConfiguration().addAMQPConnection(amqpConnection);
if (!deferCreation) {
server_2.getConfiguration().addAddressConfiguration(new CoreAddressConfiguration().setName(queueName).addRoutingType(RoutingType.ANYCAST));
server_2.getConfiguration().addQueueConfiguration(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST));
}
server_2.setIdentity("serverWithBridge");
server_2.start();
Wait.assertTrue(server_2::isStarted);
if (deferCreation) {
server_2.addAddressInfo(new AddressInfo(queueName).addRoutingType(RoutingType.ANYCAST));
server_2.createQueue(new QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST));
}
ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT_2);
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(session.createQueue(queueName));
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
String largeMessageBody = null;
for (int i = 0; i < 30; i++) {
if (i == 0) {
StringBuffer buffer = new StringBuffer();
for (int s = 0; s < 10024; s++) {
buffer.append("*******************************************************************************************************************************");
}
largeMessageBody = buffer.toString();
TextMessage message = session.createTextMessage(buffer.toString());
producer.send(message);
} else {
producer.send(session.createMessage());
}
}
Queue testQueueOnServer2 = server_2.locateQueue(queueName);
Assert.assertNotNull(testQueueOnServer2);
Wait.assertEquals(0, testQueueOnServer2::getMessageCount);
ConnectionFactory factory2 = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
Connection connection2 = factory2.createConnection();
Session session2 = connection2.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection2.start();
MessageConsumer consumer = session2.createConsumer(session2.createQueue(queueName));
for (int i = 0; i < 30; i++) {
Message message = consumer.receive(5000);
if (message instanceof TextMessage) {
if (message instanceof TextMessage) {
Assert.assertEquals(largeMessageBody, ((TextMessage)message).getText());
} else {
System.out.println("i = " + i);
}
}
}
Assert.assertNull(consumer.receiveNoWait());
}
@Test
public void testSimpleTransferPull() throws Exception {
internaltestSimpleTransferPull(false);
}
@Test
public void testSimpleTransferPullSecurity() throws Exception {
internaltestSimpleTransferPull(true);
}
public void internaltestSimpleTransferPull(boolean security) throws Exception {
server.setIdentity("targetServer");
if (security) {
enableSecurity(server, "#");
}
server.start();
server.addAddressInfo(new AddressInfo(SimpleString.toSimpleString("TEST"), RoutingType.ANYCAST));
server.createQueue(new QueueConfiguration("TEST").setRoutingType(RoutingType.ANYCAST));
server_2 = createServer(AMQP_PORT_2, false);
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT).setRetryInterval(10);
if (security) {
// we first do it with a wrong password. retries in place should be in place until we make it right
amqpConnection.setUser(fullUser).setPassword("wrongPassword");
}
amqpConnection.addElement(new AMQPBrokerConnectionElement().setMatchAddress("TEST").setType(AMQPBrokerConnectionAddressType.RECEIVER));
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server_2.getConfiguration().addAddressConfiguration(new CoreAddressConfiguration().setName("TEST").addRoutingType(RoutingType.ANYCAST));
server_2.getConfiguration().addQueueConfiguration(new QueueConfiguration("TEST").setRoutingType(RoutingType.ANYCAST));
server_2.setIdentity("serverWithBridge");
server_2.start();
Wait.assertTrue(server_2::isStarted);
ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT);
Connection connection = factory.createConnection(fullUser, fullPass);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(session.createQueue("TEST"));
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
String largeMessageBody = null;
for (int i = 0; i < 30; i++) {
if (i == 0) {
StringBuffer buffer = new StringBuffer();
for (int s = 0; s < 10024; s++) {
buffer.append("*******************************************************************************************************************************");
}
largeMessageBody = buffer.toString();
TextMessage message = session.createTextMessage(buffer.toString());
producer.send(message);
} else {
producer.send(session.createMessage());
}
}
ConnectionFactory factory2 = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT_2);
Connection connection2 = factory2.createConnection();
Session session2 = connection2.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection2.start();
MessageConsumer consumer = session2.createConsumer(session2.createQueue("TEST"));
if (security) {
Thread.sleep(500); // on this case we need to wait some time to make sure retries are kicking in.
// since the password is wrong, this should return null.
Assert.assertNull(consumer.receiveNoWait());
// we are fixing the password, hoping the connection will fix itself.
amqpConnection.setUser(fullUser).setPassword(fullPass);
}
for (int i = 0; i < 30; i++) {
Message message = consumer.receive(5000);
if (message instanceof TextMessage) {
if (message instanceof TextMessage) {
Assert.assertEquals(largeMessageBody, ((TextMessage)message).getText());
} else {
System.out.println("i = " + i);
}
}
}
Assert.assertNull(consumer.receiveNoWait());
}
}

View File

@ -0,0 +1,683 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.tests.integration.amqp.connect;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.logs.AssertionLoggerHandler;
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
import org.apache.activemq.artemis.tests.util.CFUtil;
import org.apache.activemq.artemis.utils.Wait;
import org.apache.activemq.artemis.utils.collections.LinkedListIterator;
import org.apache.activemq.transport.amqp.client.AmqpClient;
import org.apache.activemq.transport.amqp.client.AmqpConnection;
import org.apache.activemq.transport.amqp.client.AmqpMessage;
import org.apache.activemq.transport.amqp.client.AmqpReceiver;
import org.apache.activemq.transport.amqp.client.AmqpSender;
import org.apache.activemq.transport.amqp.client.AmqpSession;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class AMQPReplicaTest extends AmqpClientTestSupport {
protected static final int AMQP_PORT_2 = 5673;
protected static final int AMQP_PORT_3 = 5674;
public static final int TIME_BEFORE_RESTART = 1000;
ActiveMQServer server_2;
@Before
public void startLogging() {
AssertionLoggerHandler.startCapture();
}
@After
public void stopLogging() {
try {
Assert.assertFalse(AssertionLoggerHandler.findText("AMQ222214"));
} finally {
AssertionLoggerHandler.stopCapture();
}
}
@Override
protected ActiveMQServer createServer() throws Exception {
return createServer(AMQP_PORT, false);
}
@Test
public void testReplicaCatchupOnQueueCreates() throws Exception {
server.setIdentity("Server1");
server.stop();
server_2 = createServer(AMQP_PORT_2, false);
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPMirrorBrokerConnectionElement());
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server_2.start();
server_2.addAddressInfo(new AddressInfo("sometest").setAutoCreated(false));
server_2.createQueue(new QueueConfiguration("sometest").setDurable(true));
Wait.assertTrue(() -> server_2.locateQueue("sometest") != null);
server_2.stop();
server.start();
Assert.assertTrue(server.locateQueue("sometest") == null);
Wait.assertTrue(server::isActive);
server_2.start();
// if this does not succeed the catch up did not arrive at the other server
Wait.assertTrue(() -> server.locateQueue("sometest") != null);
server_2.stop();
server.stop();
}
@Test
public void testReplicaCatchupOnQueueCreatesAndDeletes() throws Exception {
server.start();
server.setIdentity("Server1");
server.addAddressInfo(new AddressInfo("sometest").setAutoCreated(false).addRoutingType(RoutingType.MULTICAST));
// This queue will disappear from the source, so it should go
server.createQueue(new QueueConfiguration("ToBeGone").setDurable(true).setRoutingType(RoutingType.MULTICAST));
server.stop();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPMirrorBrokerConnectionElement());
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server_2.start();
server_2.addAddressInfo(new AddressInfo("sometest").setAutoCreated(false).addRoutingType(RoutingType.MULTICAST));
server_2.createQueue(new QueueConfiguration("sometest").setDurable(true).setRoutingType(RoutingType.MULTICAST));
Wait.assertTrue(() -> server_2.locateQueue("sometest") != null);
server_2.stop();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPMirrorBrokerConnectionElement());
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server.start();
Assert.assertTrue(server.locateQueue("sometest") == null);
Assert.assertTrue(server.locateQueue("ToBeGone") != null);
Wait.assertTrue(server::isActive);
server_2.start();
// if this does not succeed the catch up did not arrive at the other server
Wait.assertTrue(() -> server.locateQueue("sometest") != null);
Wait.assertTrue(() -> server.locateQueue("ToBeGone") == null);
server_2.stop();
server.stop();
}
@Test
public void testReplicaWithDurable() throws Exception {
server.start();
server.setIdentity("Server1");
server.addAddressInfo(new AddressInfo("sometest").setAutoCreated(false).addRoutingType(RoutingType.MULTICAST));
// This queue will disappear from the source, so it should go
server.createQueue(new QueueConfiguration("ToBeGone").setDurable(true).setRoutingType(RoutingType.MULTICAST));
server.stop();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPMirrorBrokerConnectionElement());
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server_2.start();
server_2.addAddressInfo(new AddressInfo("sometest").setAutoCreated(false).addRoutingType(RoutingType.MULTICAST));
server_2.createQueue(new QueueConfiguration("sometest").setDurable(true).setRoutingType(RoutingType.MULTICAST));
Wait.assertTrue(() -> server_2.locateQueue("sometest") != null);
server_2.stop();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
amqpConnection.addElement(new AMQPMirrorBrokerConnectionElement());
server_2.getConfiguration().addAMQPConnection(amqpConnection);
server.start();
Assert.assertTrue(server.locateQueue("sometest") == null);
Assert.assertTrue(server.locateQueue("ToBeGone") != null);
Wait.assertTrue(server::isActive);
server_2.start();
// if this does not succeed the catch up did not arrive at the other server
Wait.assertTrue(() -> server.locateQueue("sometest") != null);
Wait.assertTrue(() -> server.locateQueue("ToBeGone") == null);
server_2.stop();
server.stop();
}
@Test
public void testReplicaLargeMessages() throws Exception {
replicaTest(true, true, false, false, false, false);
}
@Test
public void testReplicaLargeMessagesPagingEverywhere() throws Exception {
replicaTest(true, true, true, true, false, false);
}
@Test
public void testReplica() throws Exception {
replicaTest(false, true, false, false, false, false);
}
@Test
public void testReplicaRestart() throws Exception {
replicaTest(false, true, false, false, false, true);
}
@Test
public void testReplicaDeferredStart() throws Exception {
replicaTest(false, true, false, false, true, false);
}
@Test
public void testReplicaCopyOnly() throws Exception {
replicaTest(false, false, false, false, false, false);
}
@Test
public void testReplicaPagedTarget() throws Exception {
replicaTest(false, true, true, false, false, false);
}
@Test
public void testReplicaPagingEverywhere() throws Exception {
replicaTest(false, true, true, true, false, false);
}
private String getText(boolean large, int i) {
if (!large) {
return "Text " + i;
} else {
StringBuffer buffer = new StringBuffer();
while (buffer.length() < 110 * 1024) {
buffer.append("Text " + i + " ");
}
return buffer.toString();
}
}
/**
* This test is validating that annotations sent to the original broker are not translated to the receiving side.
* Also annotations could eventually damage the body if the broker did not take that into consideration.
* So, this test is sending delivery annotations on messages.
* @throws Exception
*/
@Test
public void testLargeMessagesWithDeliveryAnnotations() throws Exception {
server.setIdentity("targetServer");
server.start();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT).setReconnectAttempts(-1).setRetryInterval(100);
AMQPMirrorBrokerConnectionElement replica = new AMQPMirrorBrokerConnectionElement().setMessageAcknowledgements(true);
amqpConnection.addElement(replica);
server_2.getConfiguration().addAMQPConnection(amqpConnection);
int NUMBER_OF_MESSAGES = 20;
server_2.start();
Wait.assertTrue(server_2::isStarted);
// We create the address to avoid auto delete on the queue
server_2.addAddressInfo(new AddressInfo(getQueueName()).addRoutingType(RoutingType.ANYCAST).setAutoCreated(false));
server_2.createQueue(new QueueConfiguration(getQueueName()).setRoutingType(RoutingType.ANYCAST).setAddress(getQueueName()).setAutoCreated(false));
Assert.assertFalse(AssertionLoggerHandler.findText("AMQ222214"));
// Get the Queue View early to avoid racing the delivery.
final Queue queueView = locateQueue(server_2, getQueueName());
final Queue queueViewReplica = locateQueue(server_2, getQueueName());
{ // sender
AmqpClient client = new AmqpClient(new URI("tcp://localhost:" + AMQP_PORT_2), null, null);
AmqpConnection connection = addConnection(client.connect());
AmqpSession session = connection.createSession();
AmqpSender sender = session.createSender(getQueueName());
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
AmqpMessage message = new AmqpMessage();
message.setDeliveryAnnotation("gone", "test");
message.setText(getText(true, i));
sender.send(message);
}
sender.close();
connection.close();
}
Wait.assertEquals(NUMBER_OF_MESSAGES, queueView::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES, queueViewReplica::getMessageCount);
{ // receiver on replica
AmqpClient client = new AmqpClient(new URI("tcp://localhost:" + AMQP_PORT), null, null);
AmqpConnection connection = addConnection(client.connect());
AmqpSession session = connection.createSession();
// Now try and get the message
AmqpReceiver receiver = session.createReceiver(getQueueName());
receiver.flow(NUMBER_OF_MESSAGES + 1);
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
AmqpMessage received = receiver.receive(5, TimeUnit.SECONDS);
assertNotNull(received);
Assert.assertEquals(getText(true, i), received.getText());
Assert.assertNull(received.getDeliveryAnnotation("gone"));
}
Assert.assertNull(receiver.receiveNoWait());
connection.close();
}
}
/** This is setting delivery annotations and sending messages with no address.
* The broker should know how to deal with the annotations and no address on the message. */
@Test
public void testNoAddressWithAnnotations() throws Exception {
server.setIdentity("targetServer");
server.start();
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT).setReconnectAttempts(-1).setRetryInterval(100);
AMQPMirrorBrokerConnectionElement replica = new AMQPMirrorBrokerConnectionElement().setMessageAcknowledgements(true);
amqpConnection.addElement(replica);
server_2.getConfiguration().addAMQPConnection(amqpConnection);
int NUMBER_OF_MESSAGES = 20;
server_2.start();
Wait.assertTrue(server_2::isStarted);
// We create the address to avoid auto delete on the queue
server_2.addAddressInfo(new AddressInfo(getQueueName()).addRoutingType(RoutingType.ANYCAST).setAutoCreated(false));
server_2.createQueue(new QueueConfiguration(getQueueName()).setRoutingType(RoutingType.ANYCAST).setAddress(getQueueName()).setAutoCreated(false));
Assert.assertFalse(AssertionLoggerHandler.findText("AMQ222214"));
{ // sender
AmqpClient client = new AmqpClient(new URI("tcp://localhost:" + AMQP_PORT_2), null, null);
AmqpConnection connection = addConnection(client.connect());
AmqpSession session = connection.createSession();
AmqpSender sender = session.createSender(getQueueName());
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
AmqpMessage message = new AmqpMessage();
message.setDeliveryAnnotation("gone", "test");
message.setText(getText(false, i));
sender.send(message);
}
sender.close();
connection.close();
}
{ // receiver on replica
AmqpClient client = new AmqpClient(new URI("tcp://localhost:" + AMQP_PORT), null, null);
AmqpConnection connection = addConnection(client.connect());
AmqpSession session = connection.createSession();
// Now try and get the message
AmqpReceiver receiver = session.createReceiver(getQueueName());
receiver.flow(NUMBER_OF_MESSAGES + 1);
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
AmqpMessage received = receiver.receive(5, TimeUnit.SECONDS);
assertNotNull(received);
Assert.assertEquals(getText(false, i), received.getText());
Assert.assertNull(received.getDeliveryAnnotation("gone"));
}
Assert.assertNull(receiver.receiveNoWait());
connection.close();
}
}
private void replicaTest(boolean largeMessage,
boolean acks,
boolean pagingTarget,
boolean pagingSource,
boolean deferredStart,
boolean restartAndDisconnect) throws Exception {
server.setIdentity("targetServer");
if (deferredStart) {
server.stop();
} else {
server.start();
}
server_2 = createServer(AMQP_PORT_2, false);
server_2.setIdentity("server_2");
server_2.getConfiguration().setName("thisone");
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT).setReconnectAttempts(-1).setRetryInterval(100);
AMQPMirrorBrokerConnectionElement replica = new AMQPMirrorBrokerConnectionElement().setMessageAcknowledgements(acks);
amqpConnection.addElement(replica);
server_2.getConfiguration().addAMQPConnection(amqpConnection);
int NUMBER_OF_MESSAGES = 200;
server_2.start();
Wait.assertTrue(server_2::isStarted);
// We create the address to avoid auto delete on the queue
server_2.addAddressInfo(new AddressInfo(getQueueName()).addRoutingType(RoutingType.ANYCAST).setAutoCreated(false));
server_2.createQueue(new QueueConfiguration(getQueueName()).setRoutingType(RoutingType.ANYCAST).setAddress(getQueueName()).setAutoCreated(false));
ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT_2);
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(session.createQueue(getQueueName()));
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
if (!deferredStart) {
Queue queueOnServer1 = locateQueue(server, getQueueName());
if (pagingTarget) {
queueOnServer1.getPagingStore().startPaging();
}
}
if (pagingSource) {
Queue queueOnServer2 = server_2.locateQueue(getQueueName());
queueOnServer2.getPagingStore().startPaging();
}
Assert.assertFalse(AssertionLoggerHandler.findText("AMQ222214"));
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
Message message = session.createTextMessage(getText(largeMessage, i));
message.setIntProperty("i", i);
producer.send(message);
}
Assert.assertFalse(AssertionLoggerHandler.findText("AMQ222214"));
Queue queueOnServer1;
if (deferredStart) {
Thread.sleep(TIME_BEFORE_RESTART);
server.start();
Wait.assertTrue(server::isActive);
queueOnServer1 = locateQueue(server, getQueueName());
if (pagingTarget) {
queueOnServer1.getPagingStore().startPaging();
}
} else {
queueOnServer1 = locateQueue(server, getQueueName());
}
Queue snfreplica = server_2.locateQueue(replica.getSourceMirrorAddress());
Assert.assertNotNull(snfreplica);
Wait.assertEquals(0, snfreplica::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES, queueOnServer1::getMessageCount);
Queue queueOnServer2 = locateQueue(server_2, getQueueName());
Wait.assertEquals(NUMBER_OF_MESSAGES, queueOnServer1::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES, queueOnServer2::getMessageCount);
if (pagingTarget) {
assertTrue(queueOnServer1.getPagingStore().isPaging());
}
if (acks) {
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES / 2 - 1, AMQP_PORT_2, false);
// Replica is async, so we need to wait acks to arrive before we finish consuming there
Wait.assertEquals(NUMBER_OF_MESSAGES / 2, queueOnServer1::getMessageCount);
// we consume on replica, as half the messages were acked
consumeMessages(largeMessage, NUMBER_OF_MESSAGES / 2, NUMBER_OF_MESSAGES - 1, AMQP_PORT, true); // We consume on both servers as this is currently replicated
consumeMessages(largeMessage, NUMBER_OF_MESSAGES / 2, NUMBER_OF_MESSAGES - 1, AMQP_PORT_2, false);
if (largeMessage) {
validateNoFilesOnLargeDir(server.getConfiguration().getLargeMessagesDirectory(), 0);
//validateNoFilesOnLargeDir(server_2.getConfiguration().getLargeMessagesDirectory(), 50); // we kept half of the messages
}
} else {
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES - 1, AMQP_PORT_2, true);
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES - 1, AMQP_PORT, true);
if (largeMessage) {
validateNoFilesOnLargeDir(server.getConfiguration().getLargeMessagesDirectory(), 0);
validateNoFilesOnLargeDir(server_2.getConfiguration().getLargeMessagesDirectory(), 0); // we kept half of the messages
}
}
if (restartAndDisconnect) {
server.stop();
Thread.sleep(TIME_BEFORE_RESTART);
server.start();
Wait.assertTrue(server::isActive);
consumeMessages(largeMessage, 0, -1, AMQP_PORT_2, true);
consumeMessages(largeMessage, 0, -1, AMQP_PORT, true);
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
Message message = session.createTextMessage(getText(largeMessage, i));
message.setIntProperty("i", i);
producer.send(message);
}
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES - 1, AMQP_PORT, true);
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES - 1, AMQP_PORT_2, true);
}
}
@Test
public void testDualStandard() throws Exception {
dualReplica(false, false, false);
}
@Test
public void testDualRegularPagedTargets() throws Exception {
dualReplica(false, false, true);
}
@Test
public void testDualRegularPagedEverything() throws Exception {
dualReplica(false, true, true);
}
@Test
public void testDualRegularLarge() throws Exception {
dualReplica(true, false, false);
}
public Queue locateQueue(ActiveMQServer server, String queueName) throws Exception {
Wait.waitFor(() -> server.locateQueue(queueName) != null);
return server.locateQueue(queueName);
}
private void dualReplica(boolean largeMessage, boolean pagingSource, boolean pagingTarget) throws Exception {
server.setIdentity("server_1");
server.start();
ActiveMQServer server_3 = createServer(AMQP_PORT_3, false);
server_3.setIdentity("server_3");
server_3.start();
Wait.assertTrue(server_3::isStarted);
ConnectionFactory factory_3 = CFUtil.createConnectionFactory("amqp", "tcp://localhost:" + AMQP_PORT_3);
factory_3.createConnection().close();
server_2 = createServer(AMQP_PORT_2, false);
AMQPBrokerConnectConfiguration amqpConnection1 = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:" + AMQP_PORT);
AMQPMirrorBrokerConnectionElement replica1 = new AMQPMirrorBrokerConnectionElement().setType(AMQPBrokerConnectionAddressType.MIRROR);
amqpConnection1.addElement(replica1);
server_2.getConfiguration().addAMQPConnection(amqpConnection1);
AMQPBrokerConnectConfiguration amqpConnection3 = new AMQPBrokerConnectConfiguration("test2", "tcp://localhost:" + AMQP_PORT_3);
AMQPMirrorBrokerConnectionElement replica2 = new AMQPMirrorBrokerConnectionElement().setType(AMQPBrokerConnectionAddressType.MIRROR);
amqpConnection3.addElement(replica2);
server_2.getConfiguration().addAMQPConnection(amqpConnection3);
int NUMBER_OF_MESSAGES = 200;
server_2.start();
Wait.assertTrue(server_2::isStarted);
ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + AMQP_PORT_2);
Connection connection = factory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(session.createQueue(getQueueName()));
producer.setDeliveryMode(DeliveryMode.PERSISTENT);
Queue queue_server_2 = locateQueue(server_2, getQueueName());
Queue queue_server_1 = locateQueue(server, getQueueName());
Queue queue_server_3 = locateQueue(server_3, getQueueName());
if (pagingSource) {
queue_server_2.getPagingStore().startPaging();
}
if (pagingTarget) {
queue_server_1.getPagingStore().startPaging();
queue_server_3.getPagingStore().startPaging();
}
for (int i = 0; i < NUMBER_OF_MESSAGES; i++) {
Message message = session.createTextMessage(getText(largeMessage, i));
message.setIntProperty("i", i);
producer.send(message);
}
Wait.assertEquals(NUMBER_OF_MESSAGES, queue_server_2::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES, queue_server_3::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES, queue_server_1::getMessageCount);
Queue replica1Queue = server_2.locateQueue(replica1.getSourceMirrorAddress());
Queue replica2Queue = server_2.locateQueue(replica2.getSourceMirrorAddress());
Wait.assertEquals(0L, replica2Queue.getPagingStore()::getAddressSize, 1000, 100);
Wait.assertEquals(0L, replica1Queue.getPagingStore()::getAddressSize, 1000, 100);
if (pagingTarget) {
Assert.assertTrue(queue_server_1.getPagingStore().isPaging());
Assert.assertTrue(queue_server_3.getPagingStore().isPaging());
}
if (pagingSource) {
Assert.assertTrue(queue_server_2.getPagingStore().isPaging());
}
consumeMessages(largeMessage, 0, NUMBER_OF_MESSAGES / 2 - 1, AMQP_PORT_2, false);
Wait.assertEquals(NUMBER_OF_MESSAGES / 2, queue_server_1::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES / 2, queue_server_2::getMessageCount);
Wait.assertEquals(NUMBER_OF_MESSAGES / 2, queue_server_3::getMessageCount);
// Replica is async, so we need to wait acks to arrive before we finish consuming there
Wait.assertEquals(NUMBER_OF_MESSAGES / 2, queue_server_1::getMessageCount);
consumeMessages(largeMessage, NUMBER_OF_MESSAGES / 2, NUMBER_OF_MESSAGES - 1, AMQP_PORT, true); // We consume on both servers as this is currently replicated
consumeMessages(largeMessage, NUMBER_OF_MESSAGES / 2, NUMBER_OF_MESSAGES - 1, AMQP_PORT_3, true); // We consume on both servers as this is currently replicated
consumeMessages(largeMessage, NUMBER_OF_MESSAGES / 2, NUMBER_OF_MESSAGES - 1, AMQP_PORT_2, true); // We consume on both servers as this is currently replicated
validateNoFilesOnLargeDir(server.getConfiguration().getLargeMessagesDirectory(), 0);
validateNoFilesOnLargeDir(server_3.getConfiguration().getLargeMessagesDirectory(), 0);
validateNoFilesOnLargeDir(server_2.getConfiguration().getLargeMessagesDirectory(), 0);
}
/**
* this might be helpful for debugging
*/
private void printMessages(String printInfo, Queue queue) {
System.out.println("*******************************************************************************************************************************");
System.out.println(printInfo);
System.out.println();
LinkedListIterator<MessageReference> referencesIterator = queue.browserIterator();
while (referencesIterator.hasNext()) {
System.out.println("message " + referencesIterator.next().getMessage());
}
referencesIterator.close();
System.out.println("*******************************************************************************************************************************");
}
private void consumeMessages(boolean largeMessage,
int START_ID,
int LAST_ID,
int port,
boolean assertNull) throws JMSException {
ConnectionFactory cf = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:" + port);
Connection conn = cf.createConnection();
Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
conn.start();
MessageConsumer consumer = sess.createConsumer(sess.createQueue(getQueueName()));
for (int i = START_ID; i <= LAST_ID; i++) {
Message message = consumer.receive(3000);
Assert.assertNotNull(message);
System.out.println("port " + port + ",i::" + message.getIntProperty("i"));
Assert.assertEquals(i, message.getIntProperty("i"));
if (message instanceof TextMessage) {
Assert.assertEquals(getText(largeMessage, i), ((TextMessage) message).getText());
}
}
if (assertNull) {
Assert.assertNull(consumer.receiveNoWait());
}
conn.close();
}
}

View File

@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.activemq.artemis.tests.integration.amqp.connect;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import java.net.URI;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.Configuration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServers;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorControllerSource;
import org.apache.activemq.artemis.protocol.amqp.connect.mirror.AMQPMirrorMessageFactory;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.tests.util.CFUtil;
import org.apache.activemq.transport.amqp.client.AmqpClient;
import org.apache.activemq.transport.amqp.client.AmqpConnection;
import org.apache.activemq.transport.amqp.client.AmqpMessage;
import org.apache.activemq.transport.amqp.client.AmqpReceiver;
import org.apache.activemq.transport.amqp.client.AmqpSession;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.junit.Assert;
import org.junit.Test;
public class MirrorControllerBasicTest extends ActiveMQTestBase {
ActiveMQServer server;
@Override
public void setUp() throws Exception {
super.setUp();
Configuration configuration = createDefaultNettyConfig();
server = addServer(ActiveMQServers.newActiveMQServer(configuration, true));
// start the server
server.start();
}
@Test
public void testSend() throws Exception {
ConnectionFactory factory = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:61616");
Connection connection = factory.createConnection();
Session session = connection.createSession(Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue("myQueue");
MessageConsumer consumer = session.createConsumer(queue);
connection.start();
MessageProducer producer = session.createProducer(queue);
for (int i = 0; i < 10; i++) {
producer.send(session.createTextMessage("hello"));
}
for (int i = 0; i < 10; i++) {
Assert.assertNotNull(consumer.receive(1000));
}
connection.close();
}
/** this test will take the Message generated from mirror controller and send it through PostOffice
* to validate the format of the message and its delivery */
@Test
public void testDirectSend() throws Exception {
server.addAddressInfo(new AddressInfo("test").addRoutingType(RoutingType.ANYCAST));
server.createQueue(new QueueConfiguration("test").setAddress("test").setRoutingType(RoutingType.ANYCAST));
Message message = AMQPMirrorMessageFactory.createMessage("test", SimpleString.toSimpleString("ad1"), SimpleString.toSimpleString("qu1"), "test", "body-test");
AMQPMirrorControllerSource.route(server, message);
AmqpClient client = new AmqpClient(new URI("tcp://localhost:61616"), null, null);
AmqpConnection connection = client.connect();
AmqpSession session = connection.createSession();
AmqpReceiver receiver = session.createReceiver("test");
receiver.flow(1);
AmqpMessage amqpMessage = receiver.receive(5, TimeUnit.SECONDS);
AmqpValue value = (AmqpValue)amqpMessage.getWrappedMessage().getBody();
Assert.assertEquals("body-test", (String)value.getValue());
Assert.assertEquals("ad1",amqpMessage.getMessageAnnotation(AMQPMirrorControllerSource.ADDRESS.toString()));
Assert.assertEquals("qu1", amqpMessage.getMessageAnnotation(AMQPMirrorControllerSource.QUEUE.toString()));
Assert.assertEquals("test", amqpMessage.getMessageAnnotation(AMQPMirrorControllerSource.EVENT_TYPE.toString()));
connection.close();
}
}

View File

@ -0,0 +1,183 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.apache.activemq.artemis.tests.integration.amqp.connect;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.DeliveryMode;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import java.net.URL;
import org.apache.activemq.artemis.api.core.QueueConfiguration;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionElement;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.tests.integration.amqp.AmqpClientTestSupport;
import org.apache.activemq.artemis.tests.util.CFUtil;
import org.apache.activemq.artemis.tests.util.Wait;
import org.apache.activemq.artemis.utils.ExecuteUtil;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/** This test will only be executed if you have qdrouterd available on your system, otherwise is ignored by an assume exception. */
public class QpidDispatchPeerTest extends AmqpClientTestSupport {
ExecuteUtil.ProcessHolder qpidProcess;
/**
* This will validate if the environemnt has qdrouterd installed and if this test can be used or not.
*/
@BeforeClass
public static void validateqdrotuer() {
try {
int result = ExecuteUtil.runCommand(true, "qdrouterd", "--version");
Assume.assumeTrue("qdrouterd does not exist", result == 0);
} catch (Exception e) {
e.printStackTrace();
Assume.assumeNoException(e);
}
}
@Override
protected ActiveMQServer createServer() throws Exception {
return createServer(AMQP_PORT, false);
}
@Before
public void startQpidRouter() throws Exception {
URL qpidConfig = this.getClass().getClassLoader().getResource("QpidRouterPeerTest-qpidr.conf");
qpidProcess = ExecuteUtil.run(true, "qdrouterd", "-c", qpidConfig.getFile());
}
@After
public void stopQpidRouter() throws Exception {
qpidProcess.kill();
}
@Test(timeout = 60_000)
public void testWithMatching() throws Exception {
internalMultipleQueues(true);
}
@Test(timeout = 60_000)
public void testwithQueueName() throws Exception {
internalMultipleQueues(false);
}
private void internalMultipleQueues(boolean useMatching) throws Exception {
final int numberOfMessages = 100;
final int numberOfQueues = 10;
AMQPBrokerConnectConfiguration amqpConnection = new AMQPBrokerConnectConfiguration("test", "tcp://localhost:24621").setRetryInterval(10).setReconnectAttempts(-1);
if (useMatching) {
amqpConnection.addElement(new AMQPBrokerConnectionElement().setMatchAddress("queue.#").setType(AMQPBrokerConnectionAddressType.PEER));
} else {
for (int i = 0; i < numberOfQueues; i++) {
amqpConnection.addElement(new AMQPBrokerConnectionElement().setQueueName("queue.test" + i).setType(AMQPBrokerConnectionAddressType.PEER));
}
}
server.getConfiguration().addAMQPConnection(amqpConnection);
server.start();
for (int i = 0; i < numberOfQueues; i++) {
server.addAddressInfo(new AddressInfo("queue.test" + i).addRoutingType(RoutingType.ANYCAST).setAutoCreated(false).setTemporary(false));
server.createQueue(new QueueConfiguration("queue.test" + i).setAddress("queue.test" + i).setRoutingType(RoutingType.ANYCAST));
}
for (int dest = 0; dest < numberOfQueues; dest++) {
ConnectionFactory factoryProducer = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:24622");
Connection connection = null;
connection = createConnectionDumbRetry(factoryProducer, connection);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queue = session.createQueue("queue.test" + dest);
MessageProducer producer = session.createProducer(queue);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
org.apache.activemq.artemis.core.server.Queue testQueueOnServer = server.locateQueue("queue.test" + dest);
for (int i = 0; i < numberOfMessages; i++) {
producer.send(session.createTextMessage("hello " + i));
}
Wait.assertEquals(numberOfMessages, testQueueOnServer::getMessageCount);
connection.close();
}
System.out.println("*******************************************************************************************************************************");
System.out.println("Creating consumer");
for (int dest = 0; dest < numberOfQueues; dest++) {
ConnectionFactory factoryConsumer = CFUtil.createConnectionFactory("AMQP", "tcp://localhost:24622");
Connection connectionConsumer = factoryConsumer.createConnection();
Session sessionConsumer = connectionConsumer.createSession(false, Session.AUTO_ACKNOWLEDGE);
Queue queueConsumer = sessionConsumer.createQueue("queue.test" + dest);
MessageConsumer consumer = sessionConsumer.createConsumer(queueConsumer);
connectionConsumer.start();
try {
for (int i = 0; i < numberOfMessages; i++) {
TextMessage received = (TextMessage) consumer.receive(5000);
if (received == null) {
System.out.println("*******************************************************************************************************************************");
System.out.println("qdstat after message timed out:");
ExecuteUtil.runCommand(true, "qdstat", "-b", "127.0.0.1:24622", "-l");
System.out.println("*******************************************************************************************************************************");
}
Assert.assertNotNull(received);
System.out.println("message " + received.getText());
Assert.assertEquals("hello " + i, received.getText());
}
Assert.assertNull(consumer.receiveNoWait());
} finally {
try {
connectionConsumer.close();
} catch (Throwable ignored) {
}
}
org.apache.activemq.artemis.core.server.Queue testQueueOnServer = server.locateQueue("queue.test" + dest);
Wait.assertEquals(0, testQueueOnServer::getMessageCount);
}
}
private Connection createConnectionDumbRetry(ConnectionFactory factoryProducer,
Connection connection) throws InterruptedException {
for (int i = 0; i < 100; i++) {
try {
// Some retry
connection = factoryProducer.createConnection();
break;
} catch (Exception e) {
Thread.sleep(10);
}
}
return connection;
}
}

View File

@ -17,6 +17,7 @@
package org.apache.activemq.artemis.tests.integration.cli;
import java.util.List;
import java.util.function.Function;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
@ -133,9 +134,9 @@ public class DummyServerConsumer implements ServerConsumer {
}
@Override
public List<MessageReference> getDeliveringReferencesBasedOnProtocol(boolean remove,
Object protocolDataStart,
Object protocolDataEnd) {
public List<MessageReference> scanDeliveringReferences(boolean remove,
Function<MessageReference, Boolean> startFunction,
Function<MessageReference, Boolean> endFunction) {
return null;
}

View File

@ -69,6 +69,7 @@ import org.apache.activemq.artemis.core.remoting.impl.invm.TransportConstants;
import org.apache.activemq.artemis.core.security.Role;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.ActiveMQServers;
import org.apache.activemq.artemis.core.server.BrokerConnection;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.ServerSession;
@ -3908,6 +3909,57 @@ public class ActiveMQServerControlTest extends ManagementTestBase {
}
@Test
public void testBrokerConnections() throws Exception {
class Fake implements BrokerConnection {
String name;
boolean started = false;
Fake(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String getProtocol() {
return "fake";
}
@Override
public void start() throws Exception {
started = true;
}
@Override
public void stop() throws Exception {
started = false;
}
@Override
public boolean isStarted() {
return started;
}
}
Fake fake = new Fake("fake" + UUIDGenerator.getInstance().generateStringUUID());
server.registerBrokerConnection(fake);
ActiveMQServerControl serverControl = createManagementControl();
try {
String result = serverControl.listBrokerConnections();
Assert.assertTrue(result.contains(fake.getName()));
serverControl.startBrokerConnection(fake.getName());
Assert.assertTrue(fake.isStarted());
serverControl.stopBrokerConnection(fake.getName());
Assert.assertFalse(fake.isStarted());
} catch (Exception expected) {
}
}
protected void scaleDown(ScaleDownHandler handler) throws Exception {
SimpleString address = new SimpleString("testQueue");
HashMap<String, Object> params = new HashMap<>();

View File

@ -58,6 +58,25 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
@Override
protected ActiveMQServerControl createManagementControl() throws Exception {
return new ActiveMQServerControl() {
@Override
public String listBrokerConnections() {
try {
return (String) proxy.invokeOperation("listBrokerConnections");
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
}
@Override
public void startBrokerConnection(String name) throws Exception {
proxy.invokeOperation("startBrokerConnection", name);
}
@Override
public void stopBrokerConnection(String name) throws Exception {
proxy.invokeOperation("stopBrokerConnection", name);
}
@Override
public String updateAddress(String name, String routingTypes) throws Exception {

View File

@ -0,0 +1,51 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
router {
mode: standalone
id: INT.A
}
#log {
#module: DEFAULT
#enable: trace+
#outputFile: /tmp/qdrouterd.log
#}
# The broker connects into this port
listener {
saslMechanisms: ANONYMOUS
host: 0.0.0.0
role: route-container
linkCapacity: 1123
authenticatePeer: no
port: 24621
}
# Clients connect to this port
listener {
saslMechanisms: ANONYMOUS
host: 0.0.0.0
linkCapacity: 555
role: normal
authenticatePeer: no
port: 24622
}
address {
prefix: queue
waypoint: yes
}

View File

@ -16,7 +16,10 @@
*/
package org.apache.activemq.artemis.tests.unit.core.config.impl;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectConfiguration;
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPBrokerConnectionAddressType;
import org.apache.activemq.artemis.core.config.amqpBrokerConnectivity.AMQPMirrorBrokerConnectionElement;
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
import org.apache.activemq.artemis.utils.XMLUtil;
@ -65,6 +68,55 @@ public class ConfigurationValidationTest extends ActiveMQTestBase {
Assert.assertEquals(true, fc.isPersistDeliveryCountBeforeDelivery());
}
@Test
public void testAMQPConnectParsing() throws Exception {
FileConfiguration fc = new FileConfiguration();
FileDeploymentManager deploymentManager = new FileDeploymentManager("ConfigurationTest-full-config.xml");
deploymentManager.addDeployable(fc);
deploymentManager.readConfiguration();
Assert.assertEquals(3, fc.getAMQPConnection().size());
AMQPBrokerConnectConfiguration amqpBrokerConnectConfiguration = fc.getAMQPConnection().get(0);
Assert.assertEquals("testuser", amqpBrokerConnectConfiguration.getUser());
Assert.assertEquals("testpassword", amqpBrokerConnectConfiguration.getPassword());
Assert.assertEquals(33, amqpBrokerConnectConfiguration.getReconnectAttempts());
Assert.assertEquals(333, amqpBrokerConnectConfiguration.getRetryInterval());
Assert.assertEquals("test1", amqpBrokerConnectConfiguration.getName());
Assert.assertEquals("tcp://test1:111", amqpBrokerConnectConfiguration.getUri());
Assert.assertEquals("TEST-SENDER", amqpBrokerConnectConfiguration.getConnectionElements().get(0).getMatchAddress().toString());
Assert.assertEquals(AMQPBrokerConnectionAddressType.SENDER, amqpBrokerConnectConfiguration.getConnectionElements().get(0).getType());
Assert.assertEquals("TEST-RECEIVER", amqpBrokerConnectConfiguration.getConnectionElements().get(1).getMatchAddress().toString());
Assert.assertEquals(AMQPBrokerConnectionAddressType.RECEIVER, amqpBrokerConnectConfiguration.getConnectionElements().get(1).getType());
Assert.assertEquals("TEST-PEER", amqpBrokerConnectConfiguration.getConnectionElements().get(2).getMatchAddress().toString());
Assert.assertEquals(AMQPBrokerConnectionAddressType.PEER, amqpBrokerConnectConfiguration.getConnectionElements().get(2).getType());
Assert.assertEquals("TEST-WITH-QUEUE-NAME", amqpBrokerConnectConfiguration.getConnectionElements().get(3).getQueueName().toString());
Assert.assertEquals(null, amqpBrokerConnectConfiguration.getConnectionElements().get(3).getMatchAddress());
Assert.assertEquals(AMQPBrokerConnectionAddressType.RECEIVER, amqpBrokerConnectConfiguration.getConnectionElements().get(3).getType());
Assert.assertEquals(AMQPBrokerConnectionAddressType.MIRROR, amqpBrokerConnectConfiguration.getConnectionElements().get(4).getType());
AMQPMirrorBrokerConnectionElement mirrorConnectionElement = (AMQPMirrorBrokerConnectionElement) amqpBrokerConnectConfiguration.getConnectionElements().get(4);
Assert.assertFalse(mirrorConnectionElement.isMessageAcknowledgements());
Assert.assertTrue(mirrorConnectionElement.isDurable()); // queue name passed, so this is supposed to be true
Assert.assertFalse(mirrorConnectionElement.isQueueCreation());
Assert.assertFalse(mirrorConnectionElement.isQueueRemoval());
Assert.assertEquals("TEST-REPLICA", mirrorConnectionElement.getSourceMirrorAddress().toString());
amqpBrokerConnectConfiguration = fc.getAMQPConnection().get(1);
Assert.assertEquals(null, amqpBrokerConnectConfiguration.getUser()); mirrorConnectionElement = (AMQPMirrorBrokerConnectionElement) amqpBrokerConnectConfiguration.getConnectionElements().get(0);
Assert.assertEquals(null, amqpBrokerConnectConfiguration.getPassword()); Assert.assertEquals("test2", amqpBrokerConnectConfiguration.getName());
Assert.assertEquals("tcp://test2:222", amqpBrokerConnectConfiguration.getUri());
Assert.assertTrue(mirrorConnectionElement.isMessageAcknowledgements());
Assert.assertFalse(mirrorConnectionElement.isDurable()); // queue name not passed (set as null), so this is supposed to be false
Assert.assertTrue(mirrorConnectionElement.isQueueCreation());
Assert.assertTrue(mirrorConnectionElement.isQueueRemoval());
amqpBrokerConnectConfiguration = fc.getAMQPConnection().get(2);
Assert.assertFalse(amqpBrokerConnectConfiguration.isAutostart());
}
@Test
public void testChangeConfiguration() throws Exception {
FileConfiguration fc = new FileConfiguration();

View File

@ -20,6 +20,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.ToLongFunction;
import org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration;
import org.apache.activemq.artemis.api.core.Message;
@ -136,6 +137,11 @@ public class FakeQueue extends CriticalComponentImpl implements Queue {
return false;
}
@Override
public MessageReference removeWithSuppliedID(long id, ToLongFunction<MessageReference> idSupplier) {
return null;
}
@Override
public void setExclusive(boolean value) {
// no-op

View File

@ -40,8 +40,10 @@ import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.cluster.impl.MessageLoadBalancingType;
import org.apache.activemq.artemis.core.server.impl.AckReason;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.MessageReferenceImpl;
import org.apache.activemq.artemis.core.server.mirror.MirrorController;
import org.apache.activemq.artemis.core.transaction.Transaction;
public class FakePostOffice implements PostOffice {
@ -140,6 +142,21 @@ public class FakePostOffice implements PostOffice {
return null;
}
@Override
public MirrorController getMirrorControlSource() {
return null;
}
@Override
public PostOffice setMirrorControlSource(MirrorController mirrorControllerSource) {
return null;
}
@Override
public void postAcknowledge(MessageReference ref, AckReason reason) {
}
@Override
public boolean addAddressInfo(AddressInfo addressInfo) {
return false;

View File

@ -18,6 +18,7 @@ package org.apache.activemq.artemis.tests.unit.util;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
@ -109,8 +110,9 @@ public class LinkedListTest extends ActiveMQTestBase {
private static final class ObservableNode extends LinkedListImpl.Node<ObservableNode> {
ObservableNode() {
public int id;
ObservableNode(int id) {
this.id = id;
}
public LinkedListImpl.Node<ObservableNode> publicNext() {
@ -130,7 +132,7 @@ public class LinkedListTest extends ActiveMQTestBase {
// Initial add
for (int i = 0; i < 100; i++) {
final ObservableNode o = new ObservableNode();
final ObservableNode o = new ObservableNode(i);
objs.addTail(o);
}
@ -139,7 +141,7 @@ public class LinkedListTest extends ActiveMQTestBase {
for (int i = 0; i < 500; i++) {
for (int add = 0; add < 1000; add++) {
final ObservableNode o = new ObservableNode();
final ObservableNode o = new ObservableNode(add);
objs.addTail(o);
assertNotNull("prev", o.publicPrev());
assertNull("next", o.publicNext());
@ -170,13 +172,77 @@ public class LinkedListTest extends ActiveMQTestBase {
}
@Test
public void testAddAndRemoveWithIDs() {
internalAddWithID(true);
}
@Test
public void testAddAndRemoveWithIDsDeferredSupplier() {
internalAddWithID(false);
}
private void internalAddWithID(boolean deferSupplier) {
LinkedListImpl<ObservableNode> objs = new LinkedListImpl<>();
if (!deferSupplier) {
objs.setIDSupplier(source -> source.id);
}
// Initial add
for (int i = 0; i < 1000; i++) {
final ObservableNode o = new ObservableNode(i);
objs.addTail(o);
}
Assert.assertEquals(1000, objs.size());
if (deferSupplier) {
Assert.assertEquals(0, objs.getSizeOfSuppliedIDs());
objs.setIDSupplier(source -> source.id);
}
Assert.assertEquals(1000, objs.getSizeOfSuppliedIDs());
/** remove all even items */
for (int i = 0; i < 1000; i += 2) {
objs.removeWithID(i);
}
Assert.assertEquals(500, objs.size());
Assert.assertEquals(500, objs.getSizeOfSuppliedIDs());
Iterator<ObservableNode> iterator = objs.iterator();
{
int i = 1;
while (iterator.hasNext()) {
ObservableNode value = iterator.next();
Assert.assertEquals(i, value.id);
i += 2;
}
}
for (int i = 1; i < 1000; i += 2) {
objs.removeWithID(i);
}
Assert.assertEquals(0, objs.getSizeOfSuppliedIDs());
Assert.assertEquals(0, objs.size());
}
@Test
public void testAddHeadAndRemove() {
LinkedListImpl<ObservableNode> objs = new LinkedListImpl<>();
// Initial add
for (int i = 0; i < 1001; i++) {
final ObservableNode o = new ObservableNode();
final ObservableNode o = new ObservableNode(i);
objs.addHead(o);
}
assertEquals(1001, objs.size());
@ -811,7 +877,7 @@ public class LinkedListTest extends ActiveMQTestBase {
final int count = 100;
final LinkedListImpl<ObservableNode> list = new LinkedListImpl<>();
for (int i = 0; i < count; i++) {
final ObservableNode node = new ObservableNode();
final ObservableNode node = new ObservableNode(i);
assertNull(node.publicPrev());
assertNull(node.publicNext());
list.addTail(node);
@ -834,7 +900,7 @@ public class LinkedListTest extends ActiveMQTestBase {
final ObservableNode[] nodes = new ObservableNode[count];
final LinkedListImpl<ObservableNode> list = new LinkedListImpl<>();
for (int i = 0; i < count; i++) {
final ObservableNode node = new ObservableNode();
final ObservableNode node = new ObservableNode(i);
assertNull(node.publicPrev());
assertNull(node.publicNext());
nodes[i] = node;