com.kennethjorgensen.vertx.RiakPersistor Maven / Gradle / Ivy
Show all versions of mod-riak-persistor Show documentation
/*
* Copyright 2011-2013 the original author or authors.
*
* 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.
*/
package com.kennethjorgensen.vertx;
import com.basho.riak.client.*;
import com.basho.riak.client.bucket.Bucket;
import com.basho.riak.client.builders.RiakObjectBuilder;
import com.basho.riak.client.query.MultiFetchFuture;
import com.basho.riak.client.query.indexes.BinIndex;
import com.basho.riak.client.query.indexes.FetchIndex;
import com.basho.riak.client.query.indexes.IntIndex;
import com.basho.riak.client.query.indexes.RiakIndex;
import org.vertx.java.busmods.BusModBase;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ExecutionException;
/**
* Riak Persistor Bus Module
* Please see the README.md for a full description
*
* @author Tim Fox
* @author Thomas Risberg
* @author Kenneth Jorgensen (Riak port)
*/
public class RiakPersistor extends BusModBase implements Handler> {
// http://basho.github.io/riak-java-client/1.4.0/index.html
// https://github.com/basho/riak-java-client/wiki
private static SimpleDateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
protected String address;
protected String host;
protected int pb_port;
protected IRiakClient riak;
@Override
public void start() {
super.start();
// Config.
address = getOptionalStringConfig("address", "vertx.riakpersistor");
host = getOptionalStringConfig("host", "localhost");
pb_port = getOptionalIntConfig("pb_port", 8087);
// Create Riak client.
try {
riak = RiakFactory.pbcClient(host, pb_port);
} catch (RiakException e) {
logger.error("Failed to connect to Riak server", e);
}
// Subscribe to the various addresses.
eb.registerHandler(address, this);
}
@Override
public void stop() {
super.stop();
if (riak != null) {
riak.shutdown();
}
}
@Override
public void handle(Message message) {
// Fetch and check action.
String action = message.body().getString("action");
if (action == null) {
sendError(message, "action must be specified");
return;
}
// Pass it to the relevant subhandler.
try {
switch (action) {
case "ping":
doPing(message);
break;
case "store":
doStore(message);
break;
case "fetch":
doFetch(message);
break;
case "indexQuery":
doIndexQuery(message);
break;
default:
sendError(message, "Invalid action: " + action);
}
}
catch (Exception e) {
sendError(message, e.getMessage(), e);
}
}
private void doPing(Message message) throws RiakException {
riak.ping();
sendOK(message);
}
private void doStore(Message message) throws RiakRetryFailedException {
JsonObject body = message.body();
// Bucket.
String bucketName = body.getString("bucket");
if (bucketName == null || bucketName.length() == 0) {
sendError(message, "Bucket name not supplied for store");
return;
}
Bucket bucket = riak.fetchBucket(bucketName).execute();
// Key.
String key = body.getString("key");
if (key == null) {
key = UUID.randomUUID().toString();
body.putString("key", key);
}
// Create builder.
RiakObjectBuilder builder = RiakObjectBuilder.newBuilder(bucketName, key);
// Value.
try {
builder.withValue(body.getString("value"));
}
catch (ClassCastException e) {
builder.withValue(body.getObject("value").toString());
}
// Indexes.
// @todo can perhaps simplify using com.basho.riak.client.query.indexes.RiakIndexes
if (body.getFieldNames().contains("indexes")) {
JsonObject indexes = body.getObject("indexes");
for (String indexName : indexes.getFieldNames()) {
JsonObject index = indexes.getObject(indexName);
String indexType = index.getString("type");
if (indexType.equals("int")) {
builder.addIndex(indexName, index.getNumber("value").longValue());
}
else if (indexType.equals("bin")) {
builder.addIndex(indexName, index.getString("value"));
}
else {
sendError(message, "Unknown index type '"+indexType+"' for index '"+indexName+"' for store");
return;
}
}
}
// Store.
IRiakObject fetched = bucket.store(builder.build())
.returnBody(true)
.execute();
sendOK(message, riakToJson(fetched));
}
private void doFetch(Message message) throws RiakRetryFailedException {
JsonObject body = message.body();
// Bucket.
String bucketName = body.getString("bucket");
if (bucketName == null || bucketName.length() == 0) {
sendError(message, "Bucket name not supplied for fetch");
return;
}
Bucket bucket = riak.fetchBucket(bucketName).execute();
// Key.
String key = body.getString("key");
if (bucketName == null || bucketName.length() == 0) {
sendError(message, "Key not supplied for fetch");
return;
}
// Fetch.
IRiakObject fetched = bucket.fetch(key).execute();
// Reply.
sendOK(message, riakToJson(fetched));
}
private void doIndexQuery(Message message) throws RiakRetryFailedException, RiakException, InterruptedException, ExecutionException {
JsonObject body = message.body();
// Bucket.
String bucketName = body.getString("bucket");
if (bucketName == null || bucketName.length() == 0) {
sendError(message, "Bucket name not supplied for indexQuery");
return;
}
Bucket bucket = riak.fetchBucket(bucketName).execute();
// Index type.
String indexType = body.getString("type");
if (indexType == null || indexType.length() == 0) {
sendError(message, "Index type not supplied for indexQuery");
return;
}
// Index name.
String indexName = body.getString("name");
if (indexName == null || indexName.length() == 0) {
sendError(message, "Index name not supplied for indexQuery");
return;
}
// Query details and index construction.
RiakIndex index = null;
Object value = null;
Object from = null;
Object to = null;
try {
if (indexType.equals("bin")) {
index = BinIndex.named(indexName);
value = body.getString("value");
from = body.getString("from");
to = body.getString("to");
}
else if (indexType.equals("int")) {
index = IntIndex.named(indexName);
value = body.getLong("value");
from = body.getLong("from");
to = body.getLong("to");
}
else {
sendError(message, "Unknown index type '"+indexType+"' for index '"+indexName+"' for indexQuery");
return;
}
}
catch (ClassCastException e) {
sendError(message, "Incorrect types supplied for value, to, and/or from for indexQuery");
return;
}
// Create index query.
FetchIndex query = bucket.fetchIndex(index);
//IndexQuery query = null;
if (value == null) {
if (from == null && to == null) {
sendError(message, "value or to and from must be supplied for indexQuery");
return;
}
else if (from == null ^ to == null) {
sendError(message, "both to and from must be supplied for indexQuery");
return;
}
else {
// Create range index query.
query.from(from).to(to);
// if (indexType.equals("bin")) {
// query = new BinRangeQuery((BinIndex)index, bucketName, (String)from, (String)to);
// }
// else if (indexType.equals("int")) {
// query = new IntRangeQuery((IntIndex)index, bucketName, (Long)from, (Long)to);
// }
}
}
else {
if (from == null ^ to == null) {
sendError(message, "when using value neither to nor from may be supplied for indexQuery");
return;
}
else {
// Create value index query.
query.withValue(value);
// if (indexType.equals("bin")) {
// query = new BinValueQuery((BinIndex)index, bucketName, (String)value);
// }
// else if (indexType.equals("int")) {
// query = new IntValueQuery((IntIndex)index, bucketName, (Long)value);
// }
}
}
// Now query Riak.
List keys = query.execute();
String[] keysArray = keys.toArray(new String[]{});
// Fetch all the keys.
List> fetchFutures = bucket.multiFetch(keysArray).execute();
// Prepare reply.
JsonObject reply = new JsonObject();
JsonArray jsonResults = new JsonArray();
reply.putArray("results", jsonResults);
// Wait for all the fetches to finish and add them to the array.
for(MultiFetchFuture fetchFuture : fetchFutures) {
// This is a blocking call which will wait for the object to be fetched.
// Ideally this whole persistor should be properly threaded, but I guess that's the point of vertx ... ?
IRiakObject obj = fetchFuture.get();
// Convert and add.
jsonResults.addObject(riakToJson(obj));
}
// Ok, done, finally!
sendOK(message, reply);
}
private JsonObject riakToJson(IRiakObject obj) {
JsonObject json = new JsonObject()
.putString("bucket", obj.getBucket())
.putString("key", obj.getKey())
.putString("value", obj.getValueAsString())
.putString("contentType", obj.getContentType())
.putString("lastModified", ISO_8601.format(obj.getLastModified()));
//.putString("vclock", obj.getVClockAsString());
// Indexes.
JsonObject jsonIndexes = new JsonObject();
json.putObject("indexes", jsonIndexes);
// Combine all the indexes into one big entry set.
// Set>>
Set indexEntries = new HashSet();
indexEntries.addAll(obj.allBinIndexes().entrySet());
indexEntries.addAll(obj.allIntIndexesV2().entrySet());
if (indexEntries.size() > 0) {
for (Map.Entry indexEntry : indexEntries) {
Object indexObj = indexEntry.getKey();
Set