/* * 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.concurrent.ScheduledExecutorService; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ICoreMessage; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.message.impl.CoreMessage; import org.apache.activemq.artemis.core.protocol.stomp.Stomp.Headers; import org.apache.activemq.artemis.core.protocol.stomp.v10.StompFrameHandlerV10; import org.apache.activemq.artemis.core.protocol.stomp.v11.StompFrameHandlerV11; import org.apache.activemq.artemis.core.protocol.stomp.v12.StompFrameHandlerV12; import org.apache.activemq.artemis.utils.ExecutorFactory; import static org.apache.activemq.artemis.core.protocol.stomp.ActiveMQStompProtocolMessageBundle.BUNDLE; public abstract class VersionedStompFrameHandler { protected StompConnection connection; protected StompDecoder decoder; protected final ScheduledExecutorService scheduledExecutorService; protected final ExecutorFactory executorFactory; protected void disconnect() { } public static VersionedStompFrameHandler getHandler(StompConnection connection, StompVersions version, ScheduledExecutorService scheduledExecutorService, ExecutorFactory executorFactory) { if (version == StompVersions.V1_0) { return new StompFrameHandlerV10(connection, scheduledExecutorService, executorFactory); } if (version == StompVersions.V1_1) { return new StompFrameHandlerV11(connection, scheduledExecutorService, executorFactory); } if (version == StompVersions.V1_2) { return new StompFrameHandlerV12(connection, scheduledExecutorService, executorFactory); } return null; } protected VersionedStompFrameHandler(StompConnection connection, ScheduledExecutorService scheduledExecutorService, ExecutorFactory executorFactory) { this.connection = connection; this.scheduledExecutorService = scheduledExecutorService; this.executorFactory = executorFactory; } public StompFrame decode(ActiveMQBuffer buffer) throws ActiveMQStompException { return decoder.decode(buffer); } public boolean hasBytes() { return decoder.hasBytes(); } public StompDecoder getDecoder() { return decoder; } public StompFrame handleFrame(StompFrame request) { StompFrame response = null; if (Stomp.Commands.SEND.equals(request.getCommand())) { response = onSend(request); } else if (Stomp.Commands.ACK.equals(request.getCommand())) { response = onAck(request); } else if (Stomp.Commands.NACK.equals(request.getCommand())) { response = onNack(request); } else if (Stomp.Commands.BEGIN.equals(request.getCommand())) { response = onBegin(request); } else if (Stomp.Commands.COMMIT.equals(request.getCommand())) { response = onCommit(request); } else if (Stomp.Commands.ABORT.equals(request.getCommand())) { response = onAbort(request); } else if (Stomp.Commands.SUBSCRIBE.equals(request.getCommand())) { response = onSubscribe(request); } else if (Stomp.Commands.UNSUBSCRIBE.equals(request.getCommand())) { response = onUnsubscribe(request); } else if (Stomp.Commands.CONNECT.equals(request.getCommand())) { response = onConnect(request); } else if (Stomp.Commands.STOMP.equals(request.getCommand())) { response = onStomp(request); } else if (Stomp.Commands.DISCONNECT.equals(request.getCommand())) { response = onDisconnect(request); } else { response = onUnknown(request.getCommand()); } if (response == null) { response = postprocess(request); } else { if (request.hasHeader(Stomp.Headers.RECEIPT_REQUESTED)) { response.addHeader(Stomp.Headers.Response.RECEIPT_ID, request.getHeader(Stomp.Headers.RECEIPT_REQUESTED)); } } return response; } public abstract StompFrame onConnect(StompFrame frame); public abstract StompFrame onDisconnect(StompFrame frame); public abstract StompFrame onAck(StompFrame request); public abstract StompFrame onUnsubscribe(StompFrame request); public abstract StompFrame onStomp(StompFrame request); public abstract StompFrame onNack(StompFrame request); public abstract StompFrame createStompFrame(String command); public StompFrame onUnknown(String command) { ActiveMQStompException error = BUNDLE.unknownCommand(command).setHandler(this); StompFrame response = error.getFrame(); return response; } public StompFrame handleReceipt(String receiptID) { StompFrame receipt = createStompFrame(Stomp.Responses.RECEIPT); receipt.addHeader(Stomp.Headers.Response.RECEIPT_ID, receiptID); return receipt; } public StompFrame onCommit(StompFrame request) { StompFrame response = null; String txID = request.getHeader(Stomp.Headers.TRANSACTION); if (txID == null) { ActiveMQStompException error = BUNDLE.needTxIDHeader().setHandler(this); response = error.getFrame(); return response; } try { connection.commitTransaction(txID); } catch (ActiveMQStompException e) { response = e.getFrame(); } return response; } public StompFrame onSend(StompFrame frame) { StompFrame response = null; try { connection.validate(); String destination = getDestination(frame); RoutingType routingType = getRoutingType(frame.getHeader(Headers.Send.DESTINATION_TYPE), frame.getHeader(Headers.Send.DESTINATION)); connection.autoCreateDestinationIfPossible(destination, routingType); connection.checkDestination(destination); connection.checkRoutingSemantics(destination, routingType); String txID = frame.getHeader(Stomp.Headers.TRANSACTION); long timestamp = System.currentTimeMillis(); CoreMessage message = connection.createServerMessage(); if (routingType != null) { message.setRoutingType(routingType); } message.setTimestamp(timestamp); message.setAddress(SimpleString.toSimpleString(destination)); StompUtils.copyStandardHeadersFromFrameToMessage(frame, message); if (frame.hasHeader(Stomp.Headers.CONTENT_LENGTH)) { message.setType(Message.BYTES_TYPE); message.getBodyBuffer().writeBytes(frame.getBodyAsBytes()); } else { message.setType(Message.TEXT_TYPE); String text = frame.getBody(); message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(text)); } connection.sendServerMessage(message, txID); } catch (ActiveMQStompException e) { response = e.getFrame(); } catch (Exception e) { ActiveMQStompException error = BUNDLE.errorHandleSend(e).setHandler(this); response = error.getFrame(); } return response; } public StompFrame onBegin(StompFrame frame) { StompFrame response = null; String txID = frame.getHeader(Stomp.Headers.TRANSACTION); if (txID == null) { ActiveMQStompException error = BUNDLE.beginTxNoID().setHandler(this); response = error.getFrame(); } else { try { connection.beginTransaction(txID); } catch (ActiveMQStompException e) { response = e.getFrame(); } } return response; } public StompFrame onAbort(StompFrame request) { StompFrame response = null; String txID = request.getHeader(Stomp.Headers.TRANSACTION); if (txID == null) { ActiveMQStompException error = BUNDLE.abortTxNoID().setHandler(this); response = error.getFrame(); return response; } try { connection.abortTransaction(txID); } catch (ActiveMQStompException e) { response = e.getFrame(); } return response; } public StompFrame onSubscribe(StompFrame frame) { StompFrame response = null; try { String destination = getDestination(frame); String selector = frame.getHeader(Stomp.Headers.Subscribe.SELECTOR); String ack = frame.getHeader(Stomp.Headers.Subscribe.ACK_MODE); String id = frame.getHeader(Stomp.Headers.Subscribe.ID); String durableSubscriptionName = frame.getHeader(Stomp.Headers.Subscribe.DURABLE_SUBSCRIBER_NAME); if (durableSubscriptionName == null) { durableSubscriptionName = frame.getHeader(Stomp.Headers.Subscribe.DURABLE_SUBSCRIPTION_NAME); } RoutingType routingType = getRoutingType(frame.getHeader(Headers.Subscribe.SUBSCRIPTION_TYPE), frame.getHeader(Headers.Subscribe.DESTINATION)); boolean noLocal = false; if (frame.hasHeader(Stomp.Headers.Subscribe.NO_LOCAL)) { noLocal = Boolean.parseBoolean(frame.getHeader(Stomp.Headers.Subscribe.NO_LOCAL)); } connection.subscribe(destination, selector, ack, id, durableSubscriptionName, noLocal, routingType); } catch (ActiveMQStompException e) { response = e.getFrame(); } return response; } public String getDestination(StompFrame request) { return request.getHeader(Headers.Subscribe.DESTINATION); } public StompFrame postprocess(StompFrame request) { StompFrame response = null; if (request.hasHeader(Stomp.Headers.RECEIPT_REQUESTED)) { response = handleReceipt(request.getHeader(Stomp.Headers.RECEIPT_REQUESTED)); if (request.getCommand().equals(Stomp.Commands.DISCONNECT)) { response.setNeedsDisconnect(true); } } else { //request null, disconnect if so. if (request.getCommand().equals(Stomp.Commands.DISCONNECT)) { this.connection.disconnect(false); } } return response; } public StompFrame createMessageFrame(ICoreMessage serverMessage, ActiveMQBuffer bodyBuffer, StompSubscription subscription, int deliveryCount) throws Exception { StompFrame frame = createStompFrame(Stomp.Responses.MESSAGE); if (subscription.getID() != null) { frame.addHeader(Stomp.Headers.Message.SUBSCRIPTION, subscription.getID()); } ActiveMQBuffer buffer = bodyBuffer != null ? bodyBuffer : serverMessage.getReadOnlyBodyBuffer(); int bodyPos = (serverMessage).getEndOfBodyPosition() == -1 ? buffer.writerIndex() : (serverMessage).getEndOfBodyPosition(); int size = buffer.writerIndex(); byte[] data = new byte[size]; if (serverMessage.containsProperty(Stomp.Headers.CONTENT_LENGTH) || serverMessage.getType() == Message.BYTES_TYPE) { frame.addHeader(Headers.CONTENT_LENGTH, String.valueOf(data.length)); buffer.readBytes(data); } else { SimpleString text = buffer.readNullableSimpleString(); if (text != null) { data = text.toString().getBytes(StandardCharsets.UTF_8); } else { data = new byte[0]; } } frame.setByteBody(data); StompUtils.copyStandardHeadersFromMessageToFrame((serverMessage), frame, deliveryCount); return frame; } /** * this method is called when a newer version of handler is created. It should * take over the state of the decoder of the existingHandler so that * the decoding can be continued. For V10 handler it's never called. * * @param existingHandler */ public void initDecoder(VersionedStompFrameHandler existingHandler) { throw BUNDLE.invalidCall(); } //sends an ERROR frame back to client if possible then close the connection public void onError(ActiveMQStompException e) { this.connection.sendFrame(e.getFrame()); connection.destroy(); } private RoutingType getRoutingType(String typeHeader, String destination) throws ActiveMQStompException { // null is valid to return here so we know when the user didn't provide any routing info RoutingType routingType; if (typeHeader != null) { routingType = RoutingType.valueOf(typeHeader); } else { routingType = connection.getSession().getCoreSession().getAddressAndRoutingType(SimpleString.toSimpleString(destination), null).getB(); } return routingType; } }