All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.ejb.client.remoting.ChannelAssociation Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 35.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.ejb.client.remoting;

import static org.jboss.ejb.client.remoting.Protocol.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.ejb.client.EJBClientContext;
import org.jboss.ejb.client.EJBReceiverContext;
import org.jboss.ejb.client.EJBReceiverInvocationContext;
import org.jboss.ejb.client.Logs;
import org.jboss.logging.Logger;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.remoting3.Channel;
import org.jboss.remoting3.CloseHandler;
import org.jboss.remoting3.MessageInputStream;
import org.jboss.remoting3.MessageOutputStream;
import org.jboss.remoting3.RemotingOptions;
import org.xnio.FutureResult;

/**
 * A {@link ChannelAssociation} keeps track of the association between the {@link EJBReceiverContext}
 * of a {@link RemotingConnectionEJBReceiver} and the {@link Channel} on which the communication is going to
 * happen for that particular association.
 * 

* The {@link RemotingConnectionEJBReceiver} uses a {@link ChannelAssociation} to send/receive messages to/from * the remote server *

* User: Jaikiran Pai */ class ChannelAssociation { private static final Logger logger = Logger.getLogger(ChannelAssociation.class); private final RemotingConnectionEJBReceiver ejbReceiver; private final EJBReceiverContext ejbReceiverContext; private final Channel channel; private final byte protocolVersion; private final MarshallerFactory marshallerFactory; private final AtomicInteger nextInvocationId = new AtomicInteger(0); private final Map waitingMethodInvocations = Collections.synchronizedMap(new HashMap()); private final Map> waitingFutureResults = Collections.synchronizedMap(new HashMap>()); private final ReconnectHandler reconnectHandler; // A semaphore which will be used to acquire a lock while writing out to a channel // to make sure that only a limited number of simultaneous writes are allowed private final Semaphore channelWriteSemaphore; // Keeps track of the invocation ids for each of the EJB receiver invocation contexts private final Map invocationIdsPerReceiverInvocationCtx = Collections.synchronizedMap(new IdentityHashMap()); private final String remotingProtocol; /** * Creates a channel association for the passed {@link EJBReceiverContext} and the {@link Channel} * * @param ejbReceiver The EJB receiver * @param ejbReceiverContext The receiver context * @param channel The channel that will be used for remoting communication * @param protocolVersion The protocol version * @param marshallerFactory The marshalling factory * @param reconnectHandler The reconnect handler to use for broken connections/channels. Can be null. * @param remotingProtocol */ ChannelAssociation(final RemotingConnectionEJBReceiver ejbReceiver, final EJBReceiverContext ejbReceiverContext, final Channel channel, final byte protocolVersion, final MarshallerFactory marshallerFactory, final ReconnectHandler reconnectHandler, final String remotingProtocol) { this.ejbReceiver = ejbReceiver; this.ejbReceiverContext = ejbReceiverContext; this.channel = channel; this.protocolVersion = protocolVersion; this.marshallerFactory = marshallerFactory; this.reconnectHandler = reconnectHandler; this.remotingProtocol = remotingProtocol; this.channel.addCloseHandler(new CloseHandler() { @Override public void handleClose(Channel closed, IOException exception) { logger.debugf(exception, "Closing channel %s", closed); // notify about the broken channel and do the necessary cleanups if (exception != null) { ChannelAssociation.this.notifyBrokenChannel(exception); } else { ChannelAssociation.this.notifyBrokenChannel(new IOException("Channel " + closed + " has been closed")); } } }); // register a receiver for receiving messages on the channel this.channel.receiveMessage(new ResponseReceiver()); // write semaphore Integer maxOutboundWrites = this.channel.getOption(RemotingOptions.MAX_OUTBOUND_MESSAGES); if (maxOutboundWrites == null) { maxOutboundWrites = 80; } this.channelWriteSemaphore = new Semaphore(maxOutboundWrites, true); } /** * Returns the channel on which the communication for this {@link ChannelAssociation association} * takes places * * @return */ Channel getChannel() { return this.channel; } /** * Returns the {@link EJBReceiverContext} applicable for this {@link ChannelAssociation} * * @return */ EJBReceiverContext getEjbReceiverContext() { return this.ejbReceiverContext; } /** * Returns the next invocation id that can be used for invocations on the channel corresponding to * this {@link ChannelAssociation association} * * @return */ short getNextInvocationId() { return (short) nextInvocationId.getAndIncrement(); } /** * Enroll a {@link EJBReceiverInvocationContext} to receive (an inherently asynchronous) response for a * invocation corresponding to the passed invocationId. This method does not block. * Whenever a result is available for the invocationId, the {@link EJBReceiverInvocationContext#resultReady(org.jboss.ejb.client.EJBReceiverInvocationContext.ResultProducer)} * will be invoked. * * @param invocationId The invocation id * @param ejbReceiverInvocationContext The receiver invocation context which will be used to send back a result when it's available */ void receiveResponse(final short invocationId, final EJBReceiverInvocationContext ejbReceiverInvocationContext) { this.waitingMethodInvocations.put(invocationId, ejbReceiverInvocationContext); // keep track of the invocation id for the EJB receiver invocation context this.invocationIdsPerReceiverInvocationCtx.put(ejbReceiverInvocationContext, invocationId); } /** * De-enroll a {@link EJBReceiverInvocationContext} from receiving (an inherently asynchronous) response for a * invocation corresponding to the passed invocationId. * This action is required when an invocation is retried on another node as a result of a NoSuchEJBException * and effectively cleans up the stale invocationID and EJBClientReceiverContext of the previous attempt. * . * @param invocationId */ void cleanupStaleResponse(final short invocationId) { if (this.waitingMethodInvocations.containsKey(invocationId)) { final EJBReceiverInvocationContext ejbReceiverInvocationContext = this.waitingMethodInvocations.remove(invocationId); if (ejbReceiverInvocationContext != null) { // we no longer need to keep track of the invocation id that was used for // this EJB receiver invocation context, so remove it this.invocationIdsPerReceiverInvocationCtx.remove(ejbReceiverInvocationContext); } } } /** * Returns a {@link Future} {@link EJBReceiverInvocationContext.ResultProducer result producer} for the * passed invocationId. This method does not block. The caller can use the returned * {@link Future} to {@link java.util.concurrent.Future#get() wait} for a {@link org.jboss.ejb.client.EJBReceiverInvocationContext.ResultProducer} * to be available. Once available, the {@link org.jboss.ejb.client.EJBReceiverInvocationContext.ResultProducer#getResult()} * can be used to obtain the result for the invocation corresponding to the passed invocationId * * @param invocationId The invocation id * @return */ Future enrollForResult(final short invocationId) { final FutureResult futureResult = new FutureResult(); this.waitingFutureResults.put(invocationId, futureResult); return IoFutureHelper.future(futureResult.getIoFuture()); } /** * Invoked when a result is ready for a (prior) invocation corresponding to the passed invocationId. * * @param invocationId The invocation id * @param resultProducer The result producer which will be used to get hold of the result */ void resultReady(final short invocationId, final EJBReceiverInvocationContext.ResultProducer resultProducer) { if (this.waitingMethodInvocations.containsKey(invocationId)) { final EJBReceiverInvocationContext ejbReceiverInvocationContext = this.waitingMethodInvocations.remove(invocationId); if (ejbReceiverInvocationContext != null) { // we no longer need to keep track of the invocation id that was used for // this EJB receiver invocation context, so remove it this.invocationIdsPerReceiverInvocationCtx.remove(ejbReceiverInvocationContext); // let the receiver invocation context know that the result is ready ejbReceiverInvocationContext.resultReady(resultProducer); } } else if (this.waitingFutureResults.containsKey(invocationId)) { final FutureResult future = this.waitingFutureResults.remove(invocationId); if (future != null) { future.setResult(resultProducer); } } else { Logs.REMOTING.discardingInvocationResult(invocationId); } } /** * Invoked when the server notifies the client that a (previous) method invocation corresponds to a method * marked as asynchronous. This method lets the {@link EJBReceiverInvocationContext} corresponding to the invocationId * know that it can {@link org.jboss.ejb.client.EJBReceiverInvocationContext#proceedAsynchronously() unblock the invocation} * * @param invocationId The invocation id */ void handleAsyncMethodNotification(final short invocationId) { final EJBReceiverInvocationContext receiverInvocationContext = this.waitingMethodInvocations.get(invocationId); if (receiverInvocationContext == null) { logger.debugf("No waiting context found for async method invocation with id %s", invocationId); return; } receiverInvocationContext.proceedAsynchronously(); } EJBReceiverInvocationContext getEJBReceiverInvocationContext(short invocationId) { return this.waitingMethodInvocations.get(invocationId); } /** * Opens and returns a new {@link MessageOutputStream} for the {@link Channel} represented * by this {@link ChannelAssociation}. Before opening the message outputstream, this method acquires a permit * to make sure only a limited number of simultaneous writes are allowed on the channel. The permit acquistion * is a blocking wait. * * @return * @throws Exception */ MessageOutputStream acquireChannelMessageOutputStream() throws IOException { try { this.channelWriteSemaphore.acquire(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new InterruptedIOException("Thread interrupted"); } try { return this.channel.writeMessage(); } catch (IOException e) { // release this.channelWriteSemaphore.release(); throw e; } } /** * Releases a previously held permit/lock on a message outputstream of a channel and also closes * the messageOutputStream * * @param messageOutputStream The message outputstream * @throws IOException */ void releaseChannelMessageOutputStream(final MessageOutputStream messageOutputStream) throws IOException { if (messageOutputStream == null) { return; } try { messageOutputStream.close(); } finally { this.channelWriteSemaphore.release(); } } /** * Returns the invocation id that was used for a prior invocation on this channel, for the * passed {@link EJBReceiverInvocationContext ejbReceiverInvocationContext}. This method returns * null if there was no prior invocation on this channel for the passed {@link EJBReceiverInvocationContext} * or if the prior invocation has already completed * * @param ejbReceiverInvocationContext The EJB receiver invocation context * @return */ Short getInvocationId(final EJBReceiverInvocationContext ejbReceiverInvocationContext) { return this.invocationIdsPerReceiverInvocationCtx.get(ejbReceiverInvocationContext); } /** * Returns a {@link ProtocolMessageHandler} for the passed message header. Returns * null if there's no such {@link ProtocolMessageHandler}. * * @param header The message header * @return */ private ProtocolMessageHandler getProtocolMessageHandler(final byte header) { switch (header) { // Please try and maintain the case statements in the numerical order of the headers for the sake of quickly finding the highest known response header at present. case HEADER_SESSION_OPEN_RESPONSE_MESSAGE: return new SessionOpenResponseHandler(this, this.marshallerFactory); case HEADER_INVOCATION_RESPONSE_MESSAGE: return new MethodInvocationResponseHandler(this, this.marshallerFactory); case HEADER_INVOCATION_EXCEPTION_MESSAGE: return new InvocationExceptionResponseHandler(this, this.marshallerFactory); case HEADER_MODULE_AVAILABILITY1_MESSAGE: return new ModuleAvailabilityMessageHandler(this.ejbReceiver, this.ejbReceiverContext, ModuleAvailabilityMessageHandler.ModuleReportType.MODULE_AVAILABLE); case HEADER_MODULE_AVAILABILITY2_MESSAGE: return new ModuleAvailabilityMessageHandler(this.ejbReceiver, this.ejbReceiverContext, ModuleAvailabilityMessageHandler.ModuleReportType.MODULE_UNAVAILABLE); case HEADER_NO_SUCH_EJB_MESSAGE: return new NoSuchEJBExceptionResponseHandler(this); case HEADER_INVOCATION_GENERAL_FAILURE1_MESSAGE: return new GeneralInvocationFailureResponseHandler(this); case HEADER_INVOCATION_GENERAL_FAILURE2_MESSAGE: return new GeneralInvocationFailureResponseHandler(this); case HEADER_NON_STATEFUL_OPEN_MESSAGE: return new NonStatefulBeanSessionOpenFailureHandler(this); case HEADER_ASYNC_METHOD_MESSAGE: return new AsyncMethodNotificationHandler(this); case HEADER_TX_INVOCATION_RESPONSE_MESSAGE: return new TransactionInvocationResponseHandler(this); case HEADER_CLUSTER_COMPLETE_MESSAGE: return new ClusterTopologyMessageHandler(this); case HEADER_CLUSTER_REMOVAL_MESSAGE: return new ClusterRemovalMessageHandler(this.ejbReceiverContext); case HEADER_CLUSTER_NODE_ADD_MESSAGE: return new ClusterTopologyMessageHandler(this); case HEADER_CLUSTER_NODE_REMOVE_MESSAGE: return new ClusterNodeRemovalHandler(this); case HEADER_TX_RECOVER_RESPONSE_MESSAGE: // transaction recovery response return new TransactionRecoveryResponseHandler(this, this.marshallerFactory); case HEADER_COMPRESSED_INVOCATION_DATA_MESSAGE: // compressed data (response) return new CompressedMessageHandler(this); default: return null; } } void processResponse(final InputStream inputStream) throws IOException { // get the header in the message final int header = inputStream.read(); if (logger.isTraceEnabled()) { logger.trace("Received message with header 0x" + Integer.toHexString(header)); } // get the protocol message handler for the header final ProtocolMessageHandler messageHandler = getProtocolMessageHandler((byte) header); if (messageHandler == null) { if (logger.isDebugEnabled()) { logger.debug("Unsupported message received with header 0x" + Integer.toHexString(header)); } return; } // let the protocol handler process the stream messageHandler.processMessage(inputStream); } boolean isMessageCompatibleForNegotiatedProtocolVersion(final byte messageHeader) { if (protocolVersion >= 1) { switch (messageHeader) { case HEADER_SESSION_OPEN_REQUEST_MESSAGE: case HEADER_INVOCATION_REQUEST_MESSAGE: case HEADER_INVOCATION_CANCEL_MESSAGE: case HEADER_TX_COMMIT_MESSAGE: case HEADER_TX_ROLLBACK_MESSAGE: case HEADER_TX_PREPARE_MESSAGE: case HEADER_TX_FORGET_MESSAGE: case HEADER_TX_BEFORE_COMPLETION_MESSAGE: return true; } if (protocolVersion >= 2) { switch (messageHeader) { case HEADER_TX_RECOVER_REQUEST_MESSAGE: case HEADER_COMPRESSED_INVOCATION_DATA_MESSAGE: return true; } } } return false; } int getNegotiatedProtocolVersion() { return this.protocolVersion; } private void notifyBrokenChannel(final IOException ioException) { if (ioException == null) { throw new IllegalArgumentException("Exception cannot be null"); } try { final EJBReceiverInvocationContext.ResultProducer unusableChannelResultProducer = new UnusableChannelResultProducer(ioException); // notify waiting method invocations final Collection receiverInvocationContexts = this.waitingMethodInvocations.values(); // @see javadoc of Collections.synchronizedMap() synchronized (this.waitingMethodInvocations) { for (final EJBReceiverInvocationContext receiverInvocationContext : receiverInvocationContexts) { receiverInvocationContext.resultReady(unusableChannelResultProducer); } } // notify the other waiting invocations final Collection> futureResults = this.waitingFutureResults.values(); // @see javadoc of Collections.synchronizedMap() synchronized (this.waitingFutureResults) { for (final FutureResult resultProducerFutureResult : futureResults) { resultProducerFutureResult.setResult(unusableChannelResultProducer); } } } finally { // close the receiver context this.ejbReceiverContext.close(); // register a re-connect handler (if available) to the EJB client context if (this.reconnectHandler != null) { final EJBClientContext ejbClientContext = this.ejbReceiverContext.getClientContext(); if (logger.isDebugEnabled()) { logger.debug("Registering a re-connect handler " + this.reconnectHandler + " for broken channel " + this.channel + " in EJB client context " + ejbClientContext); } ejbClientContext.registerReconnectHandler(this.reconnectHandler); } } } /** * A {@link Channel.Receiver} for receiving message on the remoting channel */ private class ResponseReceiver implements Channel.Receiver { @Override public void handleError(Channel channel, IOException error) { logger.error("Error on channel " + channel, error); // close the channel and let the CloseHandler handle the cleanup try { channel.close(); } catch (IOException ioe) { // couldn't properly close, so let's do the cleanup that the CloseHandler was expected to do // notify about the broken channel and do the necessary cleanups if (error != null) { ChannelAssociation.this.notifyBrokenChannel(error); } else { ChannelAssociation.this.notifyBrokenChannel(new IOException("Channel " + channel + " received error notification")); } } } @Override public void handleEnd(Channel channel) { Logs.REMOTING.channelCanNoLongerProcessMessages(channel); // close the channel and let the CloseHandler handle the cleanup try { channel.close(); } catch (IOException ioe) { // couldn't properly close, so let's do the cleanup that the CloseHandler was expected to do // notify about the broken channel and do the necessary cleanups ChannelAssociation.this.notifyBrokenChannel(new IOException("Channel " + channel + " is no longer readable")); } } @Override public void handleMessage(Channel channel, MessageInputStream messageInputStream) { try { processResponse(messageInputStream); } catch (IOException e) { this.handleError(channel, e); } finally { // receive next message channel.receiveMessage(this); } } } /** * A {@link org.jboss.ejb.client.EJBReceiverInvocationContext.ResultProducer} which in its * {@link org.jboss.ejb.client.EJBReceiverInvocationContext.ResultProducer#getResult()} throws the {@link IOException} * which resulted in the channel being unusable */ private class UnusableChannelResultProducer implements EJBReceiverInvocationContext.ResultProducer { private final IOException ioException; UnusableChannelResultProducer(final IOException ioexception) { this.ioException = ioexception; } @Override public Object getResult() throws Exception { throw this.ioException; } @Override public void discardResult() { } } String getRemotingProtocol() { return remotingProtocol; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy