/* * 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.apache.qpid.jms.provider.amqp; import java.io.IOException; import java.util.concurrent.ScheduledFuture; import org.apache.qpid.jms.JmsOperationTimedOutException; import org.apache.qpid.jms.meta.JmsConnectionInfo; import org.apache.qpid.jms.meta.JmsResource; import org.apache.qpid.jms.meta.JmsResource.ResourceState; import org.apache.qpid.jms.provider.AsyncResult; import org.apache.qpid.proton.engine.Delivery; 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. * * @param <R> The JmsResource type that describe this resource. * @param <E> The AMQP Endpoint that this resource encapsulates. */ public abstract class AmqpAbstractResource<R extends JmsResource, E extends Endpoint> implements AmqpResource { private static final Logger LOG = LoggerFactory.getLogger(AmqpAbstractResource.class); protected AsyncResult closeRequest; protected ScheduledFuture<?> closeTimeoutTask; private final E endpoint; private final R resourceInfo; private final AmqpResourceParent parent; /** * Creates a new instance with the JmsResource provided, and sets the Endpoint to the given value. * * @param resourceInfo * The JmsResource instance that this AmqpResource is managing. * @param endpoint * The Proton Endpoint instance that this object maps to. */ public AmqpAbstractResource(R resourceInfo, E endpoint) { this(resourceInfo, endpoint, null); } /** * Creates a new instance with the JmsResource provided, and sets the Endpoint to the given value. * * @param resourceInfo * The JmsResource instance that this AmqpResource is managing. * @param endpoint * The Proton Endpoint instance that this object maps to. * @param parent * The parent of this resource (null if no parent). */ public AmqpAbstractResource(R resourceInfo, E endpoint, AmqpResourceParent parent) { this.parent = parent; this.endpoint = endpoint; this.resourceInfo = resourceInfo; this.resourceInfo.getId().setProviderHint(this); } public void close(AsyncResult request) { if (parent != null) { parent.removeChildResource(this); } resourceInfo.setState(ResourceState.CLOSED); // If already closed signal success or else the caller might never get notified. if (getEndpoint().getLocalState() == EndpointState.CLOSED || getEndpoint().getRemoteState() == EndpointState.CLOSED) { // Remote already closed this resource, close locally and free. if (getEndpoint().getLocalState() != EndpointState.CLOSED) { getEndpoint().close(); getEndpoint().free(); getEndpoint().setContext(null); } request.onSuccess(); return; } LOG.trace("{} requesting close on remote.", this); closeRequest = request; // Use close timeout for all resource closures and fallback to the request // timeout if the close timeout was not set long closeTimeout = getParent().getProvider().getCloseTimeout(); if (closeTimeout == JmsConnectionInfo.INFINITE) { closeTimeout = getParent().getProvider().getRequestTimeout(); } if (closeTimeout != JmsConnectionInfo.INFINITE) { closeTimeoutTask = getParent().getProvider().scheduleRequestTimeout( new AsyncResult() { @Override public void onSuccess() { // Not called in this context. } @Override public void onFailure(Throwable result) { closeResource(getParent().getProvider(), result, false); } @Override public boolean isComplete() { return closeRequest != null ? closeRequest.isComplete() : true; } }, closeTimeout, new JmsOperationTimedOutException("Timed Out Waiting for close response: " + this)); } closeOrDetachEndpoint(); } public void closeResource(AmqpProvider provider, Throwable cause, boolean remotelyClosed) { if (parent != null) { parent.removeChildResource(this); } resourceInfo.setState(ResourceState.CLOSED); if (getEndpoint() != null) { // TODO: if this is a producer/consumer link then we may only be detached, // rather than fully closed, and should respond appropriately. closeOrDetachEndpoint(); // Process the close before moving on to closing down child resources provider.pumpToProtonTransport(); handleResourceClosure(provider, cause); // Now clean up after the close has completed if the close is not initiated // from this client, otherwise we need to wait for the remote to respond. if (remotelyClosed) { getEndpoint().free(); getEndpoint().setContext(null); } } if (closeTimeoutTask != null) { closeTimeoutTask.cancel(true); closeTimeoutTask = null; } if (isAwaitingClose()) { LOG.debug("{} is now closed: ", this); if (cause == null) { closeRequest.onSuccess(); } else { closeRequest.onFailure(cause); } closeRequest = null; } else { if (cause != null) { if (getResourceInfo() instanceof JmsConnectionInfo) { provider.fireProviderException(cause); } else { provider.fireResourceClosed(getResourceInfo(), cause); } } } } public void handleResourceClosure(AmqpProvider provider, Throwable error) { // Nothing do be done here, subclasses can override as needed. } /** * Perform the close operation on the managed endpoint. A subclass may * override this method to alter the standard close path such as endpoint * detach etc. */ protected void closeOrDetachEndpoint() { getEndpoint().close(); } //----- Access methods ---------------------------------------------------// public E getEndpoint() { return endpoint; } public R getResourceInfo() { return resourceInfo; } public AmqpResourceParent getParent() { return parent; } //----- Endpoint state access methods ------------------------------------// public boolean isOpen() { return getEndpoint().getRemoteState() == EndpointState.ACTIVE; } public boolean isClosed() { return getEndpoint().getLocalState() == EndpointState.CLOSED; } public boolean isAwaitingClose() { return closeRequest != null; } public EndpointState getLocalState() { if (getEndpoint() == null) { return EndpointState.UNINITIALIZED; } return getEndpoint().getLocalState(); } public EndpointState getRemoteState() { if (getEndpoint() == null) { return EndpointState.UNINITIALIZED; } return getEndpoint().getRemoteState(); } //----- AmqpResource implementation --------------------------------------// @Override public final void processRemoteOpen(AmqpProvider provider) throws IOException { // Open is handled by the resource builder } @Override public void processRemoteDetach(AmqpProvider provider) throws IOException { processRemoteClose(provider); } @Override public void processRemoteClose(AmqpProvider provider) throws IOException { if (isAwaitingClose()) { closeResource(provider, null, true); // Close was expected so ignore any endpoint errors. } else { closeResource(provider, AmqpSupport.convertToException(provider, getEndpoint(), getEndpoint().getRemoteCondition()), true); } } @Override public void processDeliveryUpdates(AmqpProvider provider, Delivery delivery) throws IOException { // Nothing do be done here, subclasses can override as needed. } @Override public void processFlowUpdates(AmqpProvider provider) throws IOException { // Nothing do be done here, subclasses can override as needed. } }