/*
* 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.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.NetSocket;
import io.vertx.ext.amqp.impl.ConnectionSettings;
import io.vertx.ext.amqp.impl.CreditMode;
import io.vertx.ext.amqp.ReliabilityMode;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.engine.*;
import org.apache.qpid.proton.engine.Link;
import org.apache.qpid.proton.message.Message;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;
class ConnectionImpl implements Connection {
enum State {
NEW, CONNECTED, CLOSED, FAILED, RETRY_IN_PROGRESS
}
;
private static final Logger _logger = LoggerFactory.getLogger(ConnectionImpl.class);
protected final org.apache.qpid.proton.engine.Connection protonConnection;
private final Transport _transport;
protected final Collector _collector;
private final ArrayList<Handler<ConnectionImpl>> disconnectHandlers = new ArrayList<Handler<ConnectionImpl>>();
private final Object _lock = new Object();
private final Handler<AmqpEvent> _eventHandler;
private final ConnectionSettings _settings;
private final boolean _isInbound;
private final String _toString;
private NetSocket _socket;
private State _state = State.NEW;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private static final AtomicLong CONN_NUMBER_GENERATOR = new AtomicLong();
ConnectionImpl(ConnectionSettings settings, Handler<AmqpEvent> handler, boolean inbound) {
_toString = "amqp://" + settings.getHost() + ":" + settings.getPort();
_isInbound = inbound;
_settings = settings;
_eventHandler = handler;
protonConnection = org.apache.qpid.proton.engine.Connection.Factory.create();
_transport = org.apache.qpid.proton.engine.Transport.Factory.create();
_collector = Collector.Factory.create();
protonConnection.setContainer(String.format("vertx-amqp-bridge:num-%s:timestamp-%s",
CONN_NUMBER_GENERATOR.incrementAndGet(), DATE_FORMAT.format(new Date(System.currentTimeMillis()))));
protonConnection.setContext(this);
protonConnection.collect(_collector);
/*
* Sasl sasl = _transport.sasl(); if (inbound) { sasl.server();
* sasl.setMechanisms(new String[] { "ANONYMOUS" });
* sasl.done(Sasl.SaslOutcome.PN_SASL_OK); } else { sasl.client();
* sasl.setMechanisms(new String[] { "ANONYMOUS" }); }
*/
_transport.bind(protonConnection);
protonConnection.open();
}
ConnectionSettings getSettings() {
return _settings;
}
void addDisconnectHandler(Handler<ConnectionImpl> handler) {
disconnectHandlers.add(handler);
}
void setNetSocket(NetSocket s) {
synchronized (_lock) {
_socket = s;
_socket.handler(data -> {
byte[] bytes = data.getBytes();
int start = 0;
while (start < bytes.length) {
int count = Math.min(_transport.getInputBuffer().remaining(), bytes.length - start);
_transport.getInputBuffer().put(bytes, start, count);
start += count;
_transport.processInput();
processEvents();
}
write();
});
_socket.drainHandler(v -> {
write();
});
_socket.endHandler(v -> {
if (getState() != State.CLOSED) {
_logger.info(String.format(
"Received EOF for connection {%s:%s}, prev-state = %s. Setting state to FAILED",
_settings.getHost(), _settings.getPort(), getState()));
setState(State.FAILED);
}
for (Handler<ConnectionImpl> h : disconnectHandlers) {
h.handle(this);
}
});
_state = State.CONNECTED;
}
write();
}
void setState(State state) {
synchronized (_lock) {
_state = state;
}
}
@Override
public boolean isOpen() {
synchronized (_lock) {
return _state == State.CONNECTED && protonConnection.getLocalState() == EndpointState.ACTIVE
&& protonConnection.getRemoteState() == EndpointState.ACTIVE;
}
}
State getState() {
synchronized (_lock) {
return _state;
}
}
@Override
public boolean isInbound() {
return _isInbound;
}
@Override
public void close() {
setState(State.CLOSED);
protonConnection.close();
}
protected void processEvents() {
protonConnection.collect(_collector);
Event event = _collector.peek();
while (event != null) {
switch (event.getType()) {
case CONNECTION_REMOTE_OPEN:
AmqpEventImpl amqpEvent = new AmqpEventImpl(EventType.CONNECTION_READY);
amqpEvent.setConnection(this);
_eventHandler.handle(amqpEvent);
break;
case CONNECTION_FINAL:
amqpEvent = new AmqpEventImpl(EventType.CONNECTION_FINAL);
amqpEvent.setConnection(this);
_eventHandler.handle(amqpEvent);
break;
case SESSION_REMOTE_OPEN:
Session ssn;
org.apache.qpid.proton.engine.Session amqpSsn = event.getSession();
if (amqpSsn.getContext() != null) {
ssn = (Session) amqpSsn.getContext();
} else {
ssn = new SessionImpl(this, amqpSsn);
amqpSsn.setContext(ssn);
event.getSession().open();
}
amqpEvent = new AmqpEventImpl(EventType.SESSION_READY);
amqpEvent.setConnection(this);
amqpEvent.setSession(ssn);
_eventHandler.handle(amqpEvent);
break;
case SESSION_FINAL:
ssn = (Session) event.getSession().getContext();
amqpEvent = new AmqpEventImpl(EventType.SESSION_FINAL);
amqpEvent.setConnection(this);
amqpEvent.setSession(ssn);
_eventHandler.handle(amqpEvent);
break;
case LINK_REMOTE_OPEN:
Link link = event.getLink();
if (link instanceof Receiver) {
IncomingLinkImpl inboundLink;
SessionImpl session = (SessionImpl) link.getSession().getContext();
if (link.getContext() != null) {
inboundLink = (IncomingLinkImpl) link.getContext();
} else {
inboundLink = new IncomingLinkImpl(session, link.getRemoteTarget().getAddress(), link,
ReliabilityMode.AT_LEAST_ONCE, CreditMode.AUTO);
link.setContext(inboundLink);
inboundLink.init();
}
amqpEvent = new AmqpEventImpl(EventType.INCOMING_LINK_READY);
amqpEvent.setConnection(this);
amqpEvent.setSession(session);
amqpEvent.setLink(inboundLink);
_eventHandler.handle(amqpEvent);
} else {
OutgoingLinkImpl outboundLink;
SessionImpl session = (SessionImpl) link.getSession().getContext();
if (link.getContext() != null) {
outboundLink = (OutgoingLinkImpl) link.getContext();
} else {
outboundLink = new OutgoingLinkImpl(session, link.getRemoteSource().getAddress(), link);
link.setContext(outboundLink);
outboundLink.init();
}
amqpEvent = new AmqpEventImpl(EventType.OUTGOING_LINK_READY);
amqpEvent.setConnection(this);
amqpEvent.setSession(session);
amqpEvent.setLink(outboundLink);
_eventHandler.handle(amqpEvent);
}
break;
case LINK_FLOW:
link = event.getLink();
if (link instanceof Sender) {
OutgoingLink outboundLink = (OutgoingLink) link.getContext();
amqpEvent = new AmqpEventImpl(EventType.OUTGOING_LINK_CREDIT);
amqpEvent.setConnection(this);
amqpEvent.setSession((SessionImpl) link.getSession().getContext());
amqpEvent.setLink(outboundLink);
_eventHandler.handle(amqpEvent);
}
break;
case LINK_FINAL:
link = event.getLink();
if (link instanceof Receiver) {
IncomingLink inboundLink = (IncomingLink) link.getContext();
amqpEvent = new AmqpEventImpl(EventType.INCOMING_LINK_FINAL);
amqpEvent.setConnection(this);
amqpEvent.setSession((SessionImpl) link.getSession().getContext());
amqpEvent.setLink(inboundLink);
_eventHandler.handle(amqpEvent);
} else {
OutgoingLinkImpl outboundLink = (OutgoingLinkImpl) link.getContext();
amqpEvent = new AmqpEventImpl(EventType.OUTGOING_LINK_FINAL);
amqpEvent.setConnection(this);
amqpEvent.setSession((SessionImpl) link.getSession().getContext());
amqpEvent.setLink(outboundLink);
_eventHandler.handle(amqpEvent);
}
break;
case TRANSPORT:
// TODO
break;
case DELIVERY:
onDelivery(event.getDelivery());
break;
default:
break;
}
_collector.pop();
event = _collector.peek();
}
}
void onDelivery(Delivery d) {
Link link = d.getLink();
if (link instanceof Receiver) {
if (d.isPartial()) {
return;
}
Receiver receiver = (Receiver) link;
byte[] bytes = new byte[d.pending()];
int read = receiver.recv(bytes, 0, bytes.length);
Message pMsg = Proton.message();
pMsg.decode(bytes, 0, read);
receiver.advance();
IncomingLinkImpl inLink = (IncomingLinkImpl) link.getContext();
SessionImpl ssn = inLink.getSession();
AmqpMessageImpl msg = new InboundMessage(ssn.getID(), d.getTag(), ssn.getNextIncommingSequence(),
d.isSettled(), pMsg);
try {
AmqpEventImpl amqpEvent = new AmqpEventImpl(EventType.MESSAGE_RECEIVED);
amqpEvent.setConnection(this);
amqpEvent.setSession((SessionImpl) link.getSession().getContext());
amqpEvent.setLink(inLink);
amqpEvent.setMessage(msg);
_eventHandler.handle(amqpEvent);
} catch (Exception e) {
_logger.warn("Unexpected error while routing inbound message into event bus", e);
}
} else {
if (d.remotelySettled()) {
TrackerImpl tracker = (TrackerImpl) d.getContext();
tracker.setDisposition(d.getRemoteState());
tracker.markSettled();
AmqpEventImpl amqpEvent = new AmqpEventImpl(EventType.MESSAGE_SETTLED);
amqpEvent.setConnection(this);
amqpEvent.setSession((SessionImpl) d.getLink().getSession().getContext());
amqpEvent.setLink((io.vertx.ext.amqp.impl.protocol.Link) d.getLink());
amqpEvent.setTracker(tracker);
_eventHandler.handle(amqpEvent);
}
}
}
void write() {
synchronized (_lock) {
if (_state != State.CONNECTED) {
if (_logger.isDebugEnabled()) {
_logger.debug(String.format("Connection {%s:%s}, state = %s. Returning without writing",
_settings.getHost(), _settings.getPort(), _state));
}
return;
}
}
if (_socket.writeQueueFull()) {
System.out.println("Socket buffers are full for " + this);
} else {
ByteBuffer b = _transport.getOutputBuffer();
while (b.remaining() > 0) {
// TODO: what is the optimal solution here?
byte[] data = new byte[b.remaining()];
b.get(data);
_socket.write(Buffer.buffer(data));
_transport.outputConsumed();
b = _transport.getOutputBuffer();
}
}
}
@Override
public String toString() {
return _toString;
}
}