Java tutorial
package com.deblox.releaseboard; /* Copyright 2015 Kegan Holtzhausen 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. */ import com.deblox.Util; import io.vertx.core.AbstractVerticle; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.EventBus; import io.vertx.core.Future; import io.vertx.core.file.AsyncFile; import io.vertx.core.file.OpenOptions; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.logging.Logger; import io.vertx.core.logging.LoggerFactory; import java.io.IOException; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; import java.util.*; /** * ReleaseBoard Verticle * * Maintains a map of "releases" and sends them out to clients at interval. the id is used as the unique identifier, and * is typically made up of the component + version string, can be set explicitly. the code's are used to control the UI elements essentially * in the HTML client. * * * RELEASE EVENT: * * consumes from: "release-event" * publishes to "releases" * * consumed message format: * * { * "component": String, * "version": String, * "status": String, * "environment": String, * "code": Int, * // optional * "id": "some unique identifier" * } * * emitted message format: * * { * "id": "foo-1.2.3.4", * "component": "foo", * "version": "1.2.3.4", * "status": "event in progress", * "environment": "qa1", * "date": "yyyy-MM-dd HH:mm:ss", * "code": 302, * "expired" : false * } * * codes are a loose contract between the client application (HTML) and the publisher, they are * defined in any way you like, * * example: * * 100's -> process stage required ( approve the jira! ) * 200's -> completed stage * 300's -> stage in progress * 500's -> error in stage * * define as you like, and remember to check the release_handler.js and release.py files to sync up YOUR code's * * * EXPIRE RELEASE EVENT * * consumes from: "expire-release-event" * * message: * { * "id": "some id" * } * * * VERTICLE CONFIG * "config": { "expire_timeout": 108000, // seconds "check_expiry": 60000, // millis "state_file": "/tmp/state.json", "save_interval": 60000 // millis } * */ public class ReleaseBoardVerticle extends AbstractVerticle { private static final Logger logger = LoggerFactory.getLogger(ReleaseBoardVerticle.class); EventBus eb; Map<String, JsonObject> releasesData; String stateFile; // date formatter DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); /** * Start the verticle * * @param startFuture * @throws Exception */ public void start(Future<Void> startFuture) throws Exception { logger.info("starup with config: " + config().toString()); // get expire_release in seconds int expire_timeout = config().getInteger("expire_timeout", 86000); // map of releases, should contain date events were fired in / updated also releasesData = new HashMap<>(); stateFile = config().getString("state_file", "/state.json"); // connect to the eventbus eb = vertx.eventBus(); // load the state file if exists vertx.fileSystem().exists(stateFile, h -> { if (h.succeeded()) { try { JsonArray history = Util.loadConfig(stateFile).getJsonArray("releases"); for (Object release : history) { JsonObject releaseJson = new JsonObject(release.toString()); logger.info("loading release: " + releaseJson.getString("id")); releasesData.put(releaseJson.getString("id"), releaseJson.getJsonObject("data")); } } catch (IOException e) { logger.warn("unable to load state file, it will be created / overwritten"); e.printStackTrace(); } } }); /* * listen for release events from other verticles / clients * * example release-event published direct to the eventbus ( see Server.java ) * { "code": 205, "component": "maximus", "environment": "CI1", "status": "Deploy Succeeded", "version": "1.0.0.309" } * * */ eb.consumer("release-event", event -> { logger.info(event.body().toString()); JsonObject body = null; // create a json object from the message try { body = new JsonObject(event.body().toString()); } catch (Exception e) { logger.warn("not a json object"); event.reply(new JsonObject().put("result", "failure").put("reason", "that wasn't json")); } // create check if a id is specified, else combine component and version body.put("id", body.getString("id", body.getValue("component") + "-" + body.getValue("version"))); // used for marking expired messages when time is not enough or too much body.put("expired", false); // add the date now body.put("date", LocalDateTime.now().format(formatter)); // pop the old matching JIRA release releasesData.remove(body.getString("id")); // put the updated one releasesData.put(body.getString("id"), body); event.reply(new JsonObject().put("result", "success")); }); // expire a release event and remove it from the map eb.consumer("expire-release-event", event -> { try { logger.info("delete event: " + event.body().toString()); JsonObject request = new JsonObject(event.body().toString()); releasesData.remove(request.getString("id")); // forulate the expire message JsonObject msg = new JsonObject().put("topic", "releases").put("action", "expire"); JsonArray arr = new JsonArray().add(request.getString("id")); msg.put("data", arr); eb.publish("releases", msg); event.reply(new JsonObject().put("result", "success")); } catch (Exception e) { event.reply(new JsonObject().put("result", "error")); } }); vertx.setPeriodic(10000, tid -> { JsonObject msg = new JsonObject(); msg.put("topic", "releases"); msg.put("action", "default"); JsonArray rel = new JsonArray(); Iterator<Map.Entry<String, JsonObject>> iter = releasesData.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, JsonObject> entry = iter.next(); rel.add(entry.getValue()); } msg.put("data", rel); eb.publish("releases", msg); }); // periodically expire old releases in the map vertx.setPeriodic(config().getInteger("check_expiry", 1000), res -> { // iterate over map, check dates for each, expire as needed Iterator<Map.Entry<String, JsonObject>> iter = releasesData.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, JsonObject> entry = iter.next(); logger.debug("checking expiry on " + entry.getKey() + " v " + entry.getValue()); // now LocalDateTime now = LocalDateTime.now(); // then LocalDateTime then = LocalDateTime.parse(entry.getValue().getString("date"), formatter); // delta Long delta = now.toEpochSecond(ZoneOffset.UTC) - then.toEpochSecond(ZoneOffset.UTC); if (delta >= expire_timeout) { logger.info("expiring stale release: " + entry.getValue() + " delta: " + delta.toString()); iter.remove(); } } }); // save the current pile of releases into a JSON periodically vertx.setPeriodic(config().getInteger("save_interval", 60000), t -> { saveState(); }); startFuture.complete(); } @Override public void stop(Future<Void> stopFuture) { saveState(); vertx.setTimer(1000, tid -> { logger.info("shutdown"); stopFuture.complete(); }); } public void saveState() { logger.info("saving state"); JsonObject db = new JsonObject(); JsonArray releases = new JsonArray(); Iterator<Map.Entry<String, JsonObject>> iter = releasesData.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<String, JsonObject> entry = iter.next(); JsonObject rel = new JsonObject(); rel.put("id", entry.getKey()); rel.put("data", entry.getValue()); releases.add(rel); } db.put("releases", releases); vertx.fileSystem().exists(stateFile, te -> { if (te.succeeded()) { if (te.result().booleanValue()) { vertx.fileSystem().deleteBlocking(stateFile); vertx.fileSystem().createFileBlocking(stateFile); } else { vertx.fileSystem().createFileBlocking(stateFile); } } else { logger.warn("unable to check if file exists: " + stateFile); } vertx.fileSystem().open(stateFile, new OpenOptions().setCreate(true).setWrite(true), r -> { if (r.succeeded()) { AsyncFile file = r.result(); file.write(Buffer.buffer(db.toString())); file.close(); } else { logger.warn(r.cause()); } }); }); } }