/** * 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 io.hawtjms.provider.amqp; import io.hawtjms.jms.JmsDestination; import io.hawtjms.jms.meta.JmsConsumerId; import io.hawtjms.jms.meta.JmsConsumerInfo; import io.hawtjms.jms.meta.JmsProducerId; import io.hawtjms.jms.meta.JmsProducerInfo; import io.hawtjms.jms.meta.JmsSessionId; import io.hawtjms.jms.meta.JmsSessionInfo; import io.hawtjms.jms.meta.JmsTransactionId; import io.hawtjms.provider.AsyncResult; import java.util.HashMap; import java.util.Map; import javax.jms.IllegalStateException; import org.apache.qpid.proton.engine.Session; import org.apache.qpid.proton.message.MessageFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class AmqpSession extends AbstractAmqpResource<JmsSessionInfo, Session> { private static final Logger LOG = LoggerFactory.getLogger(AmqpSession.class); private final AmqpConnection connection; private final AmqpTransactionContext txContext; private final Map<JmsConsumerId, AmqpConsumer> consumers = new HashMap<JmsConsumerId, AmqpConsumer>(); private final Map<JmsProducerId, AmqpProducer> producers = new HashMap<JmsProducerId, AmqpProducer>(); public AmqpSession(AmqpConnection connection, JmsSessionInfo info) { super(info, connection.getProtonConnection().session()); this.connection = connection; this.info.getSessionId().setProviderHint(this); if (this.info.isTransacted()) { txContext = new AmqpTransactionContext(this); } else { txContext = null; } } @Override public void opened() { if (this.txContext != null) { this.txContext.open(openRequest); } else { super.opened(); } } @Override protected void doOpen() { this.endpoint.setIncomingCapacity(Integer.MAX_VALUE); this.connection.addSession(this); } @Override protected void doClose() { this.connection.removeSession(this); } /** * Perform an acknowledge of all delivered messages for all consumers active in this * Session. */ public void acknowledge() { for (AmqpConsumer consumer : consumers.values()) { consumer.acknowledge(); } } /** * Perform re-send of all delivered but not yet acknowledged messages for all consumers * active in this Session. */ public void recover() { for (AmqpConsumer consumer : consumers.values()) { consumer.recover(); } } public AmqpProducer createProducer(JmsProducerInfo producerInfo) { AmqpProducer producer = null; // TODO - There seems to be an issue with Proton not allowing links with a Target // that has no address. Otherwise we could just ensure that messages sent // to these anonymous targets have their 'to' value set to the destination. if (producerInfo.getDestination() != null) { LOG.debug("Creating fixed Producer for: {}", producerInfo.getDestination()); producer = new AmqpFixedProducer(this, producerInfo); } else { LOG.debug("Creating an Anonymous Producer: "); producer = new AmqpAnonymousProducer(this, producerInfo); } producer.setPresettle(connection.isPresettleProducers()); return producer; } public AmqpProducer getProducer(JmsProducerInfo producerInfo) { return getProducer(producerInfo.getProducerId()); } public AmqpProducer getProducer(JmsProducerId producerId) { if (producerId.getProviderHint() instanceof AmqpProducer) { return (AmqpProducer) producerId.getProviderHint(); } return this.producers.get(producerId); } public AmqpConsumer createConsumer(JmsConsumerInfo consumerInfo) { AmqpConsumer result = null; if (consumerInfo.isBrowser()) { result = new AmqpQueueBrowser(this, consumerInfo); } else { result = new AmqpConsumer(this, consumerInfo); } result.setPresettle(connection.isPresettleConsumers()); return result; } public AmqpConsumer getConsumer(JmsConsumerInfo consumerInfo) { return getConsumer(consumerInfo.getConsumerId()); } public AmqpConsumer getConsumer(JmsConsumerId consumerId) { if (consumerId.getProviderHint() instanceof AmqpConsumer) { return (AmqpConsumer) consumerId.getProviderHint(); } return this.consumers.get(consumerId); } public AmqpTransactionContext getTransactionContext() { return this.txContext; } /** * Begins a new Transaction using the given Transaction Id as the identifier. The AMQP * binary Transaction Id will be stored in the provider hint value of the given transaction. * * @param txId * The JMS Framework's assigned Transaction Id for the new TX. * @param request * The request that will be signaled on completion of this operation. * * @throws Exception if an error occurs while performing the operation. */ public void begin(JmsTransactionId txId, AsyncResult<Void> request) throws Exception { if (!this.info.isTransacted()) { throw new IllegalStateException("Non-transacted Session cannot start a TX."); } getTransactionContext().begin(txId, request); } /** * Commit the currently running Transaction. * * @param request * The request that will be signaled on completion of this operation. * * @throws Exception if an error occurs while performing the operation. */ public void commit(AsyncResult<Void> request) throws Exception { if (!this.info.isTransacted()) { throw new IllegalStateException("Non-transacted Session cannot start a TX."); } getTransactionContext().commit(request); } /** * Roll back the currently running Transaction * * @param request * The request that will be signaled on completion of this operation. * * @throws Exception if an error occurs while performing the operation. */ public void rollback(AsyncResult<Void> request) throws Exception { if (!this.info.isTransacted()) { throw new IllegalStateException("Non-transacted Session cannot start a TX."); } getTransactionContext().rollback(request); } void addResource(AmqpConsumer consumer) { consumers.put(consumer.getConsumerId(), consumer); } void removeResource(AmqpConsumer consumer) { consumers.remove(consumer.getConsumerId()); } void addResource(AmqpProducer producer) { producers.put(producer.getProducerId(), producer); } void removeResource(AmqpProducer producer) { producers.remove(producer.getProducerId()); } /** * Adds Topic or Queue qualifiers to the destination target. We don't add qualifiers to * Temporary Topics and Queues since AMQP works a bit differently. * * @param destination * The destination to Qualify. * * @return the qualified destination name. */ public String getQualifiedName(JmsDestination destination) { if (destination == null) { return null; } String result = destination.getName(); if (!destination.isTemporary()) { if (destination.isTopic()) { result = connection.getTopicPrefix() + destination.getName(); } else { result = connection.getQueuePrefix() + destination.getName(); } } return result; } public AmqpProvider getProvider() { return this.connection.getProvider(); } public AmqpConnection getConnection() { return this.connection; } public JmsSessionId getSessionId() { return this.info.getSessionId(); } public Session getProtonSession() { return this.endpoint; } public MessageFactory getMessageFactory() { return this.connection.getMessageFactory(); } boolean isTransacted() { return this.info.isTransacted(); } boolean isAsyncAck() { return this.info.isSendAcksAsync() || isTransacted(); } @Override public String toString() { return "AmqpSession { " + getSessionId() + " }"; } }