//filename: MainVerticle.java

package io.vertx.guides.wiki;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;

/**
* @author <a href="https://julien.ponge.org/">Julien Ponge</a>
*/
// tag::main[]
public class MainVerticle extends AbstractVerticle {

@Override
public void start(Future<Void> startFuture) throws Exception {

Future<String> dbVerticleDeployment = Future.future(); // <1>
vertx.deployVerticle(new WikiDatabaseVerticle(), dbVerticleDeployment.completer()); // <2>

dbVerticleDeployment.compose(id -> { // <3>

Future<String> httpVerticleDeployment = Future.future();
vertx.deployVerticle(
"io.vertx.guides.wiki.HttpServerVerticle", // <4>
new DeploymentOptions().setInstances(3), // <5>
httpVerticleDeployment.completer());

return httpVerticleDeployment; // <6>

}).setHandler(ar -> { // <7>
if (ar.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(ar.cause());
}
});
}
}



//filename: HttpServerVerticle.java

package io.vertx.guides.wiki;

import com.github.rjeschke.txtmark.Processor;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.DeliveryOptions;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.templ.FreeMarkerTemplateEngine;

import java.util.Date;

/**
* @author <a href="https://julien.ponge.org/">Julien Ponge</a>
*/
// tag::start[]
public class HttpServerVerticle extends AbstractVerticle {

private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerVerticle.class);

public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; // <1>
public static final String CONFIG_WIKIDB_QUEUE = "wikidb.queue";

private String wikiDbQueue = "wikidb.queue";

@Override
public void start(Future<Void> startFuture) throws Exception {

wikiDbQueue = config().getString(CONFIG_WIKIDB_QUEUE, "wikidb.queue"); // <2>

HttpServer server = vertx.createHttpServer();

Router router = Router.router(vertx);
router.get("/").handler(this::indexHandler);
router.get("/wiki/:page").handler(this::pageRenderingHandler);
router.get("/test/:msg").handler(this::pageTestHandler);
router.post().handler(BodyHandler.create());
router.post("/save").handler(this::pageUpdateHandler);
router.post("/create").handler(this::pageCreateHandler);
router.post("/delete").handler(this::pageDeletionHandler);

int portNumber = config().getInteger(CONFIG_HTTP_SERVER_PORT, 8080); // <3>
server
.requestHandler(router::accept)
.listen(portNumber, ar -> {
if (ar.succeeded()) {
LOGGER.info("HTTP server running on port " + portNumber);
startFuture.complete();
} else {
LOGGER.error("Could not start a HTTP server", ar.cause());
startFuture.fail(ar.cause());
}
});
}

// (...)
// end::start[]

// tag::indexHandler[]
private final FreeMarkerTemplateEngine templateEngine = FreeMarkerTemplateEngine.create();

private void indexHandler(RoutingContext context) {
LOGGER.info("首页");

DeliveryOptions options = new DeliveryOptions().addHeader("action", "all-pages"); // <2>

vertx.eventBus().send(wikiDbQueue, new JsonObject(), options, reply -> { // <1>
if (reply.succeeded()) {
JsonObject body = (JsonObject) reply.result().body(); // <3>
context.put("title", "Wiki home");
context.put("pages", body.getJsonArray("pages").getList());
templateEngine.render(context, "templates", "/index.ftl", ar -> {
if (ar.succeeded()) {
context.response().putHeader("Content-Type", "text/html");
context.response().end(ar.result());
} else {
context.fail(ar.cause());
}
});
} else {
context.fail(reply.cause());
}
});
}
// end::indexHandler[]

// tag::rest[]
private static final String EMPTY_PAGE_MARKDOWN =
"# A new page\n" +
"\n" +
"Feel-free to write in Markdown!\n";



private void pageTestHandler(RoutingContext context) {
String msg = context.request().getParam("msg");
LOGGER.info(msg);
JsonObject message = new JsonObject().put("msg",msg); // message body
DeliveryOptions options = new DeliveryOptions().addHeader("action","test-msg");
vertx.eventBus().send("wikidb.queue",message,options, reply ->{
if(reply.succeeded()){
JsonObject body = (JsonObject) reply.result().body();
context.response().putHeader("Content-Type", "text/html;charset=utf-8");
context.response().end(body.getString("msg"));
}
else{
context.fail(reply.cause());
}
});
}

private void pageRenderingHandler(RoutingContext context) {

String requestedPage = context.request().getParam("page");
JsonObject request = new JsonObject().put("page", requestedPage);

DeliveryOptions options = new DeliveryOptions().addHeader("action", "get-page");
vertx.eventBus().send(wikiDbQueue, request, options, reply -> {

if (reply.succeeded()) {
JsonObject body = (JsonObject) reply.result().body();

boolean found = body.getBoolean("found");
String rawContent = body.getString("rawContent", EMPTY_PAGE_MARKDOWN);
context.put("title", requestedPage);
context.put("id", body.getInteger("id", -1));
context.put("newPage", found ? "no" : "yes");
context.put("rawContent", rawContent);
context.put("content", Processor.process(rawContent));
context.put("timestamp", new Date().toString());

templateEngine.render(context, "templates","/page.ftl", ar -> {
if (ar.succeeded()) {
context.response().putHeader("Content-Type", "text/html");
context.response().end(ar.result());
} else {
context.fail(ar.cause());
}
});

} else {
context.fail(reply.cause());
}
});
}

private void pageUpdateHandler(RoutingContext context) {

String title = context.request().getParam("title");
JsonObject request = new JsonObject()
.put("id", context.request().getParam("id"))
.put("title", title)
.put("markdown", context.request().getParam("markdown"));

DeliveryOptions options = new DeliveryOptions();
if ("yes".equals(context.request().getParam("newPage"))) {
options.addHeader("action", "create-page");
} else {
options.addHeader("action", "save-page");
}

vertx.eventBus().send(wikiDbQueue, request, options, reply -> {
if (reply.succeeded()) {
context.response().setStatusCode(303);
context.response().putHeader("Location", "/wiki/" + title);
context.response().end();
} else {
context.fail(reply.cause());
}
});
}

private void pageCreateHandler(RoutingContext context) {
String pageName = context.request().getParam("name");
String location = "/wiki/" + pageName;
if (pageName == null || pageName.isEmpty()) {
location = "/";
}
context.response().setStatusCode(303);
context.response().putHeader("Location", location);
context.response().end();
}

private void pageDeletionHandler(RoutingContext context) {
String id = context.request().getParam("id");
JsonObject request = new JsonObject().put("id", id);
DeliveryOptions options = new DeliveryOptions().addHeader("action", "delete-page");
vertx.eventBus().send(wikiDbQueue, request, options, reply -> {
if (reply.succeeded()) {
context.response().setStatusCode(303);
context.response().putHeader("Location", "/");
context.response().end();
} else {
context.fail(reply.cause());
}
});
}
// end::rest[]
}


  



//filename: WikiDatabaseVerticle.java

package io.vertx.guides.wiki;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.jdbc.JDBCClient;
import io.vertx.ext.sql.ResultSet;
import io.vertx.ext.sql.SQLConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;

/**
* @author <a href="https://julien.ponge.org/">Julien Ponge</a>
*/
// tag::preamble[]
public class WikiDatabaseVerticle extends AbstractVerticle {

public static final String CONFIG_WIKIDB_JDBC_URL = "wikidb.jdbc.url";
public static final String CONFIG_WIKIDB_JDBC_DRIVER_CLASS = "wikidb.jdbc.driver_class";
public static final String CONFIG_WIKIDB_JDBC_MAX_POOL_SIZE = "wikidb.jdbc.max_pool_size";
public static final String CONFIG_WIKIDB_SQL_QUERIES_RESOURCE_FILE = "wikidb.sqlqueries.resource.file";

public static final String CONFIG_WIKIDB_QUEUE = "wikidb.queue";

private static final Logger LOGGER = LoggerFactory.getLogger(WikiDatabaseVerticle.class);

// (...)
// end::preamble[]

// tag::loadSqlQueries[]
private enum SqlQuery {
CREATE_PAGES_TABLE,
ALL_PAGES,
GET_PAGE,
CREATE_PAGE,
SAVE_PAGE,
DELETE_PAGE
}

private final HashMap<SqlQuery, String> sqlQueries = new HashMap<>();

private void loadSqlQueries() throws IOException {

String queriesFile = config().getString(CONFIG_WIKIDB_SQL_QUERIES_RESOURCE_FILE);
InputStream queriesInputStream;
if (queriesFile != null) {
queriesInputStream = new FileInputStream(queriesFile);
} else {
queriesInputStream = getClass().getResourceAsStream("/db-queries.properties");
}

Properties queriesProps = new Properties();
queriesProps.load(queriesInputStream);
queriesInputStream.close();

sqlQueries.put(SqlQuery.CREATE_PAGES_TABLE, queriesProps.getProperty("create-pages-table"));
sqlQueries.put(SqlQuery.ALL_PAGES, queriesProps.getProperty("all-pages"));
sqlQueries.put(SqlQuery.GET_PAGE, queriesProps.getProperty("get-page"));
sqlQueries.put(SqlQuery.CREATE_PAGE, queriesProps.getProperty("create-page"));
sqlQueries.put(SqlQuery.SAVE_PAGE, queriesProps.getProperty("save-page"));
sqlQueries.put(SqlQuery.DELETE_PAGE, queriesProps.getProperty("delete-page"));
}
// end::loadSqlQueries[]

// tag::start[]
private JDBCClient dbClient;

@Override
public void start(Future<Void> startFuture) throws Exception {

/*
* Note: this uses blocking APIs, but data is small...
*/
loadSqlQueries(); // <1>

dbClient = JDBCClient.createShared(vertx, new JsonObject()
.put("url", config().getString(CONFIG_WIKIDB_JDBC_URL, "jdbc:hsqldb:file:db/wiki"))
.put("driver_class", config().getString(CONFIG_WIKIDB_JDBC_DRIVER_CLASS, "org.hsqldb.jdbcDriver"))
.put("max_pool_size", config().getInteger(CONFIG_WIKIDB_JDBC_MAX_POOL_SIZE, 30)));

dbClient.getConnection(ar -> {
if (ar.failed()) {
LOGGER.error("Could not open a database connection", ar.cause());
startFuture.fail(ar.cause());
} else {
SQLConnection connection = ar.result();
connection.execute(sqlQueries.get(SqlQuery.CREATE_PAGES_TABLE), create -> { // <2>
connection.close();
if (create.failed()) {
LOGGER.error("Database preparation error", create.cause());
startFuture.fail(create.cause());
} else {
vertx.eventBus().consumer(config().getString(CONFIG_WIKIDB_QUEUE, "wikidb.queue"), this::onMessage); // <3>
startFuture.complete();
}
});
}
});
}
// end::start[]

// tag::onMessage[]
public enum ErrorCodes {
NO_ACTION_SPECIFIED,
BAD_ACTION,
DB_ERROR
}

public void onMessage(Message<JsonObject> message) {

if (!message.headers().contains("action")) {
LOGGER.error("No action header specified for message with headers {} and body {}",
message.headers(), message.body().encodePrettily());
message.fail(ErrorCodes.NO_ACTION_SPECIFIED.ordinal(), "No action header specified");
return;
}
String action = message.headers().get("action");

switch (action) {
case "all-pages":
fetchAllPages(message);
break;
case "get-page":
fetchPage(message);
break;
case "test-msg":
testMsg(message);
break;
case "create-page":
createPage(message);
break;
case "save-page":
savePage(message);
break;
case "delete-page":
deletePage(message);
break;
default:
message.fail(ErrorCodes.BAD_ACTION.ordinal(), "Bad action: " + action);
}
}
// end::onMessage[]

// tag::rest[]
private void fetchAllPages(Message<JsonObject> message) {

dbClient.getConnection(car -> {
if (car.succeeded()) {
SQLConnection connection = car.result();
connection.query(sqlQueries.get(SqlQuery.ALL_PAGES), res -> {
connection.close();
if (res.succeeded()) {
List<String> pages = res.result()
.getResults()
.stream()
.map(json -> json.getString(0))
.sorted()
.collect(Collectors.toList());
message.reply(new JsonObject().put("pages", new JsonArray(pages)));
} else {
reportQueryError(message, res.cause());
}
});
} else {
reportQueryError(message, car.cause());
}
});
}

private void testMsg(Message<JsonObject> message) {
String msg = message.body().getString("msg");
LOGGER.info(msg);
if (!"error".equals(msg)) {
JsonObject response = new JsonObject();
response.put("msg", "您好,我是WikiDatabaseVerticle: " + msg);
message.reply(response);
} else {
message.fail(-1, "你的输入有误!");
}
}

private void fetchPage(Message<JsonObject> message) {

String requestedPage = message.body().getString("page");

dbClient.getConnection(car -> {
if (car.succeeded()) {
SQLConnection connection = car.result();
connection.queryWithParams(sqlQueries.get(SqlQuery.GET_PAGE), new JsonArray().add(requestedPage), fetch -> {
connection.close();
if (fetch.succeeded()) {
JsonObject response = new JsonObject();
ResultSet resultSet = fetch.result();
if (resultSet.getNumRows() == 0) {
response.put("found", false);
} else {
response.put("found", true);
JsonArray row = resultSet.getResults().get(0);
response.put("id", row.getInteger(0));
response.put("rawContent", row.getString(1));
}
message.reply(response);
} else {
reportQueryError(message, fetch.cause());
}
});
} else {
reportQueryError(message, car.cause());
}
});

}

private void createPage(Message<JsonObject> message) {
JsonObject request = message.body();

dbClient.getConnection(car -> {

if (car.succeeded()) {
SQLConnection connection = car.result();
JsonArray data = new JsonArray()
.add(request.getString("title"))
.add(request.getString("markdown"));

connection.updateWithParams(sqlQueries.get(SqlQuery.CREATE_PAGE), data, res -> {
connection.close();
if (res.succeeded()) {
message.reply("ok");
} else {
reportQueryError(message, res.cause());
}
});
} else {
reportQueryError(message, car.cause());
}
});
}

private void savePage(Message<JsonObject> message) {
JsonObject request = message.body();

dbClient.getConnection(car -> {

if (car.succeeded()) {
SQLConnection connection = car.result();
JsonArray data = new JsonArray()
.add(request.getString("markdown"))
.add(request.getString("id"));

connection.updateWithParams(sqlQueries.get(SqlQuery.SAVE_PAGE), data, res -> {
connection.close();
if (res.succeeded()) {
message.reply("ok");
} else {
reportQueryError(message, res.cause());
}
});
} else {
reportQueryError(message, car.cause());
}
});
}

private void deletePage(Message<JsonObject> message) {
dbClient.getConnection(car -> {
if (car.succeeded()) {
SQLConnection connection = car.result();
JsonArray data = new JsonArray().add(message.body().getString("id"));
connection.updateWithParams(sqlQueries.get(SqlQuery.DELETE_PAGE), data, res -> {
connection.close();
if (res.succeeded()) {
message.reply("ok");
} else {
reportQueryError(message, res.cause());
}
});
} else {
reportQueryError(message, car.cause());
}
});
}

private void reportQueryError(Message<JsonObject> message, Throwable cause) {
LOGGER.error("Database query error", cause);
message.fail(ErrorCodes.DB_ERROR.ordinal(), cause.getMessage());
}
// end::rest[]
}