/*
* 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.v12;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ICoreMessage;
import org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompException;
import org.apache.activemq.artemis.core.protocol.stomp.Stomp;
import org.apache.activemq.artemis.core.protocol.stomp.StompConnection;
import org.apache.activemq.artemis.core.protocol.stomp.StompDecoder;
import org.apache.activemq.artemis.core.protocol.stomp.StompFrame;
import org.apache.activemq.artemis.core.protocol.stomp.StompSubscription;
import org.apache.activemq.artemis.core.protocol.stomp.v11.StompFrameHandlerV11;
import org.apache.activemq.artemis.core.protocol.stomp.v11.StompFrameV11;
import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
import org.apache.activemq.artemis.utils.ExecutorFactory;
import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE;
public class StompFrameHandlerV12 extends StompFrameHandlerV11 {
public StompFrameHandlerV12(StompConnection connection,
ScheduledExecutorService scheduledExecutorService,
ExecutorFactory factory) {
super(connection, scheduledExecutorService, factory);
decoder = new StompDecoderV12(this);
decoder.init();
}
@Override
public StompFrame createStompFrame(String command) {
return new StompFrameV12(command);
}
@Override
public StompFrame createMessageFrame(ICoreMessage serverMessage,
ActiveMQBuffer bodyBuffer,
StompSubscription subscription,
int deliveryCount) throws Exception {
StompFrame frame = super.createMessageFrame(serverMessage, bodyBuffer, subscription, deliveryCount);
if (!subscription.getAck().equals(Stomp.Headers.Subscribe.AckModeValues.AUTO)) {
frame.addHeader(Stomp.Headers.Message.ACK, String.valueOf(serverMessage.getMessageID()));
}
return frame;
}
/**
* Version 1.2's ACK frame only requires 'id' header
* here we use id = messageID
*/
@Override
public StompFrame onAck(StompFrame request) {
StompFrame response = null;
String messageID = request.getHeader(Stomp.Headers.Ack.ID);
String txID = request.getHeader(Stomp.Headers.TRANSACTION);
if (txID != null) {
ActiveMQServerLogger.LOGGER.stompTXAckNorSupported();
}
if (messageID == null) {
ActiveMQStompException error = BUNDLE.noIDInAck().setHandler(connection.getFrameHandler());
return error.getFrame();
}
try {
connection.acknowledge(messageID, null);
} catch (ActiveMQStompException e) {
response = e.getFrame();
}
return response;
}
protected class StompDecoderV12 extends StompDecoderV11 {
protected boolean nextEOLChar = false;
public StompDecoderV12(StompFrameHandlerV12 handler) {
super(handler);
//1.2 allows '\r\n'
eolLen = 2;
}
@Override
public void init() {
super.init();
nextEOLChar = false;
}
@Override
protected void checkEol() throws ActiveMQStompException {
//either \n or \r\n
if (workingBuffer[pos - 2] == NEW_LINE) {
pos--;
} else if (workingBuffer[pos - 2] != CR) {
throwInvalid();
} else if (workingBuffer[pos - 1] != NEW_LINE) {
throwInvalid();
}
}
@Override
public void init(StompDecoder decoder) {
this.data = decoder.data;
this.workingBuffer = decoder.workingBuffer;
this.pos = decoder.pos;
this.command = decoder.command;
}
@Override
protected boolean parseHeaders() throws ActiveMQStompException {
outer:
while (true) {
byte b = workingBuffer[pos++];
switch (b) {
//escaping
case ESC_CHAR: {
if (isEscaping) {
//this is a backslash
holder.append(b);
isEscaping = false;
} else {
//begin escaping
isEscaping = true;
}
break;
}
case HEADER_SEPARATOR: {
if (inHeaderName) {
headerName = holder.getString();
holder.reset();
inHeaderName = false;
headerValueWhitespace = true;
}
whiteSpaceOnly = false;
break;
}
case LN: {
if (isEscaping) {
holder.append(NEW_LINE);
isEscaping = false;
} else {
holder.append(b);
}
break;
}
case RT: {
if (isEscaping) {
holder.append(CR);
isEscaping = false;
} else {
holder.append(b);
}
break;
}
case CR: {
if (nextEOLChar) {
throw BUNDLE.invalidTwoCRs().setHandler(handler);
}
nextEOLChar = true;
break;
}
case StompDecoder.c: {
if (isEscaping) {
holder.append(StompDecoder.HEADER_SEPARATOR);
isEscaping = false;
} else {
holder.append(b);
}
break;
}
case NEW_LINE: {
nextEOLChar = false;
if (whiteSpaceOnly) {
// Headers are terminated by a blank line
readingHeaders = false;
break outer;
}
String headerValue = holder.getString();
holder.reset();
if (!headers.containsKey(headerName)) {
headers.put(headerName, headerValue);
}
if (headerName.equals(Stomp.Headers.CONTENT_LENGTH)) {
contentLength = Integer.parseInt(headerValue);
}
if (headerName.equals(Stomp.Headers.CONTENT_TYPE)) {
contentType = headerValue;
}
whiteSpaceOnly = true;
inHeaderName = true;
headerValueWhitespace = false;
break;
}
default: {
whiteSpaceOnly = false;
headerValueWhitespace = false;
if (isEscaping) {
throwUndefinedEscape(b);
}
holder.append(b);
}
}
if (pos == data) {
// Run out of data
return false;
}
}
return true;
}
@Override
protected StompFrame parseBody() throws ActiveMQStompException {
byte[] content = null;
if (contentLength != -1) {
if (pos + contentLength + 1 > data) {
// Need more bytes
} else {
content = new byte[contentLength];
System.arraycopy(workingBuffer, pos, content, 0, contentLength);
pos += contentLength;
//drain all the rest
if (bodyStart == -1) {
bodyStart = pos;
}
while (pos < data) {
if (workingBuffer[pos++] == 0) {
break;
}
}
}
} else {
// Need to scan for terminating NUL
if (bodyStart == -1) {
bodyStart = pos;
}
while (pos < data) {
if (workingBuffer[pos++] == 0) {
content = new byte[pos - bodyStart - 1];
System.arraycopy(workingBuffer, bodyStart, content, 0, content.length);
break;
}
}
}
if (content != null) {
if (data > pos) {
if (workingBuffer[pos] == NEW_LINE)
pos++;
if (data > pos)
// More data still in the buffer from the next packet
System.arraycopy(workingBuffer, pos, workingBuffer, 0, data - pos);
}
data = data - pos;
// reset
StompFrame ret = new StompFrameV11(command, headers, content);
init();
return ret;
} else {
return null;
}
}
}
}