
org.proton.plug.context.server.ProtonServerSenderContext Maven / Gradle / Ivy
The newest version!
/*
* 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.proton.plug.context.server;
import java.util.Map;
import org.apache.activemq.artemis.selector.filter.FilterException;
import org.apache.activemq.artemis.selector.impl.SelectorParser;
import org.apache.qpid.proton.amqp.DescribedType;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.Modified;
import org.apache.qpid.proton.amqp.messaging.Outcome;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.messaging.Released;
import org.apache.qpid.proton.amqp.messaging.Source;
import org.apache.qpid.proton.amqp.messaging.TerminusDurability;
import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy;
import org.apache.qpid.proton.amqp.transaction.TransactionalState;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.DeliveryState;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.message.ProtonJMessage;
import org.jboss.logging.Logger;
import org.proton.plug.AMQPSessionCallback;
import org.proton.plug.context.AbstractConnectionContext;
import org.proton.plug.context.AbstractProtonContextSender;
import org.proton.plug.context.AbstractProtonSessionContext;
import org.proton.plug.context.ProtonPlugSender;
import org.proton.plug.exceptions.ActiveMQAMQPException;
import org.proton.plug.exceptions.ActiveMQAMQPInternalErrorException;
import org.proton.plug.exceptions.ActiveMQAMQPNotFoundException;
import org.proton.plug.logger.ActiveMQAMQPProtocolMessageBundle;
import static org.proton.plug.AmqpSupport.JMS_SELECTOR_FILTER_IDS;
import static org.proton.plug.AmqpSupport.findFilter;
public class ProtonServerSenderContext extends AbstractProtonContextSender implements ProtonPlugSender {
private static final Logger log = Logger.getLogger(ProtonServerSenderContext.class);
private static final Symbol SELECTOR = Symbol.getSymbol("jms-selector");
private static final Symbol COPY = Symbol.valueOf("copy");
private static final Symbol TOPIC = Symbol.valueOf("topic");
private Object brokerConsumer;
public ProtonServerSenderContext(AbstractConnectionContext connection,
Sender sender,
AbstractProtonSessionContext protonSession,
AMQPSessionCallback server) {
super(connection, sender, protonSession, server);
}
public Object getBrokerConsumer() {
return brokerConsumer;
}
@Override
public void onFlow(int currentCredits, boolean drain) {
super.onFlow(currentCredits, drain);
sessionSPI.onFlowConsumer(brokerConsumer, currentCredits, drain);
}
/*
* start the session
* */
@Override
public void start() throws ActiveMQAMQPException {
super.start();
// protonSession.getServerSession().start();
//todo add flow control
try {
// to do whatever you need to make the broker start sending messages to the consumer
//this could be null if a link reattach has happened
if (brokerConsumer != null) {
sessionSPI.startSender(brokerConsumer);
}
//protonSession.getServerSession().receiveConsumerCredits(consumerID, -1);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorStartingConsumer(e.getMessage());
}
}
/**
* create the actual underlying ActiveMQ Artemis Server Consumer
*/
@Override
public void initialise() throws Exception {
super.initialise();
Source source = (Source) sender.getRemoteSource();
String queue;
String selector = null;
/*
* even tho the filter is a map it will only return a single filter unless a nolocal is also provided
* */
if (source != null) {
Map.Entry filter = findFilter(source.getFilter(), JMS_SELECTOR_FILTER_IDS);
if (filter != null) {
selector = filter.getValue().getDescribed().toString();
// Validate the Selector.
try {
SelectorParser.parse(selector);
}
catch (FilterException e) {
close(new ErrorCondition(AmqpError.INVALID_FIELD, e.getMessage()));
return;
}
}
}
/*
* if we have a capability for a topic (qpid-jms) or we are configured on this address to act like a topic then act
* like a subscription.
* */
boolean isPubSub = hasCapabilities(TOPIC, source) || isPubSub(source);
//filter = findFilter(source.getFilter(), NO_LOCAL_FILTER_IDS);
//if (filter != null) {
//todo implement nolocal filter
//}
if (source == null) {
// Attempt to recover a previous subscription happens when a link reattach happens on a subscription queue
String clientId = connection.getRemoteContainer();
String pubId = sender.getName();
queue = clientId + ":" + pubId;
boolean exists = sessionSPI.queueQuery(queue);
/*
* If it exists then we know it is a subscription so we set the capabilities on the source so we can delete on a
* link remote close.
* */
if (exists) {
source = new org.apache.qpid.proton.amqp.messaging.Source();
source.setAddress(queue);
source.setDurable(TerminusDurability.UNSETTLED_STATE);
source.setExpiryPolicy(TerminusExpiryPolicy.NEVER);
source.setDistributionMode(COPY);
source.setCapabilities(TOPIC);
sender.setSource(source);
}
else {
sender.setCondition(new ErrorCondition(AmqpError.NOT_FOUND, "Unknown subscription link: " + sender.getName()));
sender.close();
}
}
else {
if (source.getDynamic()) {
//if dynamic we have to create the node (queue) and set the address on the target, the node is temporary and
// will be deleted on closing of the session
queue = java.util.UUID.randomUUID().toString();
try {
sessionSPI.createTemporaryQueue(queue);
//protonSession.getServerSession().createQueue(queue, queue, null, true, false);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
source.setAddress(queue);
}
else {
//if not dynamic then we use the targets address as the address to forward the messages to, however there has to
//be a queue bound to it so we nee to check this.
if (isPubSub) {
// if we are a subscription and durable create a durable queue using the container id and link name
if (TerminusDurability.UNSETTLED_STATE.equals(source.getDurable()) ||
TerminusDurability.CONFIGURATION.equals(source.getDurable())) {
String clientId = connection.getRemoteContainer();
String pubId = sender.getName();
queue = clientId + ":" + pubId;
boolean exists = sessionSPI.queueQuery(queue);
if (!exists) {
sessionSPI.createDurableQueue(source.getAddress(), queue);
}
}
//otherwise we are a volatile subscription
else {
queue = java.util.UUID.randomUUID().toString();
try {
sessionSPI.createTemporaryQueue(source.getAddress(), queue);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingTemporaryQueue(e.getMessage());
}
source.setAddress(queue);
}
}
else {
queue = source.getAddress();
}
if (queue == null) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressNotSet();
}
try {
if (!sessionSPI.queueQuery(queue)) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.sourceAddressDoesntExist();
}
}
catch (ActiveMQAMQPNotFoundException e) {
throw e;
}
catch (Exception e) {
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
}
boolean browseOnly = !isPubSub && source.getDistributionMode() != null && source.getDistributionMode().equals(COPY);
try {
brokerConsumer = sessionSPI.createSender(this, queue, selector, browseOnly);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCreatingConsumer(e.getMessage());
}
}
}
private boolean isPubSub(Source source) {
String pubSubPrefix = sessionSPI.getPubSubPrefix();
return source != null && pubSubPrefix != null && source.getAddress() != null && source.getAddress().startsWith(pubSubPrefix);
}
/*
* close the session
* */
@Override
public void close(ErrorCondition condition) throws ActiveMQAMQPException {
super.close(condition);
try {
sessionSPI.closeSender(brokerConsumer);
}
catch (Exception e) {
log.warn(e.getMessage(), e);
throw new ActiveMQAMQPInternalErrorException(e.getMessage());
}
}
/*
* close the session
* */
@Override
public void close(boolean remoteLinkClose) throws ActiveMQAMQPException {
super.close(remoteLinkClose);
try {
sessionSPI.closeSender(brokerConsumer);
//if this is a link close rather than a connection close or detach, we need to delete any durable resources for
// say pub subs
if (remoteLinkClose ) {
Source source = (Source)sender.getSource();
if (source != null && source.getAddress() != null && hasCapabilities(TOPIC, source)) {
String address = source.getAddress();
boolean exists = sessionSPI.queueQuery(address);
if (exists) {
sessionSPI.deleteQueue(address);
}
}
}
}
catch (Exception e) {
log.warn(e.getMessage(), e);
throw new ActiveMQAMQPInternalErrorException(e.getMessage());
}
}
@Override
public void onMessage(Delivery delivery) throws ActiveMQAMQPException {
Object message = delivery.getContext();
boolean preSettle = sender.getRemoteSenderSettleMode() == SenderSettleMode.SETTLED;
DeliveryState remoteState = delivery.getRemoteState();
if (remoteState != null) {
// If we are transactional then we need ack if the msg has been accepted
if (remoteState instanceof TransactionalState) {
TransactionalState txState = (TransactionalState) remoteState;
if (txState.getOutcome() != null) {
Outcome outcome = txState.getOutcome();
if (outcome instanceof Accepted) {
if (!delivery.remotelySettled()) {
TransactionalState txAccepted = new TransactionalState();
txAccepted.setOutcome(Accepted.getInstance());
txAccepted.setTxnId(txState.getTxnId());
delivery.disposition(txAccepted);
}
//we have to individual ack as we can't guarantee we will get the delivery updates (including acks) in order
// from dealer, a perf hit but a must
try {
sessionSPI.ack(brokerConsumer, message);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorAcknowledgingMessage(message.toString(), e.getMessage());
}
}
}
}
else if (remoteState instanceof Accepted) {
//we have to individual ack as we can't guarantee we will get the delivery updates (including acks) in order
// from dealer, a perf hit but a must
try {
sessionSPI.ack(brokerConsumer, message);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorAcknowledgingMessage(message.toString(), e.getMessage());
}
}
else if (remoteState instanceof Released) {
try {
sessionSPI.cancel(brokerConsumer, message, false);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCancellingMessage(message.toString(), e.getMessage());
}
}
else if (remoteState instanceof Rejected || remoteState instanceof Modified) {
try {
sessionSPI.cancel(brokerConsumer, message, true);
}
catch (Exception e) {
throw ActiveMQAMQPProtocolMessageBundle.BUNDLE.errorCancellingMessage(message.toString(), e.getMessage());
}
}
//todo add tag caching
if (!preSettle) {
protonSession.replaceTag(delivery.getTag());
}
synchronized (connection.getLock()) {
delivery.settle();
sender.offer(1);
}
}
else {
//todo not sure if we need to do anything here
}
}
@Override
public synchronized void checkState() {
super.checkState();
sessionSPI.resumeDelivery(brokerConsumer);
}
/**
* handle an out going message from ActiveMQ Artemis, send via the Proton Sender
*/
@Override
public int deliverMessage(Object message, int deliveryCount) throws Exception {
if (closed) {
System.err.println("Message can't be delivered as it's closed");
return 0;
}
//encode the message
ProtonJMessage serverMessage;
try {
// This can be done a lot better here
serverMessage = sessionSPI.encodeMessage(message, deliveryCount);
}
catch (Throwable e) {
log.warn(e.getMessage(), e);
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
return performSend(serverMessage, message);
}
private static boolean hasCapabilities(Symbol symbol, Source source) {
if (source != null) {
if (source.getCapabilities() != null) {
for (Symbol cap : source.getCapabilities()) {
if (symbol.equals(cap)) {
return true;
}
}
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy