/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.core.protocol.stomp;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQBuffers;
import org.apache.activemq.artemis.core.protocol.stomp.v10.StompFrameV10;
/**
* Represents all the data in a STOMP frame.
*/
public class StompFrame {
protected static final byte[] END_OF_FRAME = new byte[]{0, '\n'};
protected final String command;
protected Map<String, String> headers;
private String body;
protected byte[] bytesBody;
protected ActiveMQBuffer buffer = null;
protected int size;
private boolean disconnect;
private boolean isPing;
public StompFrame(String command) {
this(command, false);
}
public StompFrame(String command, boolean disconnect) {
this.command = command;
this.headers = new LinkedHashMap<>();
this.disconnect = disconnect;
}
public StompFrame(String command, Map<String, String> headers, byte[] content) {
this.command = command;
this.headers = headers;
this.bytesBody = content;
}
public String getCommand() {
return command;
}
public int getEncodedSize() throws Exception {
if (buffer == null) {
buffer = toActiveMQBuffer();
}
return size;
}
@Override
public String toString() {
return "StompFrame[command=" + command + ", headers=" + headers + ", content= " + this.body + " bytes " +
Arrays.toString(bytesBody);
}
public boolean isPing() {
return isPing;
}
public void setPing(boolean ping) {
isPing = ping;
}
public ActiveMQBuffer toActiveMQBuffer() throws Exception {
if (buffer == null) {
if (isPing()) {
buffer = ActiveMQBuffers.fixedBuffer(1);
buffer.writeByte((byte) 10);
size = buffer.writerIndex();
return buffer;
}
StringBuilder head = new StringBuilder(512);
head.append(command);
head.append(Stomp.NEWLINE);
// Output the headers.
encodeHeaders(head);
if (bytesBody != null && bytesBody.length > 0 && !hasHeader(Stomp.Headers.CONTENT_LENGTH) && !(this instanceof StompFrameV10)) {
head.append(Stomp.Headers.CONTENT_LENGTH);
head.append(Stomp.Headers.SEPARATOR);
head.append(bytesBody.length);
head.append(Stomp.NEWLINE);
}
// Add a newline to separate the headers from the content.
head.append(Stomp.NEWLINE);
byte[] headBytes = head.toString().getBytes(StandardCharsets.UTF_8);
int bodyLength = (bytesBody == null) ? 0 : bytesBody.length;
buffer = ActiveMQBuffers.fixedBuffer(headBytes.length + bodyLength + END_OF_FRAME.length);
buffer.writeBytes(headBytes);
if (bytesBody != null) {
buffer.writeBytes(bytesBody);
}
buffer.writeBytes(END_OF_FRAME);
size = buffer.writerIndex();
} else {
buffer.readerIndex(0);
}
return buffer;
}
protected void encodeHeaders(StringBuilder head) {
for (Map.Entry<String, String> header : headers.entrySet()) {
head.append(header.getKey());
head.append(Stomp.Headers.SEPARATOR);
head.append(header.getValue());
head.append(Stomp.NEWLINE);
}
}
public String getHeader(String key) {
return headers.get(key);
}
public void addHeader(String key, String val) {
headers.put(key, val);
}
public Map<String, String> getHeadersMap() {
return headers;
}
public class Header {
public String key;
public String val;
public Header(String key, String val) {
this.key = key;
this.val = val;
}
public String getEncodedKey() {
return encode(key);
}
public String getEncodedValue() {
return encode(val);
}
}
public String encode(String str) {
if (str == null) {
return "";
}
int len = str.length();
char[] buffer = new char[2 * len];
int iBuffer = 0;
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
// \n
if (c == (byte) 10) {
buffer[iBuffer] = (byte) 92;
buffer[++iBuffer] = (byte) 110;
} else if (c == (byte) 13) { // \r
buffer[iBuffer] = (byte) 92;
buffer[++iBuffer] = (byte) 114;
} else if (c == (byte) 92) { // \
buffer[iBuffer] = (byte) 92;
buffer[++iBuffer] = (byte) 92;
} else if (c == (byte) 58) { // :
buffer[iBuffer] = (byte) 92;
buffer[++iBuffer] = (byte) 99;
} else {
buffer[iBuffer] = c;
}
iBuffer++;
}
return new String(buffer, 0, iBuffer);
}
public void setBody(String body) {
this.body = body;
this.bytesBody = body.getBytes(StandardCharsets.UTF_8);
}
public boolean hasHeader(String key) {
return headers.containsKey(key);
}
public String getBody() {
if (body == null) {
if (bytesBody != null) {
body = new String(bytesBody, StandardCharsets.UTF_8);
}
}
return body;
}
//Since 1.1, there is a content-type header that needs to take care of
public byte[] getBodyAsBytes() {
return bytesBody;
}
public boolean needsDisconnect() {
return disconnect;
}
public void setByteBody(byte[] content) {
this.bytesBody = content;
}
public void setNeedsDisconnect(boolean b) {
disconnect = b;
}
}