/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.transport.jms.common; import java.net.ProtocolException; import java.util.Map; import javax.jms.DeliveryMode; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageEOFException; import javax.jms.MessageProducer; import javax.jms.ObjectMessage; import javax.jms.Session; import javax.jms.TextMessage; import de.rcenvironment.core.communication.common.CommunicationException; import de.rcenvironment.core.communication.common.SerializationException; 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.InitialNodeInformationImpl; import de.rcenvironment.core.communication.protocol.NetworkRequestFactory; import de.rcenvironment.core.communication.protocol.NetworkResponseFactory; import de.rcenvironment.core.communication.transport.spi.HandshakeInformation; import de.rcenvironment.core.communication.utils.MessageUtils; /** * Utility class providing the mapping between RCE entities and JMS messages, plus related message-related settings. * * @author Robert Mischke */ public final class JmsProtocolUtils { private JmsProtocolUtils() { // prevent instantiation } /** * Creates a new JMS {@link Message} with the provided {@link InitialNodeInformation} as the handshake content, and a message type field * of {@link JmsProtocolConstants#MESSAGE_TYPE_INITIAL}. * * @param handshakeInformation the node information to use as body * @param session the JMS session to use * @return the created JMS {@link Message} * @throws JMSException on JMS errors */ public static Message createHandshakeMessage(JMSHandshakeInformation handshakeInformation, Session session) throws JMSException { ObjectMessage initialMessage = session.createObjectMessage(); initialMessage.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_MESSAGE_TYPE, JmsProtocolConstants.MESSAGE_TYPE_INITIAL); initialMessage.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_PROTOCOL_VERSION, handshakeInformation.getProtocolVersionString()); initialMessage.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_CHANNEL_ID, handshakeInformation.getChannelId()); initialMessage.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_REMOTE_INITIATED_REQUEST_INBOX, handshakeInformation.getTemporaryQueueInformation()); InitialNodeInformation initialNodeInformation = handshakeInformation.getInitialNodeInformation(); if (initialNodeInformation != null) { // TODO replace by JSON data? byte[] handshakeBytes = MessageUtils.serializeSafeObject(initialNodeInformation); initialMessage.setObject(handshakeBytes); } return initialMessage; } /** * Extracts the {@link HandshakeInformation} from a received initial JMS handshake response. * * @param message the received message * @param expectedProtocolVersion the protocol version string that the response must match to continue parsing; made a parameter to * allow unit testing * @return the extracted {@link HandshakeInformation} * @throws JMSException on JMS errors * @throws ProtocolException on a protocol version mismatch, or deserialization errors */ public static JMSHandshakeInformation parseHandshakeMessage(Message message, String expectedProtocolVersion) throws JMSException, ProtocolException { JMSHandshakeInformation result = new JMSHandshakeInformation(); result.setProtocolVersionString(message.getStringProperty(JmsProtocolConstants.MESSAGE_FIELD_PROTOCOL_VERSION)); result.setTemporaryQueueInformation(message .getStringProperty(JmsProtocolConstants.MESSAGE_FIELD_REMOTE_INITIATED_REQUEST_INBOX)); if (!result.matchesVersion(expectedProtocolVersion)) { // on an incompatible version, do not continue parsing; return result containing only // the version information return result; } result.setChannelId(message.getStringProperty(JmsProtocolConstants.MESSAGE_FIELD_CHANNEL_ID)); byte[] handshakeRequestBytes = (byte[]) ((ObjectMessage) message).getObject(); if (handshakeRequestBytes == null || handshakeRequestBytes.length == 0) { throw new ProtocolException("Received handshake request without payload"); } try { result.setInitialNodeInformation(MessageUtils.deserializeObject(handshakeRequestBytes, InitialNodeInformationImpl.class)); } catch (SerializationException e) { throw new ProtocolException("Failed to deserialize initial node information from handshake message: " + e.toString()); } return result; } /** * Creates a JMS message from a given {@link NetworkRequest}. * * @param request the request to transform * @param session the JMS session to use * @return the equivalent JMS message * @throws JMSException on JMS errors */ public static Message createMessageFromNetworkRequest(final NetworkRequest request, Session session) throws JMSException { Map<String, String> metadata = request.accessRawMetaData(); ObjectMessage jmsRequest = session.createObjectMessage(); jmsRequest.setObject(request.getContentBytes()); jmsRequest.setObjectProperty(JmsProtocolConstants.MESSAGE_FIELD_METADATA, metadata); jmsRequest.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_MESSAGE_TYPE, JmsProtocolConstants.MESSAGE_TYPE_REQUEST); return jmsRequest; } /** * Restores a {@link NetworkRequest} from its JMS message form. * * @param jmsRequest the JMS message * @return the reconstructed {@link NetworkRequest} * @throws JMSException on JMS errors * @throws CommunicationException on message format errors */ public static NetworkRequest createNetworkRequestFromMessage(Message jmsRequest) throws JMSException, CommunicationException { byte[] content = (byte[]) ((ObjectMessage) jmsRequest).getObject(); if (content.length == 0) { throw new CommunicationException("Received message with zero-length payload"); } @SuppressWarnings("unchecked") Map<String, String> requestMetadata = (Map<String, String>) jmsRequest .getObjectProperty(JmsProtocolConstants.MESSAGE_FIELD_METADATA); NetworkRequest originalRequest = NetworkRequestFactory.reconstructNetworkRequest(content, requestMetadata); return originalRequest; } /** * Creates a JMS message from a given {@link NetworkResponse}. * * @param response the response to transform * @param session the JMS session to use * @return the equivalent JMS message * @throws JMSException on JMS errors */ public static Message createMessageFromNetworkResponse(NetworkResponse response, Session session) throws JMSException { ObjectMessage jmsResponse = session.createObjectMessage(); jmsResponse.setObject(response.getContentBytes()); jmsResponse.setIntProperty(JmsProtocolConstants.MESSAGE_FIELD_RESULT_CODE, response.getResultCode().getCode()); // TODO add metadata? return jmsResponse; } /** * Restores a {@link NetworkResponse} from its JMS message form. * * @param jmsResponse the JMS message * @param request the {@link NetworkRequest} this response is associated with * @return the reconstructed {@link NetworkResponse} * @throws JMSException on JMS errors */ public static NetworkResponse createNetworkResponseFromMessage(Message jmsResponse, final NetworkRequest request) throws JMSException { byte[] content = (byte[]) ((ObjectMessage) jmsResponse).getObject(); if (jmsResponse.propertyExists(JmsProtocolConstants.MESSAGE_FIELD_RESULT_CODE)) { int resultCode = jmsResponse.getIntProperty(JmsProtocolConstants.MESSAGE_FIELD_RESULT_CODE); return NetworkResponseFactory.generateResponseWithResultCode(request, content, resultCode); } else { // backwards compatibility for remote 6.0.x - 6.2.x RCE nodes; can be dropped in 7.0.0 return NetworkResponseFactory.generateSuccessResponse(request, content); } } /** * Creates a JMS message to send to a JMS queue to terminate one {@link AbstractJmsQueueConsumer} listening on this queue ("poison pill" * pattern). * * @param session the JMS session to use * @param channelId the channel id to send with the message * @param securityToken the shared-secret security token to prevent unauthorized shutdown * @return the shutdown message * @throws JMSException on JMS errors */ public static Message createChannelShutdownMessage(Session session, String channelId, String securityToken) throws JMSException { TextMessage poisonPill = session.createTextMessage(); poisonPill.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_MESSAGE_TYPE, JmsProtocolConstants.MESSAGE_TYPE_CHANNEL_CLOSING); poisonPill.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_CHANNEL_ID, channelId); poisonPill.setText(securityToken); return poisonPill; } /** * Creates a JMS message to send to a JMS queue to terminate one {@link AbstractJmsQueueConsumer} listening on this queue ("poison pill" * pattern). * * @param session the JMS session to use * @param securityToken the shared-secret security token to prevent unauthorized queue shutdown * @return the shutdown message * @throws JMSException on JMS errors */ public static Message createQueueShutdownMessage(Session session, String securityToken) throws JMSException { TextMessage poisonPill = session.createTextMessage(); poisonPill.setStringProperty(JmsProtocolConstants.MESSAGE_FIELD_MESSAGE_TYPE, JmsProtocolConstants.MESSAGE_TYPE_QUEUE_SHUTDOWN); poisonPill.setText(securityToken); return poisonPill; } /** * Applies common settings (like message timeouts etc.) to a JMS {@link MessageProducer}. Should be invoked on any * {@link MessageProducer} before it is used for sending messages. * * @param producer the producer to configure * @throws JMSException on JMS errors */ public static void configureMessageProducer(MessageProducer producer) throws JMSException { // set the maximum time that messages from this producer are preserved producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT); } /** * Wraps the common JMS pattern where a temporary {@link MessageProducer} is created from a session, used to send a single * {@link MessageEOFException}, and then disposed. * * @param session the session to create the producer from * @param message the message to send * @param destination the JMS destination * @throws JMSException on JMS errors */ public static void sendWithTransientProducer(Session session, Message message, Destination destination) throws JMSException { MessageProducer producer = session.createProducer(destination); try { JmsProtocolUtils.configureMessageProducer(producer); producer.send(message); } finally { producer.close(); } } }