Search

Domino Upgrade

VersionSupport end
5.0
6.0
6.5
7.0
8.0
8.5
Upgrade to 9.x now!
(see the full Lotus lifcyle) To make your upgrade a success use the Upgrade Cheat Sheet.
Contemplating to replace Notes? You have to read this! (also available on Slideshare)

Languages

Other languages on request.

Twitter

Useful Tools

Get Firefox
Use OpenDNS
The support for Windows XP has come to an end . Time to consider an alternative to move on.

About Me

I am the "IBM Collaboration & Productivity Advisor" for IBM Asia Pacific. I'm based in Singapore.
Reach out to me via:
Follow notessensei on Twitter
(posts)
Skype
Sametime
IBM
Facebook
LinkedIn
XING
Amazon Store
Amazon Kindle
NotesSensei's Spreadshirt shop
profile for stwissel on Stack Exchange, a network of free, community-driven Q&A sites

« Now we are token - Authorization using JSON Web Token in Domino | Main| Mach Dich auf die Socken! »

Annotations to supercharge your vert.x development

ProjectCastle is well under way. Part of it, the part talking to Domino, is written in Java8 and vert.x. With some prior experience in node.js development vert.x will look familiar: base on event loop and callbacks, you develop in a very similar way. The big differences: vert.x runs on the JVM8, it is by nature of the JVM multi-threaded, features an event bus and is polyglot - you can develop in a mix of languages: Java, JavaScript, Jython, Groovy etc.
This post reflects some of the approaches I found useful developing with vert.x in Java. There are 3 components which are core to vert.x development:
  • Verticle

    A unit of compute running with an event loop. Usually you start one Verticle (optional with multiple instances) as your application, but you might want/need to start additional ones for longer running tasks. A special version is the worker verticle, that runs from a thread pool to allow execution of blocking operations
  • EventBus

    The different components of your application message each other via the EventBus. Data send over the EventBus can be a String, a JsonObject or a buffer. You also can send any arbitrary Java class as message once you have defined a codec for it
  • Route

    Like in node.js a vert.x web application can register routes and their handlers to react on web input under various conditions. Routes can be defined using URLs, HTTP Verbs, Content-Types ( for POST/PUT/PATCH operations)
Ideally when defining a route and a handler, a verticle or a potential message for the EventBus, all necessary code stays contained in the respective source code file. The challenge here is to register the components when the application starts. Your main Verticle doesn't know what components are in your application and manually maintain a loader code is a pain to keep in sync (besides leading to merge conflicts when working in a team).
Java annotations to the rescue! If you are new to annotations, go and check out this tutorial to get up to speed. For my project I defined three of them, with one being able to be applied multiple times.

CastleRequest

A class annotated with CastleRequest registers its handler with the EventBus, so the class can be sent over the EventBus and get encoded/decode appropriately. A special value for the annotation is "self" which indicates, that the class itself implements the MessageCodec interface
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CastleRequest {
  // We use value to ease the syntax
  // to @CastleRequest(NameOfCodec)
  // Special value: self = class implements the MessageCodec interface
  String value();
}

CastleRoute

This annotation can be assigned multiple times, so 2 annotation interfaces are needed
@Documented
@Repeatable(CastleRoutes.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CastleRoute {
  String route();
  String description();
  String mimetype() default "any";
  String method() default "any";
}

and the repeatability annotation (new with Java8):
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CastleRoutes {
  CastleRoute[] value();
}

CastleVerticle

Classes marked with this annotation are loaded as verticles. They can implement listeners to the whole spectrum of vert.x listening capabilities
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CastleVerticle {
  String type() default "worker";
  int instances() default 0;
  boolean multithreaded() default false;
}

Loading it all

With a little glue code (see below), all Verticles, Routes and Codecs get loaded by looking up the annotations. You add a new file or remove one: no additional action is required.
Since traversing all classes is expensive, we have one method that does that once calling the 3 different annotation processors once for each class
private void loadClassesByAnnotation(final Router router) {
    try {
      // Get the classes from the current loader to check for more
      final ClassPath classPath = ClassPath.from(this.getClass().getClassLoader());
      // Extract the package name from the full class name
      String packageName = this.getClass().getName();
      packageName = packageName.substring(0, packageName.lastIndexOf("."));
      // Get all classes in this tree
      for (final ClassInfo classInfo : classPath.getTopLevelClassesRecursive(packageName)) {
        @SuppressWarnings("rawtypes")
        final Class candidate = classInfo.load();
        this.loadVerticleByAnnotation(candidate);
        this.loadCodecByAnnotation(candidate);
        this.loadRouteByAnnotation(candidate);
      }
    } catch (IOException e) {
      this.logger.error(e);
    }
 }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  private void loadCodecByAnnotation(final Class candidate) {
    EventBus eventBus = this.vertx.eventBus();
    final Annotation[] annotation = candidate.getAnnotationsByType(CastleRequest.class);
    // Registering all Codecs
    for (int i = 0; i < annotation.length; i++) {
      try {
        final CastleRequest cr = (CastleRequest) annotation[i];
        final String codecClassName = cr.value();
        Object codecCandidate;
        if (codecClassName.equalsIgnoreCase("self")) {
          // The object contains its own message codec
          codecCandidate = candidate.newInstance();
        } else {
          codecCandidate = Class.forName(codecClassName).newInstance();
        }
        if (codecCandidate instanceof MessageCodec) {
          MessageCodec messageCodec = (MessageCodec) codecCandidate;
          eventBus.registerDefaultCodec(candidate, messageCodec);
        } else {
          this.logger.error("Class with CastleRequest Annotation has wrong codec type:" + codecClassName);
        }
      } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
        this.logger.error(e);
      }
    }
  }

  @SuppressWarnings({ "unchecked", "rawtypes" })
  private void loadRouteByAnnotation(final Class candidate) {
    final Annotation[] annotation = candidate.getAnnotationsByType(CastleRoute.class);
    for (int i = 0; i < annotation.length; i++) {
      try {
        final CastleRoute cr = (CastleRoute) annotation[i];
        // We got a class that shall be used as a router
        if (CastleRouteHandler.class.isAssignableFrom(candidate)) {
          // If a class has multiple routes assigned we might load them more
          // than once but that's acceptable
          final Class toBeloaded = candidate;
          CastleRouteHandler crh = toBeloaded.newInstance();
          // Finally initialization and recording in our list
          crh.init(this.vertx, router, cr.method(), cr.route(), cr.mimetype(), cr.description());
          this.routeList.put(cr.route(), cr.description());
          this.routeHandlers.add(crh);
        } else {
          this.logger.error("Class with CastleRouter Annotation has wrong type:" + candidate.getName());
        }
      } catch (InstantiationException | IllegalAccessException e) {
        this.logger.error(e);
      }
    }
  }

@SuppressWarnings({ "rawtypes", "unchecked" })
  private void loadVerticleByAnnotation(final Class candidate) {
    final Annotation[] annotation = candidate.getAnnotationsByType(CastleVerticle.class);
    for (int i = 0; i < annotation.length; i++) {
      if (Verticle.class.isAssignableFrom(candidate)) {
        final CastleVerticle cv = (CastleVerticle) annotation[i];
        final String verticleID = candidate.getName();
        final DeploymentOptions options = new DeploymentOptions();
        if (cv.type().equalsIgnoreCase("worker")) {
          options.setWorker(true);
        }
        // Overwrite the instances if number is specified
        if (cv.instances() > 0) {
          options.setInstances(cv.instances());
        }
        options.setMultiThreaded(cv.multithreaded());
        this.vertx.deployVerticle(verticleID, options, result -> {
          if (result.succeeded()) {
            this.logger.info(verticleID + " started as " + result.result());
            this.localVerticles.add(result.result());
          } else {
            this.logger.error(result.cause());
          }
        });
      } else {
        this.logger.error("Class with CastleVerticle Annotation has wrong type:" + candidate.getName());
      }
    }
  }

public interface CastleRouteHandler {
	public String getDescription();
	// Return the actual route
	public String getRoute();
	// The methods that needs to be overwritten in each instance
	// One for each method we do support
	public void handleDelete(final RoutingContext ctx);
	public void handleGet(final RoutingContext ctx);
	public void handleHead(final RoutingContext ctx);
	public void handleOptions(final RoutingContext ctx);
	public void handlePost(final RoutingContext ctx);
	public void handlePut(final RoutingContext ctx);
	public void handlePatch(final RoutingContext ctx);
	// initialize the actual route
	public AbstractCastleRouteHandler init(final Vertx vertx, final Router router, final String method, final String route, String mimetype, String description);
}

public AbstractCastleRouteHandler init(final Vertx vertx, final Router router, final String method, final String route,
      final String mimetype, final String description) {
    this.vertx = vertx;
    this.description = description;
    this.route = route;
    this.logger.info("Loading " + this.getClass().getName() + " for " + this.getRoute() + " .. " + description);
    Route localRoute = null;
    if ("any".equalsIgnoreCase(method) || "GET".equalsIgnoreCase(method)) {
      localRoute = router.get(route).handler(this::handleGet);
    }
    if ("any".equalsIgnoreCase(method) || "POST".equalsIgnoreCase(method)) {
      localRoute = router.post(route).handler(this::handlePost);
    }
    if ("any".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
      localRoute = router.put(route).handler(this::handlePut);
    }
    if ("any".equalsIgnoreCase(method) || "PATCH".equalsIgnoreCase(method)) {
      localRoute = router.put(route).handler(this::handlePatch);
    }
    if ("any".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
      localRoute = router.delete(route).handler(this::handleDelete);
    }

    if (localRoute != null && !"any".equalsIgnoreCase(mimetype)) {
      localRoute.consumes(mimetype);
    }

    // Methods that need to be always available
    router.options(route).handler(this::handleOptions);
    router.options(route).handler(this::handleHead);

    return this;
  }

Left as an exercise to the reader: runs the analysis at compile time instead of runtime.
As usual: YMMV

Disclaimer

This site is in no way affiliated, endorsed, sanctioned, supported, nor enlightened by Lotus Software nor IBM Corporation. I may be an employee, but the opinions, theories, facts, etc. presented here are my own and are in now way given in any official capacity. In short, these are my words and this is my site, not IBM's - and don't even begin to think otherwise. (Disclaimer shamelessly plugged from Rocky Oliver)
© 2003 - 2017 Stephan H. Wissel - some rights reserved as listed here: Creative Commons License
Unless otherwise labeled by its originating author, the content found on this site is made available under the terms of an Attribution/NonCommercial/ShareAlike Creative Commons License, with the exception that no rights are granted -- since they are not mine to grant -- in any logo, graphic design, trademarks or trade names of any type. Code samples and code downloads on this site are, unless otherwise labeled, made available under an Apache 2.0 license. Other license models are available on written request and written confirmation.