/*
* The MIT License (MIT)
* Copyright © 2013 Englishtown <opensource@englishtown.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the “Software”), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.englishtown.vertx;
import com.englishtown.vertx.clients.EtCouchbaseClient;
import com.englishtown.vertx.clients.EtCouchbaseClientFactory;
import com.google.common.util.concurrent.FutureCallback;
import net.spy.memcached.PersistTo;
import net.spy.memcached.ReplicateTo;
import org.vertx.java.core.Handler;
import org.vertx.java.core.buffer.Buffer;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;
import javax.inject.Inject;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
/**
*/
public class CouchbasePersistor extends Verticle implements Handler<Message<JsonObject>> {
private static final String DEFAULT_ADDRESS = "et.mod.couchbase";
private final EtCouchbaseClientFactory clientFactory;
private String address;
private EventBus eb;
private Logger logger;
private List<URI> couchHosts;
private final HashMap<String, EtCouchbaseClient> clients = new HashMap<>();
private String password;
private boolean asyncWrites;
@Inject
public CouchbasePersistor(EtCouchbaseClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public void start() {
super.start();
logger = container.logger();
eb = vertx.eventBus();
configure(container.config());
System.out.println("Address is " + address);
eb.registerHandler(address, this);
eb.registerHandler(address + "/saveChunk", new Handler<Message>() {
@Override
public void handle(Message message) {
doSaveChunk(message);
}
});
}
public void configure(JsonObject config) {
address = config.getString("address", DEFAULT_ADDRESS);
address = (address.equals("") ? DEFAULT_ADDRESS : address);
asyncWrites = config.getBoolean("async_writes", false);
password = config.getString("password", "");
try {
couchHosts = getHostsFromConfig(config);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
private List<URI> getHostsFromConfig(JsonObject config) throws URISyntaxException {
List<URI> hosts = new ArrayList<>();
JsonArray hostsAsArray = config.getArray("hosts", new JsonArray().addString("localhost"));
String[] hostsAsString = Arrays.copyOf(hostsAsArray.toArray(), hostsAsArray.toArray().length, String[].class);
for (String hostname : hostsAsString) {
hosts.add(new URI("http://" + hostname + ":8091/pools"));
}
return hosts;
}
@Override
public void handle(Message<JsonObject> message) {
String action = getMandatoryString("action", message);
switch (action) {
case "saveDocument":
doSaveDocument(message);
break;
case "getDocument":
doGetDocument(message);
break;
case "getChunk":
doReadChunk(message);
break;
default:
sendError(message, "Unknown action specified");
}
}
private void doReadChunk(final Message<JsonObject> message) {
try {
EtCouchbaseClient client = clientFactory.getClient(couchHosts, getMandatoryString("bucket", message), password);
String fileId = getMandatoryString("files_id", message);
Number n = getMandatoryNumber("n", message);
String key = fileId + "!" + n;
client.asyncGet(key, new FutureCallback<Object>() {
@Override
public void onSuccess(Object o) {
message.reply((byte[]) o);
}
@Override
public void onFailure(Throwable throwable) {
message.reply(throwable);
}
});
} catch (Exception e) {
message.reply(e);
}
}
private void doSaveChunk(Message<Buffer> message) {
JsonObject jsonObject;
byte[] data;
// Parse the byte[] message body
try {
Buffer body = message.body();
// First four bytes indicate the json string length
int len = body.getInt(0);
// Decode json
int from = 4;
byte[] jsonBytes = body.getBytes(from, from + len);
jsonObject = new JsonObject(decode(jsonBytes));
// Remaining bytes are the chunk to be written
from += len;
data = body.getBytes(from, body.length());
} catch (RuntimeException e) {
sendError(message, "Error while extracting message", e);
return;
}
try {
EtCouchbaseClient client = clientFactory.getClient(couchHosts, jsonObject.getString("bucket"), password);
String key = jsonObject.getString("files_id") + "!" + jsonObject.getNumber("n");
if (asyncWrites) {
client.set(key, data, PersistTo.ZERO, ReplicateTo.ZERO);
} else {
client.set(key, data).get();
}
sendOK(message);
} catch (Exception e) {
sendError(message, "Error while writing to database", e);
}
}
private void doGetDocument(final Message<JsonObject> message) {
try {
EtCouchbaseClient client = clientFactory.getClient(couchHosts, getMandatoryString("bucket", message), password);
String key = getMandatoryString("key", message);
client.asyncGet(key, new FutureCallback<String>() {
@Override
public void onSuccess(String s) {
sendOK(message, (s == null ? new JsonObject() : new JsonObject(s)));
}
@Override
public void onFailure(Throwable throwable) {
sendError(message, "Failed to retrieve record", throwable);
}
});
} catch (Exception e) {
sendError(message, e.getMessage());
}
}
private void doSaveDocument(Message<JsonObject> message) {
try {
EtCouchbaseClient client = clientFactory.getClient(couchHosts, getMandatoryString("bucket", message), password);
String id = getMandatoryString("id", message);
String document = getMandatoryObject("document", message).toString();
if (asyncWrites) {
client.set(id, document, PersistTo.ZERO, ReplicateTo.ZERO);
} else {
client.set(id, document).get();
}
sendOK(message);
} catch (Exception e) {
sendError(message, e.getMessage());
}
}
public <T> void sendError(Message<T> message, String error) {
sendError(message, error, null);
}
public <T> void sendError(Message<T> message, String error, Throwable e) {
logger.error(error, e);
JsonObject result = new JsonObject().putString("status", "error").putString("message", error);
message.reply(result);
}
public <T> void sendOK(Message<T> message) {
sendOK(message, new JsonObject());
}
public <T> void sendOK(Message<T> message, JsonObject response) {
response.putString("status", "ok");
message.reply(response);
}
private String decode(byte[] bytes) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Should never happen
throw new RuntimeException(e);
}
}
private String getMandatoryString(String fieldName, Message<JsonObject> message) {
String fieldValue = message.body().getString(fieldName);
if (fieldValue == null) {
sendError(message, "Could not find mandatory fieldname " + fieldName);
}
return fieldValue;
}
private Number getMandatoryNumber(String fieldName, Message<JsonObject> message) {
Number fieldValue = message.body().getNumber(fieldName);
if (fieldValue == null) {
sendError(message, "Could not find mandatory fieldname " + fieldName);
}
return fieldValue;
}
private JsonObject getMandatoryObject(String fieldName, Message<JsonObject> message) {
JsonObject fieldValue = message.body().getObject(fieldName);
if (fieldValue == null) {
sendError(message, "Could not find mandatory fieldname " + fieldName);
}
return fieldValue;
}
public String getAddress() {
return address;
}
public List<URI> getCouchHosts() {
return couchHosts;
}
public String getPassword() {
return password;
}
public boolean isAsyncWrites() {
return asyncWrites;
}
}