
org.vertx.mods.WorkQueue Maven / Gradle / Ivy
Show all versions of mod-work-queue Show documentation
/*
* Copyright 2011-2012 the original author or authors.
*
* Licensed 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.vertx.mods;
import org.vertx.java.busmods.BusModBase;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;
import java.util.LinkedList;
import java.util.Queue;
/**
* Work Queue Bus Module
* Please see the busmods manual for a full description
*
* @author Tim Fox
*/
public class WorkQueue extends BusModBase {
// LHS is typed as ArrayList to ensure high perf offset based index operations
private final Queue processors = new LinkedList<>();
private final Queue messages = new LinkedList<>();
private long processTimeout;
private String persistorAddress;
private String collection;
/**
* Start the busmod
*/
public void start() {
super.start();
String address = getMandatoryStringConfig("address");
processTimeout = super.getOptionalLongConfig("process_timeout", 5 * 60 * 1000);
persistorAddress = super.getOptionalStringConfig("persistor_address", null);
collection = super.getOptionalStringConfig("collection", null);
if (persistorAddress != null) {
loadMessages();
}
Handler> registerHandler = new Handler>() {
public void handle(Message message) {
doRegister(message);
}
};
eb.registerHandler(address + ".register", registerHandler);
Handler> unregisterHandler = new Handler>() {
public void handle(Message message) {
doUnregister(message);
}
};
eb.registerHandler(address + ".unregister", unregisterHandler);
Handler> sendHandler = new Handler>() {
public void handle(Message message) {
doSend(message);
}
};
eb.registerHandler(address, sendHandler);
}
// Load all the message into memory
// TODO - we could limit the amount we load at startup
private void loadMessages() {
JsonObject msg = new JsonObject().putString("action", "find").putString("collection", collection)
.putObject("matcher", new JsonObject());
eb.send(persistorAddress, msg, createLoadReplyHandler());
}
private void processLoadBatch(JsonArray toLoad) {
for (Object obj: toLoad) {
if (obj instanceof JsonObject) {
messages.add(new LoadedHolder((JsonObject)obj));
}
}
checkWork();
}
private interface MessageHolder {
JsonObject getBody();
void reply(JsonObject reply, Handler> replyReplyHandler);
}
private Handler> createLoadReplyHandler() {
return new Handler>() {
public void handle(Message reply) {
processLoadBatch(reply.body().getArray("results"));
if (reply.body().getString("status").equals("more-exist")) {
// Get next batch
reply.reply((JsonObject)null, createLoadReplyHandler());
}
}
};
}
private void checkWork() {
if (!messages.isEmpty() && !processors.isEmpty()) {
final MessageHolder message = messages.poll();
final String address = processors.poll();
final long timeoutID = vertx.setTimer(processTimeout, new Handler() {
public void handle(Long id) {
// Processor timed out - put message back on queue
logger.warn("Processor timed out, message will be put back on queue");
messages.add(message);
}
});
eb.send(address, message.getBody(), new Handler>() {
public void handle(Message reply) {
messageReplied(message, reply, address, timeoutID);
}
});
}
}
// A reply has been received from the processor
private void messageReplied(final MessageHolder message, final Message reply,
final String processorAddress,
final long timeoutID) {
if (reply.replyAddress() != null) {
// The reply itself has a reply specified so we don't consider the message processed just yet
message.reply(reply.body(), new Handler>() {
public void handle(final Message replyReply) {
reply.reply(replyReply.body(), new Handler>() {
public void handle(Message replyReplyReply) {
messageReplied(new NonLoadedHolder(replyReply), replyReplyReply, processorAddress, timeoutID);
}
});
}
});
} else {
if (persistorAddress != null) {
JsonObject msg = new JsonObject().putString("action", "delete").putString("collection", collection)
.putObject("matcher", message.getBody());
eb.send(persistorAddress, msg, new Handler>() {
public void handle(Message replyReply) {
if (!replyReply.body().getString("status").equals("ok")) {
logger.error("Failed to delete document from queue: " + replyReply.body().getString("message"));
}
messageProcessed(timeoutID, processorAddress, message, reply);
}
});
} else {
messageProcessed(timeoutID, processorAddress, message, reply);
}
}
}
// The conversation between the sender and the processor has ended, so we can add the processor back on the queue
private void messageProcessed(long timeoutID, String processorAddress, MessageHolder message,
Message reply) {
// The processor
// can go back on the queue
vertx.cancelTimer(timeoutID);
processors.add(processorAddress);
message.reply(reply.body(), null);
checkWork();
}
private void doRegister(Message message) {
String processor = getMandatoryString("processor", message);
if (processor == null) {
return;
}
processors.add(processor);
checkWork();
sendOK(message);
}
private void doUnregister(Message message) {
String processor = getMandatoryString("processor", message);
if (processor == null) {
return;
}
processors.remove(processor);
sendOK(message);
}
private void doSend(final Message message) {
if (persistorAddress != null) {
JsonObject msg = new JsonObject().putString("action", "save").putString("collection", collection)
.putObject("document", message.body());
eb.send(persistorAddress, msg, new Handler>() {
public void handle(Message reply) {
if (reply.body().getString("status").equals("ok")) {
actualSend(message);
} else {
sendAcceptedReply(message.body(), "error", reply.body().getString("message"));
sendError(message, reply.body().getString("message"));
}
}
});
} else {
actualSend(message);
}
}
private void sendAcceptedReply(JsonObject body, String status, String message) {
String acceptedReply = body.getString("accepted-reply");
if (acceptedReply != null) {
JsonObject repl = new JsonObject().putString("status", status);
if (message != null) {
repl.putString("message", message);
}
eb.send(acceptedReply, repl);
}
}
private void actualSend(Message message) {
messages.add(new NonLoadedHolder(message));
//Been added to the queue so reply if appropriate
sendAcceptedReply(message.body(), "accepted", null);
checkWork();
}
private static class LoadedHolder implements MessageHolder {
private final JsonObject body;
private LoadedHolder(JsonObject body) {
this.body = body;
}
public JsonObject getBody() {
return body;
}
public void reply(JsonObject reply, Handler> replyReplyHandler) {
//Do nothing - we are loaded from storage so the sender has long gone
}
}
private static class NonLoadedHolder implements MessageHolder {
private final Message message;
private NonLoadedHolder(Message message) {
this.message = message;
}
public JsonObject getBody() {
return message.body();
}
public void reply(JsonObject reply, Handler> replyReplyHandler) {
message.reply(reply, replyReplyHandler);
}
}
}