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

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

/*
 * 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