org.zeromq.jms.stomp.StompMessage Maven / Gradle / Ivy
package org.zeromq.jms.stomp;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
/*
* Copyright (c) 2015 Jeremy Miller
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* STOMP message implementation (https://stomp.github.io).
*/
public class StompMessage implements Externalizable {
/**
* STOMP frame types as an enumerator.
*/
public enum FrameType {
// Client Frames
SEND, SUBSCRIBE, UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, NACK, DISCONNECT,
// Stomp Frame
CONNECT, CONNECTED,
// Server Frame
MESSAGE, RECEIPT, ERROR
};
/**
* STOMP parameters as an enumerator.
*/
public enum HeaderKey {
/**
* The accept-version.
*/
HEADER_ACCEPT_VERSION("accept-version"),
/**
* The version.
*/
HEADER_VERSION("version"),
/**
* The host.
*/
HEADER_HOST("host"),
/**
* The destination.
*/
HEADER_DESTINATION("destination"),
/**
* Contains "auto", "client", or "client-individual", Default "auto" tell server if the
* client is acknowledge published messages.
*/
HEADER_ACK("ack"),
/**
* The Heart-beating settings.
*/
HEADER_HEADER_BEAT("heart-beat"),
/**
* Contains information about the STOMP server i.e. name ["/" version] *(comment), i.e. ZeroMQ/0.3.5
*/
HEADER_SERVER("server"),
/**
* Contains the message body mime type, i.e. test/html, text/plain, etc..
*/
HEADER_CONTENT_TYPE("content-type"),
/**
* Contains the length of the message body.
*/
HEADER_CONTENT_LENGTH("content-length"),
/**
* Contains the subscription identifier of the message, that is part of the message frame.
*/
HEADER_ID("id"),
/**
* Contains the unique identifier of the message, that is part of the message frame.
*/
HEADER_MESSAGE_ID("message-id"),
/**
* Contains the "message-id" that was received and sent back in a RECEIPT frame.
*/
HEADER_RECEIPT_ID("receipt-id"),
/**
* Contains the "receipt" message an arbitrary value returned as receipt.
*/
HEADER_RECEIPT("receipt"),
/**
* Contains the optional error short description for ERRO frames, i.e. mailformed frame received.
*/
HEADER_MESSAGE("messsage");
private String value;
/**
* Construct STOMP header key.
* @param value the value
*/
HeaderKey(final String value) {
this.value = value;
}
/**
* @return return the header key value.
*/
public String getValue() {
return value;
}
private static final Map VALUE_MAP;
static {
final Map valueMap = new HashMap();
for (final HeaderKey en : HeaderKey.values()) {
valueMap.put(en.value, en);
}
VALUE_MAP = Collections.unmodifiableMap(valueMap);
}
/**
* Return the header key as an enumerator based on the specified value.
* @param value the string representation of the value
* @return return the enumerator key value
*/
public static HeaderKey getEnum(final String value) {
if (!VALUE_MAP.containsKey(value)) {
throw new IllegalArgumentException("Unknown value: " + value);
}
return VALUE_MAP.get(value);
}
}
public static final String LINE_SPERATOR = System.getProperties().getProperty("line.separator");
public static final String NULL_OCTET = new String(new byte[] { 0 });
private FrameType frame;
private Map headers;
private String body;
/**
* Constructor ONLY required for Externalizable interface.
*/
public StompMessage() {
}
/**
* Construct a default STOMP message.
* @param frame the frame
* @param headers the headers
* @param body the body
*/
public StompMessage(final FrameType frame, final Map headers, final String body) {
this.frame = frame;
this.headers = headers;
this.body = body;
}
/**
* @return return the message frame, i.e. CONNECT, CONNECTED, etc....
*/
public FrameType getFrame() {
return frame;
}
/**
* @return return header property containing the "destination" were the message is to be sent, i.e. topic/a
*/
public String getDestination() {
return headers.get(HeaderKey.HEADER_DESTINATION.getValue());
}
/**
* @return return the header property for "content-type", i.e. text/plain.
*/
public String getContentType() {
return headers.get(HeaderKey.HEADER_CONTENT_TYPE.getValue());
}
/**
* @return return the header contents
*/
public Map getHeaders() {
return headers;
}
/**
* Return the header value for the specified key. Return NULL if the header is not found.
* @param key the key
* @return return the value, or null
*/
public String getHeaderValue(final String key) {
return headers.get(key);
}
/**
* Return the header value for the specified key. Return NULL if the header is not found.
* @param key the key
* @return return the value, or null
*/
public String getHeaderValue(final HeaderKey key) {
return headers.get(key.getValue());
}
/**
* Return the header value for the specified key as an "Integer". Return NULL if the header is not found.
* @param key the key
* @return return the "Integer" value, or null
*/
public Integer getHeaderValueAsInteger(final HeaderKey key) {
final String value = headers.get(key.getValue());
if (value == null || value.length() == 0) {
return null;
}
final int valueAsInt = Integer.parseInt(value);
return valueAsInt;
}
/**
* Return the header value, or the specified default value when it does not exist.
* @param key the key
* @param defaultValue the default value
* @return return the value, or default value
*/
public String getHeaderValue(final HeaderKey key, final String defaultValue) {
String value = headers.get(key.getValue());
if (value == null) {
return defaultValue;
}
return value;
}
/**
* @return return the body of the STOMP message
*/
public String getBody() {
return body;
}
/**
* @return return the encoded STOMP message.
*/
public String encode() {
return encode(this);
}
/**
* Encode the message into STOMP format.
* @param message the message to be encoded
* @return return the encoded message
*/
public static String encode(final StompMessage message) {
StringBuilder messageBuffer = new StringBuilder();
messageBuffer.append(message.getFrame()).append(LINE_SPERATOR);
Map headers = message.getHeaders();
for (String key : headers.keySet()) {
String value = headers.get(key);
messageBuffer.append(key).append(":").append(value).append(LINE_SPERATOR);
}
if (message.getBody() != null) {
messageBuffer.append(LINE_SPERATOR);
messageBuffer.append(message.getBody());
}
messageBuffer.append(NULL_OCTET).append(LINE_SPERATOR);
return messageBuffer.toString();
}
/**
* Decode the STOMP message into a message object.
* @param message the message to decode
* @return return the decoded message
* @throws StompException throws exception when message cannot be decoded
*/
public static StompMessage decode(final String message) throws StompException {
int index = message.indexOf('\n');
if (index == -1) {
throw new StompException("Malformed message: " + message);
}
int beginIndex = 0;
int endIndex = ((index > 0) && (message.charAt(index - 1) == '\r')) ? index - 1 : index;
final String messageFrame = message.substring(beginIndex, endIndex);
final FrameType frame = FrameType.valueOf(messageFrame);
beginIndex = index + 1;
Map headers = new LinkedHashMap();
boolean hasBody = false;
while (index >= 0) {
index = message.indexOf('\n', beginIndex);
if (index == -1) {
throw new StompException("Malformed message.");
}
endIndex = ((index > 0) && (message.charAt(index - 1) == '\r')) ? index - 1 : index;
final String header = message.substring(beginIndex, endIndex);
// Check for termination of headers, the empty line.
if (header.length() == 0) {
hasBody = true;
beginIndex = index + 1;
break;
}
if (header.startsWith(NULL_OCTET)) {
break;
}
int seperatorIndex = header.indexOf(":");
final String headerKey = header.substring(0, seperatorIndex);
final String headerValue = header.substring(seperatorIndex + 1);
headers.put(headerKey, headerValue);
beginIndex = index + 1;
if (beginIndex > message.length()) {
throw new StompException("Malformed message: missing NULL octlet.");
}
if (message.charAt(beginIndex) == NULL_OCTET.charAt(0)) {
break;
}
}
String body = null;
if (hasBody) {
endIndex = message.indexOf(NULL_OCTET, beginIndex);
body = message.substring(beginIndex, endIndex);
}
return new StompMessage(frame, headers, body);
}
/**
* Define a STOMP connect message.
* @param acceptedVersion the version to accept
* @param host the host address
* @return return a STOMP connect message
*/
public static StompMessage defineConnectMessage(final String acceptedVersion, final String host) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_ACCEPT_VERSION.getValue(), acceptedVersion);
headers.put(HeaderKey.HEADER_HOST.getValue(), host);
StompMessage stompMessage = new StompMessage(FrameType.CONNECT, headers, null);
return stompMessage;
}
/**
* Define a STOMP connected message.
* @param version the STOMP version
* @return return a STOMP connected message
*/
public static StompMessage defineConnectedMessage(final String version) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_VERSION.getValue(), version);
StompMessage stompMessage = new StompMessage(FrameType.CONNECTED, headers, null);
return stompMessage;
}
/**
* Define a STOMP disconnect message.
* @param receipt the receipt identifier
* @return return a STOMP disconnect message
*/
public static StompMessage defineDisconnectMessage(final String receipt) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_RECEIPT.getValue(), receipt);
StompMessage stompMessage = new StompMessage(FrameType.DISCONNECT, headers, null);
return stompMessage;
}
/**
* Define a STOMP acknowledgement message.
* @param receiptId the unique message identifier of the sent message
* @return return a STOMP acknowledgement message
*/
public static StompMessage defineAckMessage(final String receiptId) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_ID.getValue(), receiptId);
StompMessage stompMessage = new StompMessage(FrameType.ACK, headers, null);
return stompMessage;
}
/**
* Define a STOMP subscribe message.
* @param id the unique message identifier
* @param destination the destination string
* @param ack the acknowledgement
* @return return a STOMP subscribe message
*/
public static StompMessage defineSubscribeMessage(final String id, final String destination, final String ack) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_ID.getValue(), id);
headers.put(HeaderKey.HEADER_DESTINATION.getValue(), destination);
if (ack != null) {
headers.put(HeaderKey.HEADER_ACK.getValue(), ack);
}
StompMessage stompMessage = new StompMessage(FrameType.SUBSCRIBE, headers, null);
return stompMessage;
}
/**
* Define a STOMP un-subscribe message.
* @param id the unique message identifier
* @return return a STOMP un-subscribe message
*/
public static StompMessage defineUnsubscribeMessage(final String id) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_ID.getValue(), id);
StompMessage stompMessage = new StompMessage(FrameType.UNSUBSCRIBE, headers, null);
return stompMessage;
}
/**
* Define a STOMP send message with content.
* @param id the unique message identifier
* @param destination the destination string
* @param body the context
* @return return a STOMP send message
*/
public static StompMessage defineSendMessage(final String id, final String destination, final String body) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_DESTINATION.getValue(), destination);
if (id != null) {
headers.put(HeaderKey.HEADER_ID.getValue(), id);
}
StompMessage stompMessage = new StompMessage(FrameType.SEND, headers, body);
return stompMessage;
}
/**
* Define a STOMP error message.
* @param receiptId the message ID that caused the exception
* @param errorHeading the error heading
* @param errorDescription the error description
* @param relatedRequest the related request
* @return return a STOMP error message
*/
public static StompMessage defineErrorMessage(final String receiptId, final String errorHeading, final String errorDescription,
final String relatedRequest) {
Map headers = new LinkedHashMap();
headers.put(HeaderKey.HEADER_RECEIPT_ID.getValue(), receiptId);
headers.put(HeaderKey.HEADER_MESSAGE.getValue(), errorHeading);
String body = null;
if (relatedRequest != null || errorDescription != null) {
StringBuilder builder = new StringBuilder();
if (relatedRequest != null) {
builder.append("The message:").append(StompMessage.LINE_SPERATOR).append("-----").append(StompMessage.LINE_SPERATOR)
.append(relatedRequest).append(StompMessage.LINE_SPERATOR).append("-----").append(StompMessage.LINE_SPERATOR);
}
if (errorDescription != null) {
builder.append(errorDescription).append(StompMessage.LINE_SPERATOR);
}
body = builder.toString();
}
StompMessage stompMessage = new StompMessage(FrameType.ERROR, headers, body);
return stompMessage;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((body == null) ? 0 : body.hashCode());
result = prime * result + ((frame == null) ? 0 : frame.hashCode());
result = prime * result + ((headers == null) ? 0 : headers.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
StompMessage other = (StompMessage) obj;
if (body == null) {
if (other.body != null) {
return false;
}
} else if (!body.equals(other.body)) {
return false;
}
if (frame != other.frame) {
return false;
}
if (headers == null) {
if (other.headers != null) {
return false;
}
} else if (!headers.equals(other.headers)) {
return false;
}
return true;
}
@Override
public String toString() {
final String truncatedBody = (body == null || body.length() < 80) ? body : body.substring(0, 80) + "...";
return "StompMessage [frame=" + frame + ", headers=" + headers + ", body=" + truncatedBody + "]";
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeObject(frame);
out.write(headers.size());
for (String name : headers.keySet()) {
final String value = headers.get(name);
out.writeObject(name);
out.writeObject(value);
}
out.writeObject(body);
}
@Override
public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {
frame = (FrameType) in.readObject();
headers = new HashMap();
final int count = in.readInt();
for (int i = 0; i < count; i++) {
final String name = (String) in.readObject();
final String value = (String) in.readObject();
headers.put(name, value);
}
body = (String) in.readObject();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy