/*
* 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.transport.amqp.client;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.activemq.transport.amqp.client.util.AsyncResult;
import org.apache.activemq.transport.amqp.client.util.ClientFuture;
import org.apache.activemq.transport.amqp.client.util.UnmodifiableProxy;
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.Connection;
import org.apache.qpid.proton.engine.Session;
/**
* Session class that manages a Proton session endpoint.
*/
public class AmqpSession extends AmqpAbstractResource<Session> {
private final AtomicLong receiverIdGenerator = new AtomicLong();
private final AtomicLong senderIdGenerator = new AtomicLong();
private final AmqpConnection connection;
private final String sessionId;
private final AmqpTransactionContext txContext;
private final AtomicBoolean closed = new AtomicBoolean();
/**
* Create a new session instance.
*
* @param connection The parent connection that created the session.
* @param sessionId The unique ID value assigned to this session.
*/
public AmqpSession(AmqpConnection connection, String sessionId) {
this.connection = connection;
this.sessionId = sessionId;
this.txContext = new AmqpTransactionContext(this);
}
/**
* Close the receiver, a closed receiver will throw exceptions if any further send
* calls are made.
*
* @throws IOException if an error occurs while closing the receiver.
*/
public void close() throws IOException {
if (closed.compareAndSet(false, true)) {
final ClientFuture request = new ClientFuture();
getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
close(request);
pumpToProtonTransport(request);
}
});
request.sync();
}
}
/**
* Create an anonymous sender.
*
* @return a newly created sender that is ready for use.
*
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender() throws Exception {
return createSender(null, false);
}
/**
* Create an anonymous sender instance using the anonymous relay support of the broker.
*
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createAnonymousSender() throws Exception {
return createSender(null, false);
}
/**
* Create a sender instance using the given address
*
* @param address the address to which the sender will produce its messages.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender(final String address) throws Exception {
return createSender(address, false);
}
/**
* Create a sender instance using the given address
*
* @param address the address to which the sender will produce its messages.
* @param desiredCapabilities the capabilities that the caller wants the remote to support.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender(final String address, Symbol[] desiredCapabilities) throws Exception {
return createSender(address, false, desiredCapabilities, null, null);
}
/**
* Create a sender instance using the given address
*
* @param address the address to which the sender will produce its messages.
* @param presettle controls if the created sender produces message that have already been marked settled.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender(final String address, boolean presettle) throws Exception {
return createSender(address, presettle, null, null, null);
}
/**
* Create a sender instance using the given address
*
* @param address the address to which the sender will produce its messages.
* @param presettle controls if the created sender produces message that have already been marked settled.
* @param desiredCapabilities the capabilities that the caller wants the remote to support.
* @param offeredCapabilities the capabilities that the caller wants the advertise support for.
* @param properties the properties to send as part of the sender open.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender(final String address, boolean presettle, Symbol[] desiredCapabilities, Symbol[] offeredCapabilities, Map<Symbol, Object> properties) throws Exception {
checkClosed();
final AmqpSender sender = new AmqpSender(AmqpSession.this, address, getNextSenderId());
sender.setPresettle(presettle);
sender.setDesiredCapabilities(desiredCapabilities);
sender.setOfferedCapabilities(offeredCapabilities);
sender.setProperties(properties);
final ClientFuture request = new ClientFuture();
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
sender.setStateInspector(getStateInspector());
sender.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return sender;
}
/**
* Create a sender instance using the given address
*
* @param address
* the address to which the sender will produce its messages.
* @param senderSettlementMode
* controls the settlement mode used by the created Sender
* @param receiverSettlementMode
* controls the desired settlement mode used by the remote Receiver
*
* @return a newly created sender that is ready for use.
*
* @throws Exception if an error occurs while creating the sender.
*/
public AmqpSender createSender(final String address, final SenderSettleMode senderMode, ReceiverSettleMode receiverMode) throws Exception {
checkClosed();
final AmqpSender sender = new AmqpSender(AmqpSession.this, address, getNextSenderId(), senderMode, receiverMode);
final ClientFuture request = new ClientFuture();
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
sender.setStateInspector(getStateInspector());
sender.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return sender;
}
/**
* Create a sender instance using the given Target
*
* @param target the caller created and configured Target used to create the sender link.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpSender createSender(Target target) throws Exception {
return createSender(target, getNextSenderId());
}
/**
* Create a sender instance using the given Target
*
* @param target the caller created and configured Target used to create the sender link.
* @param senderId the sender ID to assign to the newly created Sender.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpSender createSender(Target target, String senderId) throws Exception {
return createSender(target, senderId, null, null, null);
}
/**
* Create a sender instance using the given Target
*
* @param target the caller created and configured Target used to create the sender link.
* @param senderId the sender ID to assign to the newly created Sender.
* @param desiredCapabilities the capabilities that the caller wants the remote to support.
* @param offeredCapabilities the capabilities that the caller wants the advertise support for.
* @param properties the properties to send as part of the sender open.
* @return a newly created sender that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpSender createSender(Target target, String senderId, Symbol[] desiredCapabilities, Symbol[] offeredCapabilities, Map<Symbol, Object> properties) throws Exception {
checkClosed();
final AmqpSender sender = new AmqpSender(AmqpSession.this, target, senderId);
sender.setDesiredCapabilities(desiredCapabilities);
sender.setOfferedCapabilities(offeredCapabilities);
sender.setProperties(properties);
final ClientFuture request = new ClientFuture();
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
sender.setStateInspector(getStateInspector());
sender.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return sender;
}
/**
* Create a receiver instance using the given address
*
* @param address the address to which the receiver will subscribe for its messages.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(String address) throws Exception {
return createReceiver(address, null, false);
}
/**
* Create a receiver instance using the given address
*
* @param address the address to which the receiver will subscribe for its messages.
* @param selector the JMS selector to use for the subscription
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(String address, String selector) throws Exception {
return createReceiver(address, selector, false);
}
/**
* Create a receiver instance using the given address
*
* @param address the address to which the receiver will subscribe for its messages.
* @param selector the JMS selector to use for the subscription
* @param noLocal should the subscription have messages from its connection filtered.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(String address, String selector, boolean noLocal) throws Exception {
return createReceiver(address, selector, noLocal, false);
}
/**
* Create a receiver instance using the given address
*
* @param address the address to which the receiver will subscribe for its messages.
* @param selector the JMS selector to use for the subscription
* @param noLocal should the subscription have messages from its connection filtered.
* @param presettle should the receiver be created with a settled sender mode.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(String address,
String selector,
boolean noLocal,
boolean presettle) throws Exception {
checkClosed();
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, address, getNextReceiverId());
receiver.setNoLocal(noLocal);
receiver.setPresettle(presettle);
if (selector != null && !selector.isEmpty()) {
receiver.setSelector(selector);
}
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given address
*
* @param address
* the address to which the receiver will subscribe for its messages.
* @param senderSettlementMode
* controls the desired settlement mode used by the remote Sender
* @param receiverSettlementMode
* controls the settlement mode used by the created Receiver
*
* @return a newly created receiver that is ready for use.
*
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(String address, SenderSettleMode senderMode, ReceiverSettleMode receiverMode) throws Exception {
checkClosed();
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, address, getNextReceiverId(), senderMode, receiverMode);
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given Source
*
* @param source the caller created and configured Source used to create the receiver link.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(Source source) throws Exception {
return createReceiver(source, getNextReceiverId());
}
/**
* Create a receiver instance using the given Source
*
* @param source the caller created and configured Source used to create the receiver link.
* @param receiverId the receiver id to use.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createReceiver(Source source, String receiverId) throws Exception {
checkClosed();
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, source, receiverId);
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given Source
*
* @param source the caller created and configured Source used to create the receiver link.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createMulticastReceiver(Source source, String receiverId, String receiveName) throws Exception {
checkClosed();
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, source, receiverId);
receiver.setSubscriptionName(receiveName);
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given Source
*
* @param source the caller created and configured Source used to create the receiver link.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createMulticastReceiver(String receiverId, String address, String receiveName) throws Exception {
checkClosed();
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, address, receiverId);
receiver.setSubscriptionName(receiveName);
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given address that creates a durable subscription.
*
* @param address the address to which the receiver will subscribe for its messages.
* @param subscriptionName the name of the subscription that is being created.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createDurableReceiver(String address, String subscriptionName) throws Exception {
return createDurableReceiver(address, subscriptionName, null, false);
}
/**
* Create a receiver instance using the given address that creates a durable subscription.
*
* @param address the address to which the receiver will subscribe for its messages.
* @param subscriptionName the name of the subscription that is being created.
* @param selector the JMS selector to use for the subscription
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createDurableReceiver(String address,
String subscriptionName,
String selector) throws Exception {
return createDurableReceiver(address, subscriptionName, selector, false);
}
/**
* Create a receiver instance using the given address that creates a durable subscription.
*
* @param address the address to which the receiver will subscribe for its messages.
* @param subscriptionName the name of the subscription that is being created.
* @param selector the JMS selector to use for the subscription
* @param noLocal should the subscription have messages from its connection filtered.
* @return a newly created receiver that is ready for use.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver createDurableReceiver(String address,
String subscriptionName,
String selector,
boolean noLocal) throws Exception {
checkClosed();
if (subscriptionName == null || subscriptionName.isEmpty()) {
throw new IllegalArgumentException("subscription name must not be null or empty.");
}
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, address, getNextReceiverId());
receiver.setSubscriptionName(subscriptionName);
receiver.setNoLocal(noLocal);
if (selector != null && !selector.isEmpty()) {
receiver.setSelector(selector);
}
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* Create a receiver instance using the given address that creates a durable subscription.
*
* @param subscriptionName the name of the subscription that should be queried for on the remote..
* @return a newly created receiver that is ready for use if the subscription exists.
* @throws Exception if an error occurs while creating the receiver.
*/
public AmqpReceiver lookupSubscription(String subscriptionName) throws Exception {
checkClosed();
if (subscriptionName == null || subscriptionName.isEmpty()) {
throw new IllegalArgumentException("subscription name must not be null or empty.");
}
final ClientFuture request = new ClientFuture();
final AmqpReceiver receiver = new AmqpReceiver(AmqpSession.this, (String) null, getNextReceiverId());
receiver.setSubscriptionName(subscriptionName);
connection.getScheduler().execute(new Runnable() {
@Override
public void run() {
checkClosed();
receiver.setStateInspector(getStateInspector());
receiver.open(request);
pumpToProtonTransport(request);
}
});
request.sync();
return receiver;
}
/**
* @return this session's parent AmqpConnection.
*/
public AmqpConnection getConnection() {
return connection;
}
public Session getSession() {
return UnmodifiableProxy.sessionProxy(getEndpoint());
}
public boolean isInTransaction() {
return txContext.isInTransaction();
}
@Override
public String toString() {
return "AmqpSession { " + sessionId + " }";
}
//----- Session Transaction Methods --------------------------------------//
/**
* Starts a new transaction associated with this session.
*
* @throws Exception if an error occurs starting a new Transaction.
*/
public void begin() throws Exception {
if (txContext.isInTransaction()) {
throw new javax.jms.IllegalStateException("Session already has an active transaction");
}
txContext.begin();
}
/**
* Commit the current transaction associated with this session.
*
* @throws Exception if an error occurs committing the Transaction.
*/
public void commit() throws Exception {
if (!txContext.isInTransaction()) {
throw new javax.jms.IllegalStateException("Commit called on Session that does not have an active transaction");
}
txContext.commit();
}
/**
* Roll back the current transaction associated with this session.
*
* @throws Exception if an error occurs rolling back the Transaction.
*/
public void rollback() throws Exception {
if (!txContext.isInTransaction()) {
throw new javax.jms.IllegalStateException("Rollback called on Session that does not have an active transaction");
}
txContext.rollback();
}
//----- Internal access used to manage resources -------------------------//
ScheduledExecutorService getScheduler() {
return connection.getScheduler();
}
Connection getProtonConnection() {
return connection.getProtonConnection();
}
void pumpToProtonTransport(AsyncResult request) {
connection.pumpToProtonTransport(request);
}
public AmqpTransactionId getTransactionId() {
if (txContext != null && txContext.isInTransaction()) {
return txContext.getTransactionId();
}
return null;
}
AmqpTransactionContext getTransactionContext() {
return txContext;
}
//----- Private implementation details -----------------------------------//
@Override
protected void doOpenInspection() {
try {
getStateInspector().inspectOpenedResource(getSession());
} catch (Throwable error) {
getStateInspector().markAsInvalid(error.getMessage());
}
}
@Override
protected void doClosedInspection() {
try {
getStateInspector().inspectClosedResource(getSession());
} catch (Throwable error) {
getStateInspector().markAsInvalid(error.getMessage());
}
}
private String getNextSenderId() {
return sessionId + ":" + senderIdGenerator.incrementAndGet();
}
private String getNextReceiverId() {
return sessionId + ":" + receiverIdGenerator.incrementAndGet();
}
private void checkClosed() {
if (isClosed() || connection.isClosed()) {
throw new IllegalStateException("Session is already closed");
}
}
}