/*
* 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 java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.api.core.RoutingType;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.core.io.IOCallback;
import org.apache.activemq.artemis.core.paging.PagingStore;
import org.apache.activemq.artemis.core.persistence.OperationContext;
import org.apache.activemq.artemis.core.persistence.StorageManager;
import org.apache.activemq.artemis.core.security.CheckType;
import org.apache.activemq.artemis.core.security.SecurityAuth;
import org.apache.activemq.artemis.core.server.AddressQueryResult;
import org.apache.activemq.artemis.core.server.BindingQueryResult;
import org.apache.activemq.artemis.core.server.MessageReference;
import org.apache.activemq.artemis.core.server.QueueQueryResult;
import org.apache.activemq.artemis.core.server.ServerConsumer;
import org.apache.activemq.artemis.core.server.ServerSession;
import org.apache.activemq.artemis.core.server.impl.AddressInfo;
import org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl;
import org.apache.activemq.artemis.core.transaction.Transaction;
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.ActiveMQAMQPResourceLimitExceededException;
import org.apache.activemq.artemis.protocol.amqp.logger.ActiveMQAMQPProtocolMessageBundle;
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.AmqpSupport;
import org.apache.activemq.artemis.protocol.amqp.proton.ProtonServerSenderContext;
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.SessionCallback;
import org.apache.activemq.artemis.spi.core.remoting.Connection;
import org.apache.activemq.artemis.spi.core.remoting.ReadyListener;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.activemq.artemis.utils.RunnableEx;
import org.apache.activemq.artemis.utils.SelectorTranslator;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.activemq.artemis.utils.UUIDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.transaction.TransactionalState;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Receiver;
import org.jboss.logging.Logger;
public class AMQPSessionCallback implements SessionCallback {
private static final Logger logger = Logger.getLogger(AMQPSessionCallback.class);
protected final IDGenerator consumerIDGenerator = new SimpleIDGenerator(0);
private final AMQPConnectionCallback protonSPI;
private final ProtonProtocolManager manager;
private final StorageManager storageManager;
private final AMQPConnectionContext connection;
private final Connection transportConnection;
private ServerSession serverSession;
private final OperationContext operationContext;
private AMQPSessionContext protonSession;
private final Executor closeExecutor;
private final AtomicBoolean draining = new AtomicBoolean(false);
public AMQPSessionCallback(AMQPConnectionCallback protonSPI,
ProtonProtocolManager manager,
AMQPConnectionContext connection,
Connection transportConnection,
Executor executor,
OperationContext operationContext) {
this.protonSPI = protonSPI;
this.manager = manager;
this.storageManager = manager.getServer().getStorageManager();
this.connection = connection;
this.transportConnection = transportConnection;
this.closeExecutor = executor;
this.operationContext = operationContext;
}
@Override
public boolean isWritable(ReadyListener callback, Object protocolContext) {
ProtonServerSenderContext senderContext = (ProtonServerSenderContext) protocolContext;
return transportConnection.isWritable(callback) && senderContext.getSender().getLocalState() != EndpointState.CLOSED;
}
public void onFlowConsumer(Object consumer, int credits, final boolean drain) {
ServerConsumerImpl serverConsumer = (ServerConsumerImpl) consumer;
if (drain) {
// If the draining is already running, then don't do anything
if (draining.compareAndSet(false, true)) {
final ProtonServerSenderContext plugSender = (ProtonServerSenderContext) serverConsumer.getProtocolContext();
serverConsumer.forceDelivery(1, new Runnable() {
@Override
public void run() {
try {
plugSender.getSender().drained();
} finally {
draining.set(false);
}
}
});
}
} else {
serverConsumer.receiveCredits(-1);
}
}
public void withinContext(RunnableEx run) throws Exception {
OperationContext context = recoverContext();
try {
run.run();
} finally {
resetContext(context);
}
}
public void afterIO(IOCallback ioCallback) {
OperationContext context = recoverContext();
try {
manager.getServer().getStorageManager().afterCompleteOperations(ioCallback);
} finally {
resetContext(context);
}
}
@Override
public void browserFinished(ServerConsumer consumer) {
}
public void init(AMQPSessionContext protonSession, SASLResult saslResult) throws Exception {
this.protonSession = protonSession;
String name = UUIDGenerator.getInstance().generateStringUUID();
String user = null;
String passcode = null;
if (saslResult != null) {
user = saslResult.getUser();
if (saslResult instanceof PlainSASLResult) {
passcode = ((PlainSASLResult) saslResult).getPassword();
}
}
serverSession = manager.getServer().createSession(name, user, passcode, ActiveMQClient.DEFAULT_MIN_LARGE_MESSAGE_SIZE, protonSPI.getProtonConnectionDelegate(), // RemotingConnection remotingConnection,
false, // boolean autoCommitSends
false, // boolean autoCommitAcks,
false, // boolean preAcknowledge,
true, //boolean xa,
(String) null, this, true, operationContext, manager.getPrefixes());
}
@Override
public void afterDelivery() throws Exception {
}
public void start() {
}
public Object createSender(ProtonServerSenderContext protonSender,
String queue,
String filter,
boolean browserOnly) throws Exception {
long consumerID = consumerIDGenerator.generateID();
filter = SelectorTranslator.convertToActiveMQFilterString(filter);
ServerConsumer consumer = serverSession.createConsumer(consumerID, SimpleString.toSimpleString(queue), SimpleString.toSimpleString(filter), browserOnly);
// AMQP handles its own flow control for when it's started
consumer.setStarted(true);
consumer.setProtocolContext(protonSender);
return consumer;
}
public void startSender(Object brokerConsumer) throws Exception {
ServerConsumer serverConsumer = (ServerConsumer) brokerConsumer;
// flow control is done at proton
serverConsumer.receiveCredits(-1);
}
public void createTemporaryQueue(String queueName, RoutingType routingType) throws Exception {
serverSession.createQueue(SimpleString.toSimpleString(queueName), SimpleString.toSimpleString(queueName), routingType, null, true, false);
}
public void createTemporaryQueue(String address,
String queueName,
RoutingType routingType,
String filter) throws Exception {
serverSession.createQueue(SimpleString.toSimpleString(address), SimpleString.toSimpleString(queueName), routingType, SimpleString.toSimpleString(filter), true, false);
}
public void createUnsharedDurableQueue(String address,
RoutingType routingType,
String queueName,
String filter) throws Exception {
serverSession.createQueue(SimpleString.toSimpleString(address), SimpleString.toSimpleString(queueName), routingType, SimpleString.toSimpleString(filter), false, true, 1, false, false);
}
public void createSharedDurableQueue(String address,
RoutingType routingType,
String queueName,
String filter) throws Exception {
serverSession.createQueue(SimpleString.toSimpleString(address), SimpleString.toSimpleString(queueName), routingType, SimpleString.toSimpleString(filter), false, true, -1, false, false);
}
public void createSharedVolatileQueue(String address,
RoutingType routingType,
String queueName,
String filter) throws Exception {
serverSession.createQueue(SimpleString.toSimpleString(address), SimpleString.toSimpleString(queueName), routingType, SimpleString.toSimpleString(filter), false, false, -1, true, true);
}
public QueueQueryResult queueQuery(String queueName, RoutingType routingType, boolean autoCreate) throws Exception {
QueueQueryResult queueQueryResult = serverSession.executeQueueQuery(SimpleString.toSimpleString(queueName));
if (!queueQueryResult.isExists() && queueQueryResult.isAutoCreateQueues() && autoCreate) {
try {
serverSession.createQueue(new SimpleString(queueName), new SimpleString(queueName), routingType, null, false, true);
} catch (ActiveMQQueueExistsException e) {
// The queue may have been created by another thread in the mean time. Catch and do nothing.
}
queueQueryResult = serverSession.executeQueueQuery(SimpleString.toSimpleString(queueName));
}
if (queueQueryResult.getRoutingType() != routingType) {
throw new IllegalStateException("Incorrect Routing Type for queue, expecting: " + routingType);
}
return queueQueryResult;
}
public boolean bindingQuery(String address) throws Exception {
BindingQueryResult bindingQueryResult = serverSession.executeBindingQuery(SimpleString.toSimpleString(address));
if (!bindingQueryResult.isExists() && bindingQueryResult.isAutoCreateQueues()) {
try {
serverSession.createQueue(new SimpleString(address), new SimpleString(address), RoutingType.ANYCAST, null, false, true);
} catch (ActiveMQQueueExistsException e) {
// The queue may have been created by another thread in the mean time. Catch and do nothing.
}
bindingQueryResult = serverSession.executeBindingQuery(SimpleString.toSimpleString(address));
}
return bindingQueryResult.isExists();
}
public AddressQueryResult addressQuery(String addressName,
RoutingType routingType,
boolean autoCreate) throws Exception {
AddressQueryResult addressQueryResult = serverSession.executeAddressQuery(SimpleString.toSimpleString(addressName));
if (!addressQueryResult.isExists() && addressQueryResult.isAutoCreateAddresses() && autoCreate) {
try {
serverSession.createAddress(SimpleString.toSimpleString(addressName), routingType, true);
} catch (ActiveMQQueueExistsException e) {
// The queue may have been created by another thread in the mean time. Catch and do nothing.
}
addressQueryResult = serverSession.executeAddressQuery(SimpleString.toSimpleString(addressName));
}
return addressQueryResult;
}
public void closeSender(final Object brokerConsumer) throws Exception {
final ServerConsumer consumer = ((ServerConsumer) brokerConsumer);
final CountDownLatch latch = new CountDownLatch(1);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
consumer.close(false);
latch.countDown();
} catch (Exception e) {
}
}
};
// Due to the nature of proton this could be happening within flushes from the queue-delivery (depending on how it happened on the protocol)
// to avoid deadlocks the close has to be done outside of the main thread on an executor
// otherwise you could get a deadlock
Executor executor = protonSPI.getExeuctor();
if (executor != null) {
executor.execute(runnable);
} else {
runnable.run();
}
try {
latch.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new ActiveMQAMQPInternalErrorException("Unable to close consumers for queue: " + consumer.getQueue());
}
}
public String tempQueueName() {
return UUIDGenerator.getInstance().generateStringUUID();
}
public void close() throws Exception {
//need to check here as this can be called if init fails
if (serverSession != null) {
OperationContext context = recoverContext();
try {
serverSession.close(false);
} finally {
resetContext(context);
}
}
}
public void ack(Transaction transaction, Object brokerConsumer, Message message) throws Exception {
if (transaction == null) {
transaction = serverSession.getCurrentTransaction();
}
OperationContext oldContext = recoverContext();
try {
((ServerConsumer) brokerConsumer).individualAcknowledge(transaction, message.getMessageID());
} finally {
resetContext(oldContext);
}
}
public void cancel(Object brokerConsumer, Message message, boolean updateCounts) throws Exception {
OperationContext oldContext = recoverContext();
try {
((ServerConsumer) brokerConsumer).individualCancel(message.getMessageID(), updateCounts);
((ServerConsumer) brokerConsumer).getQueue().forceDelivery();
} finally {
resetContext(oldContext);
}
}
public void reject(Object brokerConsumer, Message message) throws Exception {
OperationContext oldContext = recoverContext();
try {
((ServerConsumer) brokerConsumer).reject(message.getMessageID());
} finally {
resetContext(oldContext);
}
}
public void resumeDelivery(Object consumer) {
((ServerConsumer) consumer).receiveCredits(-1);
}
public void serverSend(final Transaction transaction,
final Receiver receiver,
final Delivery delivery,
String address,
int messageFormat,
byte[] data) throws Exception {
AMQPMessage message = new AMQPMessage(messageFormat, data);
if (address != null) {
message.setAddress(new SimpleString(address));
} else {
// Anonymous relay must set a To value
if (message.getAddress() == null) {
rejectMessage(delivery, Symbol.valueOf("failed"), "Missing 'to' field for message sent to an anonymous producer");
return;
}
if (!bindingQuery(message.getAddress().toString())) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.addressDoesntExist();
}
}
OperationContext oldcontext = recoverContext();
try {
PagingStore store = manager.getServer().getPagingManager().getPageStore(message.getAddressSimpleString());
if (store.isRejectingMessages()) {
// We drop pre-settled messages (and abort any associated Tx)
if (delivery.remotelySettled()) {
if (transaction != null) {
String amqpAddress = delivery.getLink().getTarget().getAddress();
ActiveMQException e = new ActiveMQAMQPResourceLimitExceededException("Address is full: " + amqpAddress);
transaction.markAsRollbackOnly(e);
}
} else {
rejectMessage(delivery, AmqpError.RESOURCE_LIMIT_EXCEEDED, "Address is full: " + address);
}
} else {
serverSend(transaction, message, delivery, receiver);
}
} finally {
resetContext(oldcontext);
}
}
private void rejectMessage(Delivery delivery, Symbol errorCondition, String errorMessage) {
ErrorCondition condition = new ErrorCondition();
condition.setCondition(errorCondition);
condition.setDescription(errorMessage);
Rejected rejected = new Rejected();
rejected.setError(condition);
afterIO(new IOCallback() {
@Override
public void done() {
connection.lock();
try {
delivery.disposition(rejected);
delivery.settle();
} finally {
connection.unlock();
}
connection.flush();
}
@Override
public void onError(int errorCode, String errorMessage) {
}
});
}
private void serverSend(final Transaction transaction,
final Message message,
final Delivery delivery,
final Receiver receiver) throws Exception {
message.setConnectionID(receiver.getSession().getConnection().getRemoteContainer());
serverSession.send(transaction, message, false, false);
afterIO(new IOCallback() {
@Override
public void done() {
connection.lock();
try {
if (delivery.getRemoteState() instanceof TransactionalState) {
TransactionalState txAccepted = new TransactionalState();
txAccepted.setOutcome(Accepted.getInstance());
txAccepted.setTxnId(((TransactionalState) delivery.getRemoteState()).getTxnId());
delivery.disposition(txAccepted);
} else {
delivery.disposition(Accepted.getInstance());
}
delivery.settle();
} finally {
connection.unlock();
}
connection.flush();
}
@Override
public void onError(int errorCode, String errorMessage) {
connection.lock();
try {
receiver.setCondition(new ErrorCondition(AmqpError.ILLEGAL_STATE, errorCode + ":" + errorMessage));
connection.flush();
} finally {
connection.unlock();
}
}
});
}
public void offerProducerCredit(final String address,
final int credits,
final int threshold,
final Receiver receiver) {
try {
if (address == null) {
connection.lock();
try {
receiver.flow(credits);
} finally {
connection.unlock();
}
connection.flush();
return;
}
final PagingStore store = manager.getServer().getPagingManager().getPageStore(new SimpleString(address));
store.checkMemory(new Runnable() {
@Override
public void run() {
connection.lock();
try {
if (receiver.getRemoteCredit() <= threshold) {
receiver.flow(credits);
}
} finally {
connection.unlock();
}
connection.flush();
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteQueue(String queueName) throws Exception {
manager.getServer().destroyQueue(new SimpleString(queueName));
}
public void resetContext(OperationContext oldContext) {
storageManager.setContext(oldContext);
}
public OperationContext recoverContext() {
OperationContext oldContext = storageManager.getContext();
manager.getServer().getStorageManager().setContext(serverSession.getSessionContext());
return oldContext;
}
@Override
public void sendProducerCreditsMessage(int credits, SimpleString address) {
}
@Override
public boolean updateDeliveryCountAfterCancel(ServerConsumer consumer, MessageReference ref, boolean failed) {
return false;
}
@Override
public void sendProducerCreditsFailMessage(int credits, SimpleString address) {
}
@Override
public int sendMessage(MessageReference ref, Message message, ServerConsumer consumer, int deliveryCount) {
ProtonServerSenderContext plugSender = (ProtonServerSenderContext) consumer.getProtocolContext();
try {
return plugSender.deliverMessage(ref, deliveryCount);
} catch (Exception e) {
connection.lock();
try {
plugSender.getSender().setCondition(new ErrorCondition(AmqpError.INTERNAL_ERROR, e.getMessage()));
connection.flush();
} finally {
connection.unlock();
}
throw new IllegalStateException("Can't deliver message " + e, e);
}
}
@Override
public int sendLargeMessage(MessageReference ref,
Message message,
ServerConsumer consumer,
long bodySize,
int deliveryCount) {
return 0;
}
@Override
public int sendLargeMessageContinuation(ServerConsumer consumer,
byte[] body,
boolean continues,
boolean requiresResponse) {
return 0;
}
@Override
public void closed() {
}
@Override
public void disconnect(ServerConsumer consumer, String queueName) {
ErrorCondition ec = new ErrorCondition(AmqpSupport.RESOURCE_DELETED, "Queue was deleted: " + queueName);
connection.lock();
try {
((ProtonServerSenderContext) consumer.getProtocolContext()).close(ec);
connection.flush();
} catch (ActiveMQAMQPException e) {
logger.error("Error closing link for " + consumer.getQueue().getAddress());
} finally {
connection.unlock();
}
}
@Override
public boolean hasCredits(ServerConsumer consumer) {
ProtonServerSenderContext plugSender = (ProtonServerSenderContext) consumer.getProtocolContext();
if (plugSender != null && plugSender.getSender().getCredit() > 0) {
return true;
} else {
return false;
}
}
public Transaction getTransaction(Binary txid, boolean remove) throws ActiveMQAMQPException {
return protonSPI.getTransaction(txid, remove);
}
public Binary newTransaction() {
return protonSPI.newTransaction();
}
public SimpleString getMatchingQueue(SimpleString address, RoutingType routingType) throws Exception {
return serverSession.getMatchingQueue(address, routingType);
}
public SimpleString getMatchingQueue(SimpleString address,
SimpleString queueName,
RoutingType routingType) throws Exception {
return serverSession.getMatchingQueue(address, queueName, routingType);
}
public AddressInfo getAddress(SimpleString address) {
return serverSession.getAddress(address);
}
public void removeTemporaryQueue(String address) throws Exception {
serverSession.deleteQueue(SimpleString.toSimpleString(address));
}
public RoutingType getDefaultRoutingType(String address) {
return manager.getServer().getAddressSettingsRepository().getMatch(address).getDefaultQueueRoutingType();
}
public void check(SimpleString address, CheckType checkType, SecurityAuth session) throws Exception {
manager.getServer().getSecurityStore().check(address, checkType, session);
}
}