/** * 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.meta.JmsResource; import io.hawtjms.provider.AsyncResult; import java.io.IOException; import javax.jms.JMSException; import javax.jms.JMSSecurityException; import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.transport.AmqpError; import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.engine.Endpoint; import org.apache.qpid.proton.engine.EndpointState; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Abstract base for all AmqpResource implementations to extend. * * This abstract class wraps up the basic state management bits so that the concrete * object don't have to reproduce it. Provides hooks for the subclasses to initialize * and shutdown. */ public abstract class AbstractAmqpResource<R extends JmsResource, E extends Endpoint> implements AmqpResource { private static final Logger LOG = LoggerFactory.getLogger(AbstractAmqpResource.class); protected AsyncResult<Void> openRequest; protected AsyncResult<Void> closeRequest; protected E endpoint; protected R info; /** * Creates a new AbstractAmqpResource instance with the JmsResource provided, and * sets the Endpoint to null. * * @param info * The JmsResource instance that this AmqpResource is managing. */ public AbstractAmqpResource(R info) { this(info, null); } /** * Creates a new AbstractAmqpResource instance with the JmsResource provided, and * sets the Endpoint to the given value. * * @param info * The JmsResource instance that this AmqpResource is managing. * @param endpoint * The Proton Endpoint instance that this object maps to. */ public AbstractAmqpResource(R info, E endpoint) { this.info = info; this.endpoint = endpoint; } @Override public void open(AsyncResult<Void> request) { this.openRequest = request; doOpen(); this.endpoint.setContext(this); this.endpoint.open(); } @Override public boolean isOpen() { return this.endpoint.getRemoteState() == EndpointState.ACTIVE; } @Override public boolean isAwaitingOpen() { return this.openRequest != null; } @Override public void opened() { if (this.openRequest != null) { this.openRequest.onSuccess(); this.openRequest = null; } } @Override public void close(AsyncResult<Void> request) { // If already closed signal success or else the caller might never get notified. if (endpoint.getLocalState() == EndpointState.CLOSED && endpoint.getRemoteState() == EndpointState.CLOSED) { request.onSuccess(); } this.closeRequest = request; doClose(); this.endpoint.close(); } @Override public boolean isClosed() { return this.endpoint.getRemoteState() == EndpointState.CLOSED; } @Override public boolean isAwaitingClose() { return this.closeRequest != null; } @Override public void closed() { if (this.closeRequest != null) { this.closeRequest.onSuccess(); this.closeRequest = null; } } @Override public void failed() { failed(new JMSException("Remote request failed.")); } @Override public void failed(Exception cause) { if (openRequest != null) { openRequest.onFailure(cause); openRequest = null; } if (closeRequest != null) { closeRequest.onFailure(cause); closeRequest = null; } } public E getEndpoint() { return this.endpoint; } public R getJmsResource() { return this.info; } public EndpointState getLocalState() { if (endpoint == null) { return EndpointState.UNINITIALIZED; } return this.endpoint.getLocalState(); } public EndpointState getRemoteState() { if (endpoint == null) { return EndpointState.UNINITIALIZED; } return this.endpoint.getRemoteState(); } @Override public Exception getRemoteError() { String message = getRemoteErrorMessage(); Exception remoteError = null; Symbol error = endpoint.getRemoteCondition().getCondition(); if (error.equals(AmqpError.UNAUTHORIZED_ACCESS)) { remoteError = new JMSSecurityException(message); } else { remoteError = new JMSException(message); } return remoteError; } @Override public String getRemoteErrorMessage() { String message = "Received unkown error from remote peer"; if (endpoint.getRemoteCondition() != null) { ErrorCondition error = endpoint.getRemoteCondition(); if (error.getDescription() != null && !error.getDescription().isEmpty()) { message = error.getDescription(); } } return message; } @Override public void processStateChange() throws IOException { EndpointState remoteState = endpoint.getRemoteState(); if (remoteState == EndpointState.ACTIVE) { if (isAwaitingOpen()) { LOG.debug("Link {} is now open: ", this); opened(); } // Should not receive an ACTIVE event if not awaiting the open state. } else if (remoteState == EndpointState.CLOSED) { if (isAwaitingClose()) { LOG.debug("Link {} is now closed: ", this); closed(); } else if (isAwaitingOpen()) { // Error on Open, create exception and signal failure. LOG.warn("Open of link {} failed: ", this); Exception remoteError = this.getRemoteError(); failed(remoteError); } else { // TODO - Handle remote asynchronous close. } } } @Override public void processDeliveryUpdates() throws IOException { } @Override public void processFlowUpdates() throws IOException { } @Override public void processUpdates() throws IOException { } protected abstract void doOpen(); protected abstract void doClose(); }