/* * Copyright (C) 2014-2015 ULYSSIS VZW * * This file is part of i++. * * i++ is free software: you can redistribute it and/or modify * it under the terms of version 3 of the GNU Affero General Public License * as published by the Free Software Foundation. No other versions apply. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.ulyssis.ipp.control; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.ulyssis.ipp.control.commands.Command; import org.ulyssis.ipp.status.StatusMessage; import org.ulyssis.ipp.status.StatusReporter; import org.ulyssis.ipp.utils.JedisHelper; import org.ulyssis.ipp.utils.Serialization; import redis.clients.jedis.BinaryJedisPubSub; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisConnectionException; import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; /** * = The command processor * * The command processor should preferably be run on its own thread, * and processes any commands that come in on the given command channel, * and publishes the result on the given status channel. */ public final class CommandProcessor implements Runnable { private static final Logger LOG = LogManager.getLogger(CommandProcessor.class); private final Map<Class<? extends Command>, CommandHandler> commandHandlers = new HashMap<>(); private final Jedis jedis; private final StatusReporter reporter; private final String channel; private BinaryJedisPubSub listener; public CommandProcessor(URI redisUri, String commandChannel, String statusChannel) { this(redisUri, JedisHelper.dbLocalChannel(commandChannel, redisUri), new StatusReporter(redisUri, statusChannel)); } public CommandProcessor(URI redisUri, String channel, StatusReporter statusReporter) { this.jedis = JedisHelper.get(redisUri); this.channel = JedisHelper.dbLocalChannel(channel, redisUri); this.reporter = statusReporter; } public void run() { jedis.subscribe(createCommandListener(), channel.getBytes()); } private BinaryJedisPubSub createCommandListener() { JedisHelper.BinaryCallBackPubSub pubSub = new JedisHelper.BinaryCallBackPubSub(); listener = pubSub; pubSub.addOnMessageListener(this::onCommandMessage); return pubSub; } private void onCommandMessage(byte[] channel, byte[] message) { assert (Arrays.equals(channel, this.channel.getBytes())); try { Command command = Serialization.getJsonMapper().readValue(message, Command.class); handleCommand(command); } catch (IOException e) { LOG.error("Couldn't parse command: {}", new String(message), e); } } public void addHandler(CommandHandler handler) { commandHandlers.put(handler.getCommandClass(), handler); } private void handleCommand(Command command) { LOG.debug("Handing command {}: {}", command.getCommandId(), command.getClass().toString()); if (commandHandlers.containsKey(command.getClass())) { commandHandlers.get(command.getClass()).handle(command, notifyCommandExecuted(command)); } else { notifyCommandUnsupported(command); } } private Consumer<Boolean> notifyCommandExecuted(Command command) { return (result) -> { if (result) { notifySuccess(command); } else { notifyFailure(command); } }; } private void notifySuccess(Command command) { reporter.broadcast(new StatusMessage(StatusMessage.MessageType.COMMAND_COMPLETE, command.getCommandId())); } private void notifyFailure(Command command) { reporter.broadcast(new StatusMessage(StatusMessage.MessageType.COMMAND_FAILED, command.getCommandId())); } private void notifyCommandUnsupported(Command command) { reporter.broadcast(new StatusMessage(StatusMessage.MessageType.COMMAND_UNSUPPORTED, command.getCommandId())); } public void stop() { try { listener.unsubscribe(); } catch (JedisConnectionException ignored) { } } }