org.zeromq.ZProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jeromq Show documentation
Show all versions of jeromq Show documentation
Pure Java implementation of libzmq
package org.zeromq;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.zeromq.ZActor.Actor;
import org.zeromq.ZAgent.SelectorCreator;
import org.zeromq.ZMQ.Socket;
import org.zeromq.ZStar.Exit;
import zmq.Msg;
import zmq.SocketBase;
/**
* Implementation of a remotely controlled proxy for 0MQ, using {@link ZActor}.
*
* The goals of this implementation are to delegate the creation of sockets
* in a background thread via a callback interface to ensure their correct use
* and to provide ultimately to end-users the following features.
*
* Basic features:
*
* - Remote Control
*
* - Start: if was paused, flushes the pending messages
*
- Pause: lets the socket queues accumulate messages according to their types
*
- Stop: Shutdowns the proxy, can be restarted
*
- Status: Retrieves the status of the proxy
*
- Cold Restart: Closes and recreates the connections
*
- {@link #restart(ZMsg) Hot Restart}: User-defined behavior with custom messages
*
- {@link #configure(ZMsg) Configure}: User-defined behavior with custom messages
*
- {@link #command(String, boolean) ...}: Custom commands of your own
*
- Exit: Definitive shutdown of the proxy and its control
*
* All the non-custom commands can be performed in asynchronous or synchronous mode.
*
*
* - Proxy mechanism ensured by pluggable pumps
*
* - with built-in low-level {@link org.zeromq.ZProxy.ZmqPump} (zmq.ZMQ): useful for performances
*
- with built-in high-level {@link org.zeromq.ZProxy.ZPump} (ZeroMQ): useful for {@link org.zeromq.ZProxy.ZPump.Transformer message transformation}, lower performances
*
- with your own-custom proxy pump implementing a {@link Pump 1-method interface}
*
*
*
* You can have all the above non-customizable features in about these lines of code:
*
*
* {@code
final ZProxy.Proxy provider = new ZProxy.SimpleProxy()
{
public Socket create(ZContext ctx, ZProxy.Plug place, Object ... args)
{
assert ("TEST".equals(args[0]);
Socket socket = null;
if (place == ZProxy.Plug.FRONT) {
socket = ctx.createSocket(ZMQ.ROUTER);
}
if (place == ZProxy.Plug.BACK) {
socket = ctx.createSocket(ZMQ.DEALER);
}
return socket;
}
public void configure(Socket socket, ZProxy.Plug place, Object ... args)
{
assert ("TEST".equals(args[0]);
int port = -1;
if (place == ZProxy.Plug.FRONT) {
port = socket.bind("tcp://127.0.0.1:6660");
}
if (place == ZProxy.Plug.BACK) {
port = socket.bind("tcp://127.0.0.1:6661");
}
if (place == ZProxy.Plug.CAPTURE && socket != null) {
socket.bind("tcp://127.0.0.1:4263");
}
}
};
ZProxy proxy = ZProxy.newProxy("ProxyOne", provider, "ABRACADABRA", Arrays.asList("TEST"));
}
*
* Once created, the proxy is not started. You have to perform first a start command on it.
* This choice was made because it is easier for a user to start it with one line of code than for the code to internally handle
* different possible starting states (after all, someone may want the proxy started but paused at first or configured in a specific way?)
* and because the a/sync stuff was funnier. Life is unfair ...
* Or maybe an idea is floating in the air?
*
*
* You can then use it like this:
*
* {@code
final boolean async = false, sync = true;
String status = null;
status = proxy.status();
status = proxy.pause(sync);
status = proxy.start(async);
status = proxy.restart(new ZMsg());
status = proxy.status(async);
status = proxy.stop(sync);
boolean here = proxy.sign();
ZMsg cfg = new ZMsg();
msg.add("CONFIG-1");
ZMsg rcvd = proxy.configure(cfg);
proxy.exit();
status = proxy.status(sync);
assert (!proxy.started());
}
*
*
* A {@link #command(Command, boolean) programmatic interface} with enums is also available.
*
*
*/
// Proxy for 0MQ.
public class ZProxy
{
/**
* Possible places for sockets in the proxy.
*/
public enum Plug
{
FRONT, // The position of the frontend socket.
BACK, // The position of the backend socket.
CAPTURE // The position of the capture socket.
}
// Contract for socket creation and customizable configuration in proxy threading.
public interface Proxy
{
/**
* Creates and initializes (bind, options ...) the socket for the given plug in the proxy.
* The proxy will close them afterwards, and the context as well if not provided in the constructor.
* There is no need to keep a reference on the created socket or the context given in parameter.
*
* @param ctx the context used for initialization.
* @param place the position for the future created socket in the proxy.
* @param args the optional array of arguments that has been passed at the creation of the ZProxy.
* @return the created socket. Possibly null only for capture.
*/
Socket create(ZContext ctx, Plug place, Object... args);
/**
* Configures the given socket.
*
* @param socket the socket to configure
* @param place the position for the socket in the proxy
* @param args the optional array of arguments that has been passed at the creation of the ZProxy.
* @return true if successfully configured, otherwise false
*/
boolean configure(Socket socket, Plug place, Object... args) throws IOException;
/**
* Performs a hot restart of the given socket.
* Usually an unbind/bind but you can use whatever method you like.
*
* @param cfg the custom configuration message sent by the control.
* @param socket the socket to hot restart
* @param place the position for the socket in the proxy
* @param args the optional array of arguments that has been passed at the creation of the ZProxy.
* @return true to perform a cold restart instead, false to do nothing. All the results will be collected from calls for all plugs.
* If any of them returns true, the cold restart is performed.
*/
boolean restart(ZMsg cfg, Socket socket, Plug place, Object... args) throws IOException;
/**
* Configures the proxy with a custom message.
*
* Note: you need to send one (1) mandatory custom response message with the pipe before the end of this call.
*
* @param pipe the control pipe
* @param cfg the custom configuration message sent by the control
* @param frontend the frontend socket
* @param backend the backend socket
* @param capture the optional capture socket
* @param args the optional array of arguments that has been passed at the creation of the ZProxy.
* @return true to continue the proxy, false to exit
*/
boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture, Object... args);
/**
* Handles a custom command not recognized by the proxy.
*
* Note: you need to send the current state at the end of the call.
*
* @param pipe the control pipe
* @param cmd the unrecognized command
* @param frontend the frontend socket
* @param backend the backend socket
* @param capture the optional capture socket
* @param args the optional array of arguments that has been passed at the creation of the ZProxy.
*
* @return true to continue the proxy, false to exit
*/
boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture, Object... args);
// this may be useful
public abstract static class SimpleProxy implements Proxy
{
@Override
public boolean restart(ZMsg cfg, Socket socket, Plug place, Object... args) throws IOException
{
return true;
}
@Override
public boolean configure(Socket pipe, ZMsg cfg, Socket frontend, Socket backend, Socket capture,
Object... args)
{
return true;
}
@Override
public boolean custom(Socket pipe, String cmd, Socket frontend, Socket backend, Socket capture,
Object... args)
{
return true;
}
}
}
/**
* Creates a new proxy in a ZeroMQ way.
* This proxy will be less efficient than the
* {@link #newZProxy(ZContext, String, org.zeromq.ZProxy.Proxy, String, Object...) low-level one}.
*
* @param ctx the context used for the proxy.
* Possibly null, in this case a new context will be created and automatically destroyed afterwards.
* @param name the name of the proxy. Possibly null.
* @param selector the creator of the selector used for the internal polling. Not null.
* @param sockets the sockets creator of the proxy. Not null.
* @param args an optional array of arguments that will be passed at the creation.
*
* @return the created proxy.
*/
// creates a new proxy
public static ZProxy newZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets,
String motdelafin, Object... args)
{
return new ZProxy(ctx, name, selector, sockets, new ZPump(), motdelafin, args);
}
public static ZProxy newZProxy(ZContext ctx, String name, Proxy sockets, String motdelafin, Object... args)
{
return new ZProxy(ctx, name, null, sockets, new ZPump(), motdelafin, args);
}
/**
* Creates a new low-level proxy for better performances.
*
* @param ctx the context used for the proxy.
* Possibly null, in this case a new context will be created and automatically destroyed afterwards.
* @param name the name of the proxy. Possibly null.
* @param selector the creator of the selector used for the internal polling. Not null.
* @param sockets the sockets creator of the proxy. Not null.
* @param args an optional array of arguments that will be passed at the creation.
*
* @return the created proxy.
*/
// creates a new low-level proxy
public static ZProxy newProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, String motdelafin,
Object... args)
{
return new ZProxy(ctx, name, selector, sockets, new ZmqPump(), motdelafin, args);
}
public static ZProxy newProxy(ZContext ctx, String name, Proxy sockets, String motdelafin, Object... args)
{
return new ZProxy(ctx, name, null, sockets, new ZmqPump(), motdelafin, args);
}
/**
* Starts the proxy.
*
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the read status
*/
public String start(boolean sync)
{
return command(START, sync);
}
/**
* Pauses the proxy.
* A paused proxy will cease processing messages, causing
* them to be queued up and potentially hit the high-water mark on the
* frontend or backend socket, causing messages to be dropped, or writing
* applications to block.
*
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the read status
*/
public String pause(boolean sync)
{
return command(PAUSE, sync);
}
/**
* Stops the proxy.
*
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the read status
*/
public String stop(boolean sync)
{
return command(STOP, sync);
}
/**
* Sends a command message to the proxy actor.
* Can be useful for programmatic interfaces.
* Does not works with commands {@link #CONFIG CONFIG} and {@link #RESTART RESTART}.
*
* @param command the command to execute. Not null.
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the read status
*/
public String command(String command, boolean sync)
{
assert (!CONFIG.equals(command));
assert (!RESTART.equals(command));
if (STATUS.equals(command)) {
return status(sync);
}
if (EXIT.equals(command)) {
return exit();
}
// consume the status in the pipe
String status = recvStatus();
if (agent.send(command)) {
// the pipe is refilled
if (sync) {
status = status(true);
}
}
return status;
}
/**
* Sends a command message to the proxy actor.
* Can be useful for programmatic interfaces.
* Does not works with commands {@link Command#CONFIG CONFIG} and {@link Command#RESTART RESTART}.
*
* @param command the command to execute.
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the read state
*/
public State command(Command command, boolean sync)
{
return State.valueOf(command(command.name(), sync));
}
/**
* Sends a command message to the proxy actor.
* Can be useful for programmatic interfaces.
* Works only with commands {@link Command#CONFIG CONFIG} and {@link Command#RESTART RESTART}.
*
* @param command the command to execute.
* @param msg the custom message to transmit.
* @param sync true to read the status in synchronous way, false for asynchronous mode
* @return the response message
*/
public ZMsg command(Command command, ZMsg msg, boolean sync)
{
if (command == Command.CONFIG) {
return configure(msg);
}
if (command == Command.RESTART) {
String status = restart(msg);
msg = new ZMsg();
msg.add(status);
return msg;
}
return null;
}
/**
* Configures the proxy.
* The distant side has to send back one (1) mandatory response message.
*
* @param msg the custom message sent as configuration tip
* @return the mandatory response message of the configuration.
*/
public ZMsg configure(ZMsg msg)
{
msg.addFirst(CONFIG);
if (agent.send(msg)) {
// consume the status in the pipe
recvStatus();
ZMsg reply = agent.recv();
assert (reply != null);
// refill the pipe with status
agent.send(STATUS);
return reply;
}
return null;
}
/**
* Restarts the proxy. Stays alive.
*
* @param hot null to make a cold restart (closing then re-creation of the sockets)
* or a configuration message to perform a configurable hot restart,
*/
public String restart(ZMsg hot)
{
ZMsg msg = new ZMsg();
msg.add(RESTART);
final boolean cold = hot == null;
if (cold) {
msg.add(Boolean.toString(false));
}
else {
msg.add(Boolean.toString(true));
msg.append(hot);
}
String status = EXITED;
if (agent.send(msg)) {
status = status(false);
}
return status;
}
/**
* Stops the proxy and exits.
*
* @param sync forced to true to read the status in synchronous way.
* @return the read status.
* @deprecated The call is synchronous: the sync parameter is ignored,
* as it leads to many mistakes in case of a provided ZContext.
*/
@Deprecated
public String exit(boolean sync)
{
return exit();
}
/**
* Stops the proxy and exits.
* The call is synchronous.
*
* @return the read status.
*
*/
public String exit()
{
agent.send(EXIT);
exit.awaitSilent();
agent.close();
return EXITED;
}
/**
* Inquires for the status of the proxy.
* This call is synchronous.
*/
public String status()
{
return status(true);
}
/**
* Inquires for the status of the proxy.
*
* @param sync true to read the status in synchronous way, false for asynchronous mode.
* If false, you get the last cached status of the proxy
*/
public String status(boolean sync)
{
if (exit.isExited()) {
return EXITED;
}
try {
String status = recvStatus();
if (agent.send(STATUS) && sync) {
// wait for the response to emulate sync
status = recvStatus();
// AND refill a status
if (EXITED.equals(status) || !agent.send(STATUS)) {
return EXITED;
}
}
return status;
}
catch (ZMQException e) {
return EXITED;
}
}
// receives the last known state of the proxy
private String recvStatus()
{
if (!agent.sign()) {
return EXITED;
}
// receive the status response
final ZMsg msg = agent.recv();
if (msg == null) {
return EXITED;
}
String status = msg.popString();
msg.destroy();
return status;
}
/**
* Binary inquiry for the status of the proxy.
*/
public boolean isStarted()
{
return started();
}
/**
* Binary inquiry for the status of the proxy.
*/
public boolean started()
{
String status = status(true);
return STARTED.equals(status);
}
// to handle commands in a more java-centric way
public enum Command
{
START,
PAUSE,
STOP,
RESTART,
EXIT,
STATUS,
CONFIG
}
// commands for the control pipe
private static final String START = Command.START.name();
private static final String PAUSE = Command.PAUSE.name();
private static final String STOP = Command.STOP.name();
private static final String RESTART = Command.RESTART.name();
private static final String EXIT = Command.EXIT.name();
private static final String STATUS = Command.STATUS.name();
private static final String CONFIG = Command.CONFIG.name();
// to handle states in a more java-centric way
public enum State
{
ALIVE,
STARTED,
PAUSED,
STOPPED,
EXITED
}
// state responses from the control pipe
public static final String STARTED = State.STARTED.name();
public static final String PAUSED = State.PAUSED.name();
public static final String STOPPED = State.STOPPED.name();
public static final String EXITED = State.EXITED.name();
// defines the very first time where no command changing the state has been issued
public static final String ALIVE = State.ALIVE.name();
private static final AtomicInteger counter = new AtomicInteger();
// the endpoint to the distant proxy actor
private final ZAgent agent;
// the synchronizer for exiting
private final Exit exit;
/**
* Creates a new unnamed proxy.
*
* @param selector the creator of the selector used for the proxy.
* @param creator the creator of the sockets of the proxy.
*/
public ZProxy(SelectorCreator selector, Proxy creator, String motdelafin, Object... args)
{
this(null, null, selector, creator, null, motdelafin, args);
}
/**
* Creates a new named proxy.
*
* @param name the name of the proxy (used in threads naming).
* @param selector the creator of the selector used for the proxy.
* @param creator the creator of the sockets of the proxy.
*/
public ZProxy(String name, SelectorCreator selector, Proxy creator, String motdelafin, Object... args)
{
this(null, name, selector, creator, null, motdelafin, args);
}
/**
* Creates a new named proxy.
*
* @param ctx the main context used.
* If null, a new context will be created and closed at the stop of the operation.
* If not null, it is the responsibility of the call to close it.
* @param name the name of the proxy (used in threads naming).
* @param selector the creator of the selector used for the proxy.
* @param sockets the creator of the sockets of the proxy.
* @param pump the pump used for the proxy
*/
public ZProxy(ZContext ctx, String name, SelectorCreator selector, Proxy sockets, Pump pump, String motdelafin,
Object... args)
{
super();
// arguments parsing
if (pump == null) {
pump = new ZmqPump();
}
int count = 1;
count += args.length;
Object[] vars = new Object[count];
vars[0] = sockets;
Actor shadow = null;
// copy the arguments and retrieve the last optional shadow given in input
for (int index = 0; index < args.length; ++index) {
Object arg = args[index];
if (arg instanceof Actor) {
shadow = (Actor) arg;
}
vars[index + 1] = arg;
}
// handle the actor
int id = counter.incrementAndGet();
Actor actor = new ProxyActor(name, pump, id);
if (shadow != null) {
actor = new ZActor.Duo(actor, shadow);
}
ZActor zactor = new ZActor(ctx, selector, actor, motdelafin, vars);
agent = zactor.agent(); // NB: the zactor is also its own agent
exit = zactor.exit();
}
// defines a pump that will flow messages from one socket to another
public interface Pump
{
/**
* Transfers a message from one source to one destination, with an optional capture.
*
* @param src the plug of the source socket
* @param source the socket where to receive the message from.
* @param capture the optional sockets where to send the message to. Possibly null.
* @param dst the plug of the destination socket
* @param destination the socket where to send the message to.
*
* @return false in case of error or interruption, true if successfully transferred the message
*/
boolean flow(Plug src, Socket source, Socket capture, Plug dst, Socket destination);
}
// acts in background to proxy messages
private static final class ProxyActor extends ZActor.SimpleActor
{
// the states container of the proxy
private static final class State
{
@Override
public String toString()
{
return "State [alive=" + alive + ", started=" + started + ", paused=" + paused + ", restart=" + restart
+ ", hot=" + hot + "]";
}
// are we alive ?
private boolean alive = false;
// are we started ?
private boolean started = false;
// are we paused ?
private boolean paused = false;
// controls creation of a new agent if asked of a cold restart
private boolean restart = false;
// one-shot configuration for hot restart
private ZMsg hot;
}
// the state of the proxy
private final State state = new State();
// used to transfer message from one socket to another
private final Pump transport;
// the nice name of the proxy
private final String name;
// the provider of the sockets
private Proxy provider;
// the sockets creator user arguments
private Object[] args;
private Socket frontend;
private Socket backend;
private Socket capture;
// creates a new Proxy actor.
public ProxyActor(String name, Pump transport, int id)
{
if (name == null) {
// default basic name
this.name = String.format("ZProxy-%sd", id);
}
else {
this.name = name;
}
this.transport = transport;
}
@Override
public String premiere(Socket pipe)
{
ZMsg reply = new ZMsg();
reply.add(ALIVE);
reply.send(pipe);
return name;
}
// creates the sockets before the start of the proxy
@Override
public List createSockets(ZContext ctx, Object... args)
{
provider = (Proxy) args[0];
this.args = new Object[args.length - 1];
System.arraycopy(args, 1, this.args, 0, this.args.length);
frontend = provider.create(ctx, Plug.FRONT, this.args);
capture = provider.create(ctx, Plug.CAPTURE, this.args);
backend = provider.create(ctx, Plug.BACK, this.args);
assert (frontend != null);
assert (backend != null);
return Arrays.asList(frontend, backend);
}
@Override
public void start(Socket pipe, List sockets, ZPoller poller)
{
// init the state machine
state.alive = true;
state.restart = false;
}
// Process a control message
@Override
public boolean backstage(Socket pipe, ZPoller poller, int events)
{
assert (state.hot == null);
String cmd = pipe.recvStr();
// a message has been received from the API
if (START.equals(cmd)) {
if (start(poller)) {
return status().send(pipe);
}
// unable to start the proxy, exit
state.restart = false;
return false;
}
else if (STOP.equals(cmd)) {
stop();
return status().send(pipe);
}
else if (PAUSE.equals(cmd)) {
pause(poller, true);
return status().send(pipe);
}
else if (RESTART.equals(cmd)) {
String val = pipe.recvStr();
boolean hot = Boolean.parseBoolean(val);
return restart(pipe, hot);
}
else if (STATUS.equals(cmd)) {
return status().send(pipe);
}
else if (CONFIG.equals(cmd)) {
ZMsg cfg = ZMsg.recvMsg(pipe);
boolean rc = provider.configure(pipe, cfg, frontend, backend, capture, args);
cfg.destroy();
return rc;
}
else if (EXIT.equals(cmd)) {
// stops the proxy and the agent.
// the status will be sent at the end of the loop
state.restart = false;
}
else {
return provider.custom(pipe, cmd, frontend, backend, capture, args);
}
return false;
}
// returns the status
private ZMsg status()
{
ZMsg reply = new ZMsg();
if (!state.alive) {
reply.add(EXITED);
}
else if (state.paused) {
reply.add(PAUSED);
}
else if (state.started) {
reply.add(STARTED);
}
else {
reply.add(STOPPED);
}
return reply;
}
// starts the proxy sockets
private boolean start(ZPoller poller)
{
boolean success = true;
if (!state.started) {
try {
success = false;
success |= provider.configure(frontend, Plug.FRONT, args);
success |= provider.configure(backend, Plug.BACK, args);
success |= provider.configure(capture, Plug.CAPTURE, args);
state.started = true;
}
catch (RuntimeException | IOException e) {
e.printStackTrace();
// unable to configure proxy, exit
state.restart = false;
state.started = false;
return false;
}
}
pause(poller, false);
return success;
}
// pauses the proxy sockets
private boolean pause(ZPoller poller, boolean pause)
{
state.paused = pause;
if (pause) {
poller.unregister(frontend);
poller.unregister(backend);
// TODO why not a mechanism for eventually flushing the sockets during the pause?
}
else {
poller.register(frontend, ZPoller.POLLIN);
poller.register(backend, ZPoller.POLLIN);
// Now Wait also until there are either requests or replies to process.
}
return true;
}
private boolean stop()
{
// restart the actor in stopped state
state.started = false;
state.paused = false;
// close connections
state.restart = true;
return true;
}
// handles the restart command in both modes
private boolean restart(Socket pipe, boolean hot)
{
state.restart = true;
if (hot) {
assert (provider != null);
state.hot = ZMsg.recvMsg(pipe);
// continue with the same agent
return true;
}
else {
// stop the loop and restart a new agent
// with the same started state
// the next loop will refill the updated status
return false;
}
}
@Override
public long looping(Socket pipe, ZPoller poller)
{
state.hot = null;
return super.looping(pipe, poller);
}
// a message has been received for the proxy to process
@Override
public boolean stage(Socket socket, Socket pipe, ZPoller poller, int events)
{
if (socket == frontend) {
// Process a request.
return transport.flow(Plug.FRONT, frontend, capture, Plug.BACK, backend);
}
if (socket == backend) {
// Process a reply.
return transport.flow(Plug.BACK, backend, capture, Plug.FRONT, frontend);
}
return false;
}
@Override
public boolean looped(Socket pipe, ZPoller poller)
{
if (state.restart) {
if (state.hot == null) {
// caught the cold restart
return false;
}
else {
// caught the hot restart
ZMsg cfg = state.hot;
state.hot = null;
// we perform a cold restart if the provider says so
boolean cold;
ZMsg dup = cfg.duplicate();
try {
cold = provider.restart(dup, frontend, Plug.FRONT, this.args);
dup.destroy();
dup = cfg.duplicate();
cold |= provider.restart(dup, backend, Plug.BACK, this.args);
dup.destroy();
dup = cfg.duplicate();
cold |= provider.restart(dup, capture, Plug.CAPTURE, this.args);
dup.destroy();
cfg.destroy();
}
catch (RuntimeException | IOException e) {
e.printStackTrace();
state.restart = false;
return false;
}
// cold restart means the loop has to stop.
return !cold;
}
}
return true;
}
// called in the proxy thread when it stopped.
@Override
public boolean destroyed(ZContext ctx, Socket pipe, ZPoller poller)
{
if (capture != null) {
ctx.destroySocket(capture);
}
state.alive = false;
if (!state.restart) {
status().send(pipe);
}
return state.restart;
}
}
/**
* A pump that reads a message as a whole before transmitting it.
* It offers a way to transform messages for capture and destination.
*/
public static class ZPump implements Pump
{
private static final Identity IDENTITY = new Identity();
// the messages transformer
private final Transformer transformer;
// transforms one message into another
public interface Transformer
{
/**
* Transforms a ZMsg into another ZMsg.
* Please note that this will be used during the message transfer,
* so lengthy operations will have a cost on performances by definition.
* If you return back another message than the one given in input, then this one has to be destroyed by you.
* @param msg the message to transform
* @param src the source plug
* @param dst the destination plug
* @return the transformed message
*/
ZMsg transform(ZMsg msg, Plug src, Plug dst);
}
private static class Identity implements Transformer
{
@Override
public ZMsg transform(ZMsg msg, Plug src, Plug dst)
{
return msg;
}
}
public ZPump()
{
this(null);
}
public ZPump(Transformer transformer)
{
super();
this.transformer = transformer == null ? IDENTITY : transformer;
}
@Override
public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination)
{
boolean success;
// we read the whole message
ZMsg msg = ZMsg.recvMsg(source);
if (msg == null) {
return false;
}
if (capture != null) {
// Copy transformed message to capture socket if any message
// TODO what if the transformer modifies or destroys the original message ?
ZMsg cpt = transformer.transform(msg, splug, Plug.CAPTURE);
// boolean destroy = !msg.equals(cpt); // TODO ?? which one
boolean destroy = msg != cpt;
success = cpt.send(capture, destroy);
if (!success) {
// not successful, but we can still try to send it to the destination
}
}
ZMsg dst = transformer.transform(msg, splug, dplug);
// we send the whole transformed message
success = dst.send(destination);
// finished
msg.destroy();
return success;
}
}
/**
* A specialized transport for better transmission purposes
* that will send each packets individually instead of the whole message.
*/
private static final class ZmqPump implements Pump
{
// transfers each message as a whole by sending each packet received to the capture socket
@Override
public boolean flow(Plug splug, Socket source, Socket capture, Plug dplug, Socket destination)
{
boolean rc;
SocketBase src = source.base();
SocketBase dst = destination.base();
SocketBase cpt = capture == null ? null : capture.base();
// we transfer the whole message
while (true) {
// we read the packet
Msg msg = src.recv(0);
if (msg == null) {
return false;
}
long more = src.getSocketOpt(zmq.ZMQ.ZMQ_RCVMORE);
if (more < 0) {
return false;
}
// Copy message to capture socket if any packet
if (cpt != null) {
Msg ctrl = new Msg(msg);
rc = cpt.send(ctrl, more > 0 ? zmq.ZMQ.ZMQ_SNDMORE : 0);
if (!rc) {
// not successful, but we can still try to send it to the destination
}
}
// we send the packet
rc = dst.send(msg, more > 0 ? zmq.ZMQ.ZMQ_SNDMORE : 0);
if (!rc) {
return false;
}
if (more == 0) {
break;
}
}
return true;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy