
io.vertx.ext.stomp.impl.DefaultStompHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vertx-stomp Show documentation
Show all versions of vertx-stomp Show documentation
Stomp support for Vert.x 3
/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.stomp.impl;
import io.vertx.core.*;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.PromiseInternal;
import io.vertx.core.internal.logging.Logger;
import io.vertx.core.internal.logging.LoggerFactory;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.authentication.AuthenticationProvider;
import io.vertx.ext.auth.authentication.UsernamePasswordCredentials;
import io.vertx.ext.stomp.Acknowledgement;
import io.vertx.ext.stomp.BridgeOptions;
import io.vertx.ext.stomp.DefaultAbortHandler;
import io.vertx.ext.stomp.DefaultAckHandler;
import io.vertx.ext.stomp.DefaultBeginHandler;
import io.vertx.ext.stomp.DefaultCommitHandler;
import io.vertx.ext.stomp.DefaultConnectHandler;
import io.vertx.ext.stomp.DefaultNackHandler;
import io.vertx.ext.stomp.DefaultSendHandler;
import io.vertx.ext.stomp.DefaultSubscribeHandler;
import io.vertx.ext.stomp.DefaultUnsubscribeHandler;
import io.vertx.ext.stomp.Destination;
import io.vertx.ext.stomp.DestinationFactory;
import io.vertx.ext.stomp.Frame;
import io.vertx.ext.stomp.Frames;
import io.vertx.ext.stomp.ServerFrame;
import io.vertx.ext.stomp.StompServer;
import io.vertx.ext.stomp.StompServerConnection;
import io.vertx.ext.stomp.StompServerHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* A plug-able implementation of {@link StompServerHandler}. The default behavior is compliant with the STOMP
* specification.
*
* By default {@code ACK/NACK} are managed as a dead messages. Not acknowledges messages are dropped from the list
* and a warning is printed in the log.
*
* This class is thread safe.
*
* @author Clement Escoffier
*/
public class DefaultStompHandler implements StompServerHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultStompHandler.class);
private final Vertx vertx;
private final Context context;
private Handler connectHandler;
private Handler stompHandler;
private Handler sendHandler = new DefaultSendHandler();
private Handler subscribeHandler = new DefaultSubscribeHandler();
private Handler unsubscribeHandler = new DefaultUnsubscribeHandler();
private Handler closeHandler;
private Handler commitHandler = new DefaultCommitHandler();
private Handler abortHandler = new DefaultAbortHandler();
private Handler beginHandler = new DefaultBeginHandler();
private Handler ackHandler = new DefaultAckHandler();
private Handler nackHandler = new DefaultNackHandler();
private Handler disconnectHandler = (sf -> {
StompServerConnection connection = sf.connection();
Frames.handleReceipt(sf.frame(), connection);
connection.close();
});
private AuthenticationProvider authProvider;
private Handler pingHandler = StompServerConnection::ping;
private Handler onAckHandler = (acknowledgement) -> LOGGER.info("Acknowledge messages - " +
acknowledgement.frames());
private Handler onNackHandler = (acknowledgement) ->
LOGGER.warn("Messages not acknowledge - " + acknowledgement.frames());
private final LocalMap destinations;
// user is mutable and built from other modules so there's no guarantees
// about thread safety so use w/ care..
private final ConcurrentHashMap users;
private DestinationFactory factory = Destination::topic;
private Handler receivedFrameHandler;
/**
* Creates a new instance of {@link DefaultStompHandler}.
*
* @param vertx the vert.x instance
*/
public DefaultStompHandler(Vertx vertx) {
this.vertx = vertx;
this.context = Vertx.currentContext();
this.destinations = vertx.sharedData().getLocalMap("stomp.destinations");
this.users = new ConcurrentHashMap<>();
this.connectHandler = new DefaultConnectHandler();
}
@Override
public synchronized void onClose(StompServerConnection connection) {
// Default behavior.
getDestinations().stream().forEach((d) -> d.unsubscribeConnection(connection));
Transactions.instance().unregisterTransactionsFromConnection(connection);
// Remove user, if exists
this.users.remove(connection.session());
if (closeHandler != null) {
closeHandler.handle(connection);
}
}
@Override
public synchronized StompServerHandler receivedFrameHandler(Handler handler) {
this.receivedFrameHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler connectHandler(Handler handler) {
this.connectHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler stompHandler(Handler handler) {
this.stompHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler subscribeHandler(Handler handler) {
this.subscribeHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler unsubscribeHandler(Handler handler) {
this.unsubscribeHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler sendHandler(Handler handler) {
this.sendHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler closeHandler(Handler handler) {
this.closeHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler commitHandler(Handler handler) {
this.commitHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler abortHandler(Handler handler) {
this.abortHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler beginHandler(Handler handler) {
this.beginHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler disconnectHandler(Handler handler) {
this.disconnectHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler ackHandler(Handler handler) {
this.ackHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler nackHandler(Handler handler) {
this.nackHandler = handler;
return this;
}
@Override
public void handle(ServerFrame serverFrame) {
Frame frame = serverFrame.frame();
StompServerConnection connection = serverFrame.connection();
connection.onServerActivity();
synchronized (this) {
if (receivedFrameHandler != null) {
receivedFrameHandler.handle(serverFrame);
}
}
switch (frame.getCommand()) {
case CONNECT:
handleConnect(frame, connection);
break;
case STOMP:
handleStomp(frame, connection);
break;
case SEND:
handleSend(frame, connection);
break;
case SUBSCRIBE:
handleSubscribe(frame, connection);
break;
case UNSUBSCRIBE:
handleUnsubscribe(frame, connection);
break;
case BEGIN:
handleBegin(frame, connection);
break;
case ABORT:
handleAbort(frame, connection);
break;
case COMMIT:
handleCommit(frame, connection);
break;
case ACK:
handleAck(frame, connection);
break;
case NACK:
handleNack(frame, connection);
break;
case DISCONNECT:
handleDisconnect(frame, connection);
break;
case PING:
// We received a ping frame, we do nothing.
break;
default:
// Unknown frames
break;
}
}
private void handleAck(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = ackHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleNack(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = nackHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleBegin(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = beginHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleAbort(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = abortHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleCommit(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = commitHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleSubscribe(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = subscribeHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleUnsubscribe(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = unsubscribeHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleSend(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = sendHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleConnect(Frame frame, StompServerConnection connection) {
Handler handler;
Handler pingH;
synchronized (this) {
handler = connectHandler;
pingH = pingHandler;
}
// Compute heartbeat, and register pinger and ponger
// Stomp server acts as a client to call the computePingPeriod & computePongPeriod method
long ping = Frame.Heartbeat.computePingPeriod(
Frame.Heartbeat.create(connection.server().options().getHeartbeat()),
Frame.Heartbeat.parse(frame.getHeader(Frame.HEARTBEAT)));
long pong = Frame.Heartbeat.computePongPeriod(
Frame.Heartbeat.create(connection.server().options().getHeartbeat()),
Frame.Heartbeat.parse(frame.getHeader(Frame.HEARTBEAT)));
connection.configureHeartbeat(ping, pong, pingH);
// Then, handle the frame.
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleDisconnect(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = disconnectHandler;
}
if (handler != null) {
handler.handle(new ServerFrameImpl(frame, connection));
}
}
private void handleStomp(Frame frame, StompServerConnection connection) {
Handler handler;
synchronized (this) {
handler = stompHandler;
}
if (handler == null) {
// Per spec, STOMP frame must be handled as CONNECT
handleConnect(frame, connection);
return;
}
handler.handle(new ServerFrameImpl(frame, connection));
}
@Override
public synchronized StompServerHandler authProvider(AuthenticationProvider handler) {
this.authProvider = handler;
return this;
}
@Override
public Future onAuthenticationRequest(StompServerConnection connection, String login, String passcode) {
PromiseInternal promise = ((ContextInternal) context).promise();
onAuthenticationRequest(connection, login, passcode, promise);
return promise.future();
}
public StompServerHandler onAuthenticationRequest(StompServerConnection connection,
String login, String passcode,
Completable handler) {
final AuthenticationProvider auth;
synchronized (this) {
// Stack contention.
auth = authProvider;
}
final StompServer server = connection.server();
if (!server.options().isSecured()) {
if (auth != null) {
LOGGER.warn("Authentication handler set while the server is not secured");
}
context.runOnContext(v -> handler.succeed(true));
return this;
}
if (server.options().isSecured() && auth == null) {
LOGGER.error("Cannot authenticate connection - no authentication provider");
context.runOnContext(v -> handler.succeed(false));
return this;
}
context.runOnContext(v ->
auth.authenticate(new UsernamePasswordCredentials(login, passcode))
.onFailure(err -> context.runOnContext(v2 -> handler.succeed(false)))
.onSuccess(user -> {
// make the user available
users.put(connection.session(), user);
context.runOnContext(v2 -> handler.succeed(true));
}));
return this;
}
/**
* Return the authenticated user for this session.
*
* @param session session ID for the server connection.
* @return the user provided by the {@link AuthenticationProvider} or null if not found.
*/
@Override
public User getUserBySession(String session) {
return this.users.get(session);
}
@Override
public List getDestinations() {
return new ArrayList<>(destinations.keySet());
}
/**
* Gets the destination with the given name..
*
* @param destination the destination
* @return the {@link Destination}, {@code null} if not found.
*/
public Destination getDestination(String destination) {
for (Destination d : destinations.keySet()) {
if (d.matches(destination)) {
return d;
}
}
return null;
}
public Destination getOrCreateDestination(String destination) {
DestinationFactory destinationFactory;
synchronized (this) {
destinationFactory = this.factory;
}
synchronized (vertx) {
Destination d = getDestination(destination);
if (d == null) {
d = destinationFactory.create(vertx, destination);
if (d != null) {
// We use the local map as a set, the value is irrelevant.
destinations.put(d, "");
}
}
return d;
}
}
@Override
public synchronized StompServerHandler destinationFactory(DestinationFactory factory) {
this.factory = factory;
return this;
}
/**
* Configures the STOMP server to act as a bridge with the Vert.x event bus.
*
* @param options the configuration options
* @return the current {@link StompServerHandler}.
* @see Vertx#eventBus()
*/
@Override
public synchronized StompServerHandler bridge(BridgeOptions options) {
destinations.put(Destination.bridge(vertx, options), "");
return this;
}
@Override
public StompServerHandler onAck(StompServerConnection connection, Frame subscription, List messages) {
Handler handler;
synchronized (this) {
handler = onAckHandler;
}
if (handler != null) {
handler.handle(new AcknowledgementImpl(subscription, messages));
}
return this;
}
@Override
public StompServerHandler onNack(StompServerConnection connection, Frame subscribe, List messages) {
Handler handler;
synchronized (this) {
handler = onNackHandler;
}
if (handler != null) {
handler.handle(new AcknowledgementImpl(subscribe, messages));
}
return this;
}
@Override
public synchronized StompServerHandler onAckHandler(Handler handler) {
this.onAckHandler = handler;
return this;
}
@Override
public synchronized StompServerHandler onNackHandler(Handler handler) {
this.onNackHandler = handler;
return this;
}
/**
* Allows customizing the action to do when the server needs to send a `PING` to the client. By default it send a
* frame containing {@code EOL} (specification). However, you can customize this and send another frame. However,
* be aware that this may requires a custom client.
*
* The handler will only be called if the connection supports heartbeats.
*
* @param handler the action to execute when a `PING` needs to be sent.
* @return the current {@link StompServerHandler}
*/
@Override
public synchronized StompServerHandler pingHandler(Handler handler) {
this.pingHandler = handler;
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy