/* * Copyright 2011 the original author or authors. * * Licensed 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.vertx.ext.amqp.impl.protocol; import io.vertx.ext.amqp.*; import io.vertx.ext.amqp.impl.CreditMode; import org.apache.qpid.proton.amqp.messaging.*; import org.apache.qpid.proton.amqp.transport.DeliveryState; import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode; import org.apache.qpid.proton.amqp.transport.SenderSettleMode; import org.apache.qpid.proton.engine.Delivery; import org.apache.qpid.proton.engine.Link; import org.apache.qpid.proton.engine.Receiver; import org.apache.qpid.proton.engine.Sender; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; class SessionImpl implements Session { static final int CUMULATIVE = 0x01; static final int SETTLE = 0x02; private static final DeliveryState ACCEPTED = Accepted.getInstance(); private static final DeliveryState REJECTED = new Rejected(); private static final DeliveryState RELEASED = Released.getInstance(); private ConnectionImpl _conn; private org.apache.qpid.proton.engine.Session _ssn; private AtomicBoolean _closed = new AtomicBoolean(false); private final Map<Link, BaseLink> _links = new HashMap<Link, BaseLink>(); private final AtomicLong _deliveryTag = new AtomicLong(0); private final AtomicLong _incommingSequence = new AtomicLong(0); private final Map<Long, Delivery> _unsettled = new ConcurrentHashMap<Long, Delivery>(); private final AtomicLong _lastSettled = new AtomicLong(0); private final AtomicLong _lastDispositionMark = new AtomicLong(0); private final String _id; SessionImpl(ConnectionImpl conn, org.apache.qpid.proton.engine.Session ssn) { _id = UUID.randomUUID().toString(); _conn = conn; _ssn = ssn; } void open() { _ssn.open(); _conn.write(); } @Override public OutgoingLink createOutboundLink(String address, ReliabilityMode mode) throws MessagingException { checkClosed(); String name = UUID.randomUUID().toString(); Sender sender = _ssn.sender(name); // Source source = new Source(); Target target = new Target(); if (address == null || address.isEmpty() || address.equals("#")) { target.setDynamic(true); } else { target.setAddress(address); } sender.setTarget(target); // sender.setSource(source); sender.setSenderSettleMode(mode == ReliabilityMode.UNRELIABLE ? SenderSettleMode.SETTLED : SenderSettleMode.UNSETTLED); sender.open(); OutgoingLinkImpl outLink = new OutgoingLinkImpl(this, address, sender); outLink.setDynamicAddress(target.getDynamic()); _links.put(sender, outLink); sender.setContext(outLink); return outLink; } @Override public IncomingLink createInboundLink(String address, ReliabilityMode mode, CreditMode creditMode) throws MessagingException { checkClosed(); String name = UUID.randomUUID().toString(); Receiver receiver = _ssn.receiver(name); Source source = new Source(); // Target target = new Target(); if (address == null || address.isEmpty() || address.equals("#")) { source.setDynamic(true); } else { source.setAddress(address); } receiver.setSource(source); // receiver.setTarget(target); switch (mode) { case UNRELIABLE: receiver.setReceiverSettleMode(ReceiverSettleMode.FIRST); receiver.setSenderSettleMode(SenderSettleMode.SETTLED); break; case AT_LEAST_ONCE: receiver.setReceiverSettleMode(ReceiverSettleMode.FIRST); receiver.setSenderSettleMode(SenderSettleMode.UNSETTLED); break; // case EXACTLY_ONCE: // receiver.setReceiverSettleMode(ReceiverSettleMode.SECOND); // receiver.setSenderSettleMode(SenderSettleMode.UNSETTLED); // break; } receiver.open(); IncomingLinkImpl inLink = new IncomingLinkImpl(this, address, receiver, mode, creditMode); inLink.setDynamicAddress(source.getDynamic()); _links.put(receiver, inLink); receiver.setContext(inLink); return inLink; } @Override public void disposition(AmqpMessage msg, MessageDisposition disposition, int... flags) throws MessageFormatException, MessagingException { disposition(convertMessage(msg).getSequence(), disposition, flags); } void disposition(long sequence, MessageDisposition disposition, int... flags) throws MessageFormatException, MessagingException { DeliveryState state; switch (disposition) { case ACCEPTED: state = ACCEPTED; break; case REJECTED: state = REJECTED; break; case RELEASED: state = RELEASED; break; default: throw new MessagingException("UNKNOWN is not a valid option for this method", ErrorCode.INTERNAL_ERROR); } disposition(sequence, state, flags); } @Override public void settle(AmqpMessage msg, int... flags) throws MessageFormatException, MessagingException { settle(convertMessage(msg).getSequence(), flags.length == 0 ? false : (flags[0] & CUMULATIVE) != 0, true); } @Override public void close() { if (!_closed.get()) { closeImpl(); // _conn.removeSession(_ssn); _conn.write(); } } void closeImpl() { _closed.set(true); _ssn.close(); for (Link link : _links.keySet()) { _links.get(link).closeImpl(); } _links.clear(); } void removeLink(Link link) { _links.remove(link); } ConnectionImpl getConnection() { return _conn; } long getNextDeliveryTag() { return _deliveryTag.incrementAndGet(); } long getNextIncommingSequence() { return _incommingSequence.incrementAndGet(); } String getID() { return _id; } void checkClosed() throws MessagingException { if (_closed.get()) { throw new MessagingException("Session is closed", ErrorCode.SESSION_CLOSED); } } void addUnsettled(long id, Delivery d) { _unsettled.put(id, d); } void disposition(long sequence, DeliveryState state, int... flags) { int flag = flags.length == 1 ? flags[0] : 0; boolean cumilative = (flag & CUMULATIVE) != 0; boolean settle = (flag & SETTLE) != 0; long count = cumilative ? _lastDispositionMark.get() : sequence; long end = sequence; while (count <= end) { Delivery d = _unsettled.get(count); if (d != null) { d.disposition(state); } count++; } _lastDispositionMark.set(end); if (settle) { settle(sequence, cumilative, false); } _conn.write(); } void settle(long sequence, boolean cumilative, boolean write) { long count = cumilative ? _lastSettled.get() : sequence; long end = sequence; while (count <= end) { Delivery d = _unsettled.get(count); if (d != null) { if (!d.isSettled() && d.getLink().getReceiverSettleMode() == ReceiverSettleMode.FIRST) { d.settle(); ((IncomingLinkImpl) d.getLink().getContext()).decrementUnsettledCount(); _unsettled.remove(count); } } count++; } _lastSettled.set(end); _conn.write(); } InboundMessage convertMessage(AmqpMessage msg) throws MessageFormatException, MessagingException { if (!(msg instanceof InboundMessage)) { throw new MessageFormatException("The supplied message is not a recognized type", ErrorCode.INVALID_MSG_FORMAT); } InboundMessage m = (InboundMessage) msg; if (m.getSessionID() != _id) { throw new MessagingException("The supplied message is not associated with this session", ErrorCode.INVALID_MSG_REF); } return m; } }