ARTEMIS-2937 Server Side AMQP Connectivity with options to transfer queues or replicate data
This commit is contained in:
parent
4d6096f88d
commit
8499eac76c
|
@ -35,3 +35,6 @@ docs/hacking-guide/en/_book
|
|||
|
||||
# overlay outpit
|
||||
**/overlays/**/*
|
||||
|
||||
# result of gitbook pdf under docs
|
||||
docs/user-manual/en/book.pdf
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 " +
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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() +
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -53,6 +53,10 @@ public class ActiveMQProtonRemotingConnection extends AbstractRemotingConnection
|
|||
transportConnection.setProtocolConnection(this);
|
||||
}
|
||||
|
||||
public AMQPConnectionContext getAmqpConnection() {
|
||||
return amqpConnection;
|
||||
}
|
||||
|
||||
public ProtonProtocolManager getManager() {
|
||||
return manager;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -24,7 +24,7 @@ public class ProtonInitializable {
|
|||
return initialized;
|
||||
}
|
||||
|
||||
public void initialise() throws Exception {
|
||||
public void initialize() throws Exception {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -54,7 +54,7 @@ public class ProtonServerSenderContextTest {
|
|||
when(mockSender.getRemoteSource()).thenReturn(source);
|
||||
|
||||
|
||||
sc.initialise();
|
||||
sc.initialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<>();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
*
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.apache.activemq.artemis.core.server;
|
||||
|
||||
@Deprecated
|
||||
public interface ConnectorService extends ActiveMQComponent {
|
||||
|
||||
String getName();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -564,6 +564,7 @@ public class LastValueQueue extends QueueImpl {
|
|||
|
||||
@Override
|
||||
public void setOwner(PagingStore owner) {
|
||||
|
||||
ref.setOwner(owner);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue