/*
* 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.postoffice.impl;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.WildcardConfiguration;
import org.apache.activemq.artemis.core.postoffice.Address;
import org.apache.activemq.artemis.core.postoffice.AddressManager;
import org.apache.activemq.artemis.core.postoffice.Binding;
import org.apache.activemq.artemis.core.postoffice.Bindings;
import org.apache.activemq.artemis.core.postoffice.BindingsFactory;
import org.apache.activemq.artemis.core.postoffice.QueueBinding;
import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.utils.CompositeAddress;
import org.jboss.logging.Logger;
/**
* A simple address manager that maintains the addresses and bindings.
*/
public class SimpleAddressManager implements AddressManager {
private static final Logger logger = Logger.getLogger(SimpleAddressManager.class);
private final ConcurrentMap<SimpleString, AddressInfo> addressInfoMap = new ConcurrentHashMap<>();
/**
* HashMap<Address, Binding>
*/
private final ConcurrentMap<SimpleString, Bindings> mappings = new ConcurrentHashMap<>();
/**
* HashMap<QueueName, Binding>
*/
private final ConcurrentMap<SimpleString, Binding> nameMap = new ConcurrentHashMap<>();
private final BindingsFactory bindingsFactory;
protected final WildcardConfiguration wildcardConfiguration;
public SimpleAddressManager(final BindingsFactory bindingsFactory) {
this(bindingsFactory, new WildcardConfiguration());
}
public SimpleAddressManager(final BindingsFactory bindingsFactory, final WildcardConfiguration wildcardConfiguration) {
this.wildcardConfiguration = wildcardConfiguration;
this.bindingsFactory = bindingsFactory;
}
@Override
public boolean addBinding(final Binding binding) throws Exception {
if (nameMap.putIfAbsent(binding.getUniqueName(), binding) != null) {
throw ActiveMQMessageBundle.BUNDLE.bindingAlreadyExists(binding);
}
if (logger.isTraceEnabled()) {
logger.trace("Adding binding " + binding + " with address = " + binding.getUniqueName(), new Exception("trace"));
}
return addMappingInternal(binding.getAddress(), binding);
}
@Override
public Binding removeBinding(final SimpleString uniqueName, Transaction tx) throws Exception {
final Binding binding = nameMap.remove(uniqueName);
if (binding == null) {
return null;
}
removeBindingInternal(binding.getAddress(), uniqueName);
return binding;
}
@Override
public Bindings getBindingsForRoutingAddress(final SimpleString address) throws Exception {
return mappings.get(address);
}
@Override
public Binding getBinding(final SimpleString bindableName) {
return nameMap.get(CompositeAddress.extractQueueName(bindableName));
}
@Override
public Map<SimpleString, Binding> getBindings() {
return nameMap;
}
@Override
public Bindings getMatchingBindings(final SimpleString address) throws Exception {
Address add = new AddressImpl(address, wildcardConfiguration);
Bindings bindings = bindingsFactory.createBindings(address);
for (Binding binding : nameMap.values()) {
Address addCheck = new AddressImpl(binding.getAddress(), wildcardConfiguration);
if (addCheck.matches(add)) {
bindings.addBinding(binding);
}
}
return bindings;
}
@Override
public SimpleString getMatchingQueue(final SimpleString address, RoutingType routingType) throws Exception {
Binding binding = getBinding(address);
if (binding == null || !(binding instanceof LocalQueueBinding)
|| !binding.getAddress().equals(address)) {
Bindings bindings = mappings.get(address);
if (bindings != null) {
for (Binding theBinding : bindings.getBindings()) {
if (theBinding instanceof LocalQueueBinding) {
binding = theBinding;
break;
}
}
}
}
return binding != null ? binding.getUniqueName() : null;
}
@Override
public SimpleString getMatchingQueue(final SimpleString address, final SimpleString queueName, RoutingType routingType) throws Exception {
Binding binding = getBinding(queueName);
if (binding != null && !binding.getAddress().equals(address) && !address.toString().isEmpty()) {
throw new IllegalStateException("queue belongs to address" + binding.getAddress());
}
return binding != null ? binding.getUniqueName() : null;
}
@Override
public void clear() {
nameMap.clear();
mappings.clear();
}
@Override
public Set<SimpleString> getAddresses() {
Set<SimpleString> addresses = new HashSet<>();
addresses.addAll(mappings.keySet());
return addresses;
}
protected void removeBindingInternal(final SimpleString address, final SimpleString bindableName) {
Bindings bindings = mappings.get(address);
if (bindings != null) {
removeMapping(bindableName, bindings);
if (bindings.getBindings().isEmpty()) {
mappings.remove(address);
}
}
}
protected Binding removeMapping(final SimpleString bindableName, final Bindings bindings) {
Binding theBinding = null;
for (Binding binding : bindings.getBindings()) {
if (binding.getUniqueName().equals(CompositeAddress.extractQueueName(bindableName))) {
theBinding = binding;
break;
}
}
if (theBinding == null) {
throw new IllegalStateException("Cannot find binding " + bindableName);
}
bindings.removeBinding(theBinding);
return theBinding;
}
protected boolean addMappingInternal(final SimpleString address, final Binding binding) throws Exception {
Bindings bindings = mappings.get(address);
Bindings prevBindings = null;
if (bindings == null) {
bindings = bindingsFactory.createBindings(address);
prevBindings = mappings.putIfAbsent(address, bindings);
if (prevBindings != null) {
bindings = prevBindings;
}
}
bindings.addBinding(binding);
return prevBindings != null;
}
@Override
public boolean addAddressInfo(AddressInfo addressInfo) {
return addressInfoMap.putIfAbsent(addressInfo.getName(), addressInfo) == null;
}
@Override
public AddressInfo updateAddressInfo(SimpleString addressName,
Collection<RoutingType> routingTypes) {
if (routingTypes == null || routingTypes.isEmpty()) {
return this.addressInfoMap.get(addressName);
} else {
return this.addressInfoMap.computeIfPresent(addressName, (name, oldAddressInfo) -> {
validateRoutingTypes(name, routingTypes);
final Set<RoutingType> updatedRoutingTypes = EnumSet.copyOf(routingTypes);
oldAddressInfo.setRoutingTypes(updatedRoutingTypes);
return oldAddressInfo;
});
}
}
private void validateRoutingTypes(SimpleString addressName, Collection<RoutingType> routingTypes) {
final Bindings bindings = this.mappings.get(addressName);
if (bindings != null) {
for (Binding binding : bindings.getBindings()) {
if (binding instanceof QueueBinding) {
final QueueBinding queueBinding = (QueueBinding) binding;
final RoutingType routingType = queueBinding.getQueue().getRoutingType();
if (!routingTypes.contains(routingType)) {
throw ActiveMQMessageBundle.BUNDLE.invalidRoutingTypeDelete(routingType, addressName.toString());
}
}
}
}
}
@Override
public AddressInfo removeAddressInfo(SimpleString address) {
return addressInfoMap.remove(address);
}
@Override
public AddressInfo getAddressInfo(SimpleString addressName) {
return addressInfoMap.get(addressName);
}
}