All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.kennethjorgensen.vertx.RiakPersistor Maven / Gradle / Ivy

The newest version!
/*
 * 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 indexValues = (Set)indexEntry.getValue(); JsonObject jsonIndex = new JsonObject(); JsonArray jsonIndexValues = new JsonArray(); if (indexObj instanceof BinIndex) { BinIndex index = (BinIndex)indexObj; jsonIndex.putString("type", "bin"); jsonIndexes.putObject(index.getName(), jsonIndex); for (Object indexValue : indexValues) { jsonIndexValues.addString((String)indexValue); } } else if (indexObj instanceof IntIndex) { IntIndex index = (IntIndex)indexObj; jsonIndex.putString("type", "int"); jsonIndexes.putObject(index.getName(), jsonIndex); for (Object indexValue : indexValues) { jsonIndexValues.addNumber((Long)indexValue); } } jsonIndex.putArray("values", jsonIndexValues); } } return json; } }