/*
* 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.cluster.impl;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.filter.Filter;
import org.apache.activemq.artemis.core.filter.impl.FilterImpl;
import org.apache.activemq.artemis.core.postoffice.BindingType;
import org.apache.activemq.artemis.core.server.Bindable;
import org.apache.activemq.artemis.core.server.Queue;
import org.apache.activemq.artemis.core.server.RoutingContext;
import org.apache.activemq.artemis.core.server.cluster.RemoteQueueBinding;
import org.jboss.logging.Logger;
public class RemoteQueueBindingImpl implements RemoteQueueBinding {
private static final Logger logger = Logger.getLogger(RemoteQueueBindingImpl.class);
private final SimpleString address;
private final Queue storeAndForwardQueue;
private final SimpleString uniqueName;
private final SimpleString routingName;
private final long remoteQueueID;
private final Filter queueFilter;
private final Set<Filter> filters = new HashSet<>();
private final Map<SimpleString, Integer> filterCounts = new HashMap<>();
private int consumerCount;
private final SimpleString idsHeaderName;
private final long id;
private final int distance;
private boolean connected = true;
public RemoteQueueBindingImpl(final long id,
final SimpleString address,
final SimpleString uniqueName,
final SimpleString routingName,
final Long remoteQueueID,
final SimpleString filterString,
final Queue storeAndForwardQueue,
final SimpleString bridgeName,
final int distance) throws Exception {
this.id = id;
this.address = address;
this.storeAndForwardQueue = storeAndForwardQueue;
this.uniqueName = uniqueName;
this.routingName = routingName;
this.remoteQueueID = remoteQueueID;
queueFilter = FilterImpl.createFilter(filterString);
idsHeaderName = Message.HDR_ROUTE_TO_IDS.concat(bridgeName);
this.distance = distance;
}
@Override
public long getID() {
return id;
}
@Override
public SimpleString getAddress() {
return address;
}
@Override
public Bindable getBindable() {
return storeAndForwardQueue;
}
@Override
public Queue getQueue() {
return storeAndForwardQueue;
}
@Override
public SimpleString getRoutingName() {
return routingName;
}
@Override
public SimpleString getUniqueName() {
return uniqueName;
}
@Override
public SimpleString getClusterName() {
return uniqueName;
}
@Override
public boolean isExclusive() {
return false;
}
@Override
public BindingType getType() {
return BindingType.REMOTE_QUEUE;
}
@Override
public Filter getFilter() {
return queueFilter;
}
@Override
public int getDistance() {
return distance;
}
@Override
public synchronized boolean isHighAcceptPriority(final Message message) {
if (consumerCount == 0) {
return false;
}
if (filters.isEmpty()) {
return true;
} else {
for (Filter filter : filters) {
if (filter.match(message)) {
return true;
}
}
}
return false;
}
@Override
public void unproposed(SimpleString groupID) {
}
@Override
public void route(final Message message, final RoutingContext context) {
addRouteContextToMessage(message);
List<Queue> durableQueuesOnContext = context.getDurableQueues(storeAndForwardQueue.getAddress());
if (!durableQueuesOnContext.contains(storeAndForwardQueue)) {
// There can be many remote bindings for the same node, we only want to add the message once to
// the s & f queue for that node
context.addQueue(storeAndForwardQueue.getAddress(), storeAndForwardQueue);
}
}
@Override
public void routeWithAck(Message message, RoutingContext context) {
addRouteContextToMessage(message);
List<Queue> durableQueuesOnContext = context.getDurableQueues(storeAndForwardQueue.getAddress());
if (!durableQueuesOnContext.contains(storeAndForwardQueue)) {
// There can be many remote bindings for the same node, we only want to add the message once to
// the s & f queue for that node
context.addQueueWithAck(storeAndForwardQueue.getAddress(), storeAndForwardQueue);
}
}
@Override
public synchronized void addConsumer(final SimpleString filterString) throws Exception {
if (filterString != null) {
// There can actually be many consumers on the same queue with the same filter, so we need to maintain a ref
// count
Integer i = filterCounts.get(filterString);
if (i == null) {
filterCounts.put(filterString, 1);
filters.add(FilterImpl.createFilter(filterString));
} else {
filterCounts.put(filterString, i + 1);
}
}
consumerCount++;
}
@Override
public synchronized void removeConsumer(final SimpleString filterString) throws Exception {
if (filterString != null) {
Integer i = filterCounts.get(filterString);
if (i != null) {
int ii = i - 1;
if (ii == 0) {
filterCounts.remove(filterString);
filters.remove(FilterImpl.createFilter(filterString));
} else {
filterCounts.put(filterString, ii);
}
}
}
consumerCount--;
}
@Override
public void reset() {
consumerCount = 0;
filterCounts.clear();
filters.clear();
}
@Override
public synchronized int consumerCount() {
return consumerCount;
}
@Override
public String toString() {
return "RemoteQueueBindingImpl(" +
(connected ? "connected" : "disconnected") + ")[address=" + address +
", consumerCount=" +
consumerCount +
", distance=" +
distance +
", filters=" +
filters +
", id=" +
id +
", idsHeaderName=" +
idsHeaderName +
", queueFilter=" +
queueFilter +
", remoteQueueID=" +
remoteQueueID +
", routingName=" +
routingName +
", storeAndForwardQueue=" +
storeAndForwardQueue +
", uniqueName=" +
uniqueName +
"]";
}
@Override
public String toManagementString() {
return "RemoteQueueBindingImpl [address=" + address +
", storeAndForwardQueue=" + storeAndForwardQueue.getName() +
", remoteQueueID=" +
remoteQueueID + "]";
}
@Override
public void disconnect() {
connected = false;
}
@Override
public boolean isConnected() {
return connected;
}
@Override
public void connect() {
connected = true;
}
public Set<Filter> getFilters() {
return filters;
}
@Override
public void close() throws Exception {
storeAndForwardQueue.close();
}
/**
* This will add routing information to the message.
* This will be later processed during the delivery between the nodes. Because of that this has to be persisted as a property on the message.
*
* @param message
*/
private void addRouteContextToMessage(final Message message) {
byte[] ids = message.getBytesProperty(idsHeaderName);
if (ids == null) {
ids = new byte[8];
} else {
byte[] newIds = new byte[ids.length + 8];
System.arraycopy(ids, 0, newIds, 8, ids.length);
ids = newIds;
}
ByteBuffer buff = ByteBuffer.wrap(ids);
buff.putLong(remoteQueueID);
message.putBytesProperty(idsHeaderName, ids);
if (logger.isTraceEnabled()) {
logger.trace("Adding remoteQueue ID = " + remoteQueueID + " into message=" + message + " store-forward-queue=" + storeAndForwardQueue);
}
}
@Override
public long getRemoteQueueID() {
return remoteQueueID;
}
}