/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.transport.virtual; import java.util.Arrays; import java.util.Map; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.channel.MessageChannelState; import de.rcenvironment.core.communication.channel.ServerContactPoint; import de.rcenvironment.core.communication.common.CommunicationException; import de.rcenvironment.core.communication.common.SerializationException; import de.rcenvironment.core.communication.connection.internal.ConnectionClosedException; import de.rcenvironment.core.communication.model.InitialNodeInformation; import de.rcenvironment.core.communication.model.NetworkRequest; import de.rcenvironment.core.communication.model.NetworkResponse; import de.rcenvironment.core.communication.model.impl.NetworkResponseImpl; import de.rcenvironment.core.communication.protocol.MessageMetaData; import de.rcenvironment.core.communication.protocol.NetworkRequestFactory; import de.rcenvironment.core.communication.protocol.NetworkResponseFactory; import de.rcenvironment.core.communication.transport.spi.AbstractMessageChannel; import de.rcenvironment.core.communication.transport.spi.MessageChannel; import de.rcenvironment.core.communication.transport.spi.MessageChannelEndpointHandler; import de.rcenvironment.core.communication.transport.spi.MessageChannelResponseHandler; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.LogUtils; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * The {@link MessageChannel} implementation of {@link VirtualNetworkTransportProvider}. * * @author Robert Mischke */ public class VirtualNetworkMessageChannel extends AbstractMessageChannel { protected final Log log = LogFactory.getLog(getClass()); private MessageChannelEndpointHandler receivingRawEndpointHandler; private InitialNodeInformation ownNodeInformation; private AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService(); public VirtualNetworkMessageChannel(InitialNodeInformation ownNodeInformation, String ownProtocolVersion, MessageChannelEndpointHandler receivingRawEndpointHandler, ServerContactPoint remoteSCP) throws CommunicationException { this.receivingRawEndpointHandler = receivingRawEndpointHandler; this.ownNodeInformation = ownNodeInformation; this.associatedSCP = remoteSCP; // note: this check is performed at a different place than in the JMS channel failOnIncompatibleVersions(remoteSCP.getExpectedProtocolVersion(), ownProtocolVersion); } @Override public void sendRequest(final NetworkRequest request, final MessageChannelResponseHandler responseHandler, int timeoutMsec) { // TODO add local timeout // TODO send NetworkResponseHandler connection failure response on invalid destination // sanity check to avoid exceptions in async code if (request == null || responseHandler == null) { throw new NullPointerException(); } Callable<NetworkResponse> task = new Callable<NetworkResponse>() { @Override @TaskDescription("Communication Layer: Virtual connection message sending") public NetworkResponse call() throws Exception { if (isSimulatingBreakdown()) { responseHandler.onChannelBroken(request, VirtualNetworkMessageChannel.this); throw new ConnectionClosedException("Simulating breakdown of virtual channel " + getChannelId()); } if (associatedSCP.isSimulatingBreakdown()) { responseHandler.onChannelBroken(request, VirtualNetworkMessageChannel.this); throw new ConnectionClosedException(associatedSCP + " is simulating breakdown; failing send attempt on channel " + getChannelId()); } try { return simulateRoundTrip(request, responseHandler); } catch (RuntimeException e) { String errorId = LogUtils.logExceptionWithStacktraceAndAssignUniqueMarker(log, "Uncaught RuntimeException", e); NetworkResponse errorResponse = NetworkResponseFactory.generateResponseForErrorDuringDelivery(request, ownNodeInformation.getInstanceNodeSessionId(), errorId); responseHandler.onResponseAvailable(errorResponse); // responseHandler.onRequestFailure(request, VirtualNetworkConnection.this, e); // TODO review: keep throwing this exception? throw new CommunicationException("Failed to simulate request-response loop (request id: '" + request.getRequestId() + "')", e); } } private NetworkResponse simulateRoundTrip(final NetworkRequest request, final MessageChannelResponseHandler responseHandler) throws SerializationException { // as only the string form is passed, this is detached from the sending side final String virtualSenderId = ownNodeInformation.getInstanceNodeSessionId().getInstanceNodeSessionIdString(); // create a detached clone of the request NetworkRequest clonedRequest = NetworkRequestFactory.createDetachedClone(request); // invoke the connection service on the "receiving" side and fetch the response NetworkResponse generatedResponse = receivingRawEndpointHandler.onRawRequestReceived(clonedRequest, virtualSenderId); // create a detached clone of the response NetworkResponse clonedResponse = createDetachedClone(generatedResponse); responseHandler.onResponseAvailable(clonedResponse); return clonedResponse; } }; // TODO rework to plain runnable; no Future needed threadPool.submit(task); } @Override protected void onClosedOrBroken() { log.debug("Closing connection " + this + " (remote=" + getRemoteNodeInformation().getLogDescription() + ", NCP=" + associatedSCP); // on a clean shutdown, simulate "goodbye" message if (getState() == MessageChannelState.CLOSED) { if (isSimulatingBreakdown()) { log.debug("Simulating breakdown of virtual channel " + getChannelId() + "; not sending shutdown message"); return; } if (associatedSCP.isSimulatingBreakdown()) { log.debug(associatedSCP + " is simulating breakdown; not sending shutdown message for channel " + getChannelId()); return; } receivingRawEndpointHandler.onInboundChannelClosing(getChannelId()); } } private NetworkResponseImpl createDetachedClone(NetworkResponse response) throws SerializationException { // clone the received metadata; should be safe as it is a String/String map final Map<String, String> clonedResponseMetaData = MessageMetaData.wrap(response.accessRawMetaData()).cloneData(); // clone content byte array byte[] originalContentBytes = response.getContentBytes(); byte[] detachedContentBytes = null; if (originalContentBytes != null) { detachedContentBytes = Arrays.copyOf(originalContentBytes, originalContentBytes.length); } NetworkResponseImpl clonedResponse = new NetworkResponseImpl(detachedContentBytes, clonedResponseMetaData); return clonedResponse; } }