Java tutorial
package pt.davidafsilva.slacker.api; /* * #%L * slacker-api * %% * Copyright (C) 2015 David Silva * %% * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * #L% */ import java.util.Objects; import java.util.Optional; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.ServiceHelper; import io.vertx.core.Vertx; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageCodec; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.json.JsonObject; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import io.vertx.core.spi.FutureFactory; /** * This abstract implementation serves as a baseline for slacker command executors. * It includes the communication protocol for registering the executor for the supported command * as well as utility methods for creating the sending the execution results. * * @author david * @since 1.0 */ public abstract class AbstractSlackerExecutor implements SlackerExecutor { // the logger instance private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSlackerExecutor.class); // the vertx instance that deployed this verticle private Vertx vertx; // the verticle context private Context context; // the future factory private FutureFactory futureFactory; // the executor slacker requests consumer private Optional<MessageConsumer<SlackerRequest>> consumer; @Override public void init(final Vertx vertx, final Context context) { this.vertx = vertx; this.context = context; this.futureFactory = ServiceHelper.loadFactory(FutureFactory.class); // register the slacker message codecs registerCodecs(); } /** * Registers the required {@link MessageCodec} for the {@link SlackerRequest} and {@link * SlackerResponse} messages. */ private void registerCodecs() { try { vertx.eventBus().registerCodec(new SlackerRequestMessageCodec()) .registerCodec(new SlackerResponseMessageCodec()); } catch (final IllegalStateException e) { LOGGER.debug("codecs already registered", e); } } @Override public Vertx getVertx() { return vertx; } @Override public void start(final Future<Void> startFuture) throws Exception { LOGGER.info("starting {0}..", identifier()); // start the HELLO slacker protocol final JsonObject helloMessage = new JsonObject().put("i", identifier()).put("d", description()).put("v", version()); vertx.eventBus().send("reg.slacker-server", helloMessage, result -> { if (result.succeeded() && JsonObject.class.isInstance(result.result().body())) { final JsonObject response = (JsonObject) result.result().body(); if (response.containsKey("a")) { // everything went smoothly - register the listener and complete the startup registerListener(response.getString("a")); LOGGER.info("successfully registered {0} executor", identifier()); startFuture.complete(); } else { failStart(startFuture, "no address to bind was received"); } } else { // something unexpected happened failStart(startFuture, Optional.ofNullable(result.cause()).map(Throwable::getMessage).orElse("invalid response")); } }); } /** * Registers the listener for for this executor on the underlying event bus, so that this * executor can successfully receive slacker command requests. * * Note that this method should only be called when the executor has been successfully registered * at the slacker-server. * * @param address the address assigned by slacker-server */ protected void registerListener(final String address) { consumer = Optional.of(vertx.eventBus().consumer(address, this::handleExecutorEvent)); } /** * Creates a success response with the result code equal to {@link ResultCode#OK} and no * message as reply. * This factory method shall be used whenever the executor work-flow has been completed and as * such we want to send a reply to the server without sending any message to the channel/issuer * of the command. * * @return a new instance of a success slacker response */ protected SlackerResponse success() { return SlackerResponseFactory.create(ResultCode.OK, Optional.empty()); } /** * Creates a success response with the result code equal to {@link ResultCode#OK} and with the * given message as reply. * This factory method shall be used whenever the executor work-flow has been completed and as * such we want to send a reply to the server and also inform the channel/issuer of the command. * * @param message the message to be included at the response * @return a new instance of an success slacker response */ protected SlackerResponse success(final String message) { return response(ResultCode.OK, message); } /** * Creates a error response with the result code equal to {@link ResultCode#ERROR} and with the * given message as the error reason. * This factory method shall be used to create the reply message whenever and unexpected error * has occurred, such as an {@link Exception} that has been thrown/catched. * * @param message the message with the error reason * @return a new instance of an error slacker response */ protected SlackerResponse error(final String message) { return response(ResultCode.ERROR, message); } /** * Creates a invalid response with the result code equal to {@link ResultCode#INVALID} and with * the given message as the error reason. * This factory method shall be used whenever invalid data as been received. * * @param message the message with the error reason * @return a new instance of an error slacker response */ protected SlackerResponse invalid(final String message) { return response(ResultCode.INVALID, message); } private SlackerResponse response(final ResultCode code, final String message) { return SlackerResponseFactory.create(code, Optional.of(Objects.requireNonNull(message, "message"))); } /** * Handles an incoming request from the event bus * * @param request the request message to be handled */ private void handleExecutorEvent(final Message<SlackerRequest> request) { LOGGER.info("<=<= receiving incoming request <=<="); LOGGER.debug(request); // execute the request handling asynchronously context.runOnContext(a -> { final Future<SlackerResponse> future = futureFactory.future(); execute(request.body(), future); future.setHandler(handler -> { if (handler.succeeded()) { LOGGER.info("=>=> successfully handled request =>=>"); LOGGER.debug(handler.result()); request.reply(handler.result(), new DeliveryOptions().setCodecName(SlackerResponseMessageCodec.NAME)); } else { request.fail(ResultCode.ERROR.ordinal(), handler.cause().getMessage()); LOGGER.error("failed to handle request", handler.cause()); } }); }); } /** * Fails the startup of this executor with the given failure reason * * @param startFuture the start future to be canceled * @param errorMessage the error/failure message */ private void failStart(final Future<Void> startFuture, final String errorMessage) { final String reason = String.format("unable to register '%s' executor: %s", identifier(), errorMessage); LOGGER.error(reason); startFuture.fail(reason); } @Override public void stop(final Future<Void> stopFuture) throws Exception { LOGGER.info("stopping {0}..", identifier()); consumer.ifPresent(MessageConsumer::unregister); stopFuture.complete(); } }