Hai Brenner 2016-07-06 21:45:35 +03:00
commit 88c4e0e644
153 changed files with 10132 additions and 7679 deletions

34
.gitignore vendored
View File

@ -1,16 +1,18 @@
.gradle .gradle
.idea .idea
build build
bin bin
.settings .settings
.classpath .classpath
.project .project
out out
*.iml *.iml
*.ipr *.ipr
*.iws *.iws
**/*.swp **/*.swp
*.prefs *.prefs
*.project *.project
*.classpath *.classpath
bulletin-board-server/local-instances/meerkat.db *.db
*.sql
.arcconfig

View File

@ -1,10 +1,10 @@
subprojects { proj -> subprojects { proj ->
proj.afterEvaluate { proj.afterEvaluate {
// Used to generate initial maven-dir layout // Used to generate initial maven-dir layout
task "create-dirs" { description = "Create default maven directory structure" } << { task "create-dirs" { description = "Create default maven directory structure" } << {
sourceSets*.java.srcDirs*.each { it.mkdirs() } sourceSets*.java.srcDirs*.each { it.mkdirs() }
sourceSets*.resources.srcDirs*.each { it.mkdirs() } sourceSets*.resources.srcDirs*.each { it.mkdirs() }
} }
} }
} }

View File

@ -1,220 +1,220 @@
plugins { plugins {
id "us.kirchmeier.capsule" version "1.0.1" id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.7.0' id 'com.google.protobuf' version '0.7.0'
} }
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
// Uncomment the lines below to define an application // Uncomment the lines below to define an application
// (this will also allow you to build a "fatCapsule" which includes // (this will also allow you to build a "fatCapsule" which includes
// the entire application, including all dependencies in a single jar) // the entire application, including all dependencies in a single jar)
//apply plugin: 'application' //apply plugin: 'application'
//mainClassName='your.main.ApplicationClass' //mainClassName='your.main.ApplicationClass'
// Is this a snapshot version? // Is this a snapshot version?
ext { isSnapshot = false } ext { isSnapshot = false }
ext { ext {
groupId = 'org.factcenter.meerkat' groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties // Should be set in ${HOME}/.gradle/gradle.properties
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
} }
description = "TODO: Add a description" description = "TODO: Add a description"
// Your project version // Your project version
version = "0.0" version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}" version += "${isSnapshot ? '-SNAPSHOT' : ''}"
dependencies { dependencies {
// Meerkat common // Meerkat common
compile project(':meerkat-common') compile project(':meerkat-common')
// Logging // Logging
compile 'org.slf4j:slf4j-api:1.7.7' compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2' runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2' runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs // Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+' compile 'com.google.protobuf:protobuf-java:3.+'
testCompile 'junit:junit:4.+' testCompile 'junit:junit:4.+'
runtime 'org.codehaus.groovy:groovy:2.4.+' runtime 'org.codehaus.groovy:groovy:2.4.+'
} }
/*==== You probably don't have to edit below this line =======*/ /*==== You probably don't have to edit below this line =======*/
// Setup test configuration that can appear as a dependency in // Setup test configuration that can appear as a dependency in
// other subprojects // other subprojects
configurations { configurations {
testOutput.extendsFrom (testCompile) testOutput.extendsFrom (testCompile)
} }
task testJar(type: Jar, dependsOn: testClasses) { task testJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests' classifier = 'tests'
from sourceSets.test.output from sourceSets.test.output
} }
artifacts { artifacts {
testOutput testJar testOutput testJar
} }
// The run task added by the application plugin // The run task added by the application plugin
// is also of type JavaExec. // is also of type JavaExec.
tasks.withType(JavaExec) { tasks.withType(JavaExec) {
// Assign all Java system properties from // Assign all Java system properties from
// the command line to the JavaExec task. // the command line to the JavaExec task.
systemProperties System.properties systemProperties System.properties
} }
protobuf { protobuf {
// Configure the protoc executable // Configure the protoc executable
protoc { protoc {
// Download from repositories // Download from repositories
artifact = 'com.google.protobuf:protoc:3.+' artifact = 'com.google.protobuf:protoc:3.+'
} }
} }
idea { idea {
module { module {
project.sourceSets.each { sourceSet -> project.sourceSets.each { sourceSet ->
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir" println "Adding $srcDir"
// add protobuf generated sources to generated source dir. // add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) { if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir) testSourceDirs += file(srcDir)
} else { } else {
sourceDirs += file(srcDir) sourceDirs += file(srcDir)
} }
generatedSourceDirs += file(srcDir) generatedSourceDirs += file(srcDir)
} }
// Don't exclude build directory // Don't exclude build directory
excludeDirs -= file(buildDir) excludeDirs -= file(buildDir)
} }
} }
/*=================================== /*===================================
* "Fat" Build targets * "Fat" Build targets
*===================================*/ *===================================*/
if (project.hasProperty('mainClassName') && (mainClassName != null)) { if (project.hasProperty('mainClassName') && (mainClassName != null)) {
task mavenCapsule(type: MavenCapsule) { task mavenCapsule(type: MavenCapsule) {
description = "Generate a capsule jar that automatically downloads and caches dependencies when run." description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
applicationClass mainClassName applicationClass mainClassName
destinationDir = buildDir destinationDir = buildDir
} }
task fatCapsule(type: FatCapsule) { task fatCapsule(type: FatCapsule) {
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
destinationDir = buildDir destinationDir = buildDir
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
applicationClass fatMain applicationClass fatMain
def testJar = hasProperty('test') def testJar = hasProperty('test')
if (hasProperty('fatmain')) { if (hasProperty('fatmain')) {
appendix = "fat-${fatMain}" appendix = "fat-${fatMain}"
} else { } else {
appendix = "fat" appendix = "fat"
} }
if (testJar) { if (testJar) {
from sourceSets.test.output from sourceSets.test.output
} }
} }
} }
/*=================================== /*===================================
* Repositories * Repositories
*===================================*/ *===================================*/
repositories { repositories {
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven { maven {
url nexusRepository url nexusRepository
if (isSnapshot) { if (isSnapshot) {
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
// Use local maven repository // Use local maven repository
mavenLocal() mavenLocal()
// Use 'maven central' for other dependencies. // Use 'maven central' for other dependencies.
mavenCentral() mavenCentral()
} }
task "info" << { task "info" << {
println "Project: ${project.name}" println "Project: ${project.name}"
println "Description: ${project.description}" println "Description: ${project.description}"
println "--------------------------" println "--------------------------"
println "GroupId: $groupId" println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println "" println ""
} }
info.description 'Print some information about project parameters' info.description 'Print some information about project parameters'
/*=================================== /*===================================
* Publishing * Publishing
*===================================*/ *===================================*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
groupId project.groupId groupId project.groupId
pom.withXml { pom.withXml {
asNode().appendNode('description', project.description) asNode().appendNode('description', project.description)
} }
from project.components.java from project.components.java
} }
} }
repositories { repositories {
maven { maven {
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
} }

View File

@ -69,7 +69,7 @@ dependencies {
test { test {
exclude '**/*IntegrationTest*' exclude '**/*IntegrationTest*'
outputs.upToDateWhen { false } // outputs.upToDateWhen { false }
} }
task integrationTest(type: Test) { task integrationTest(type: Test) {

View File

@ -1,7 +1,7 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.protobuf.ByteString; import meerkat.protobuf.BulletinBoardAPI.BatchChunk;
import meerkat.protobuf.BulletinBoardAPI.BatchData; import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.List; import java.util.List;
@ -11,15 +11,13 @@ import java.util.List;
*/ */
public class BatchDataContainer { public class BatchDataContainer {
public final byte[] signerId; public final MultiServerBatchIdentifier batchId;
public final int batchId; public final List<BatchChunk> batchChunkList;
public final List<BatchData> batchDataList;
public final int startPosition; public final int startPosition;
public BatchDataContainer(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition) { public BatchDataContainer(MultiServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
this.signerId = signerId;
this.batchId = batchId; this.batchId = batchId;
this.batchDataList = batchDataList; this.batchChunkList = batchChunkList;
this.startPosition = startPosition; this.startPosition = startPosition;
} }
} }

View File

@ -1,38 +1,38 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
/** /**
* Created by Arbel Deutsch Peled on 09-Dec-15. * Created by Arbel Deutsch Peled on 09-Dec-15.
* *
* This class handles bulletin client work. * This class handles bulletin client work.
* It is meant to be used in a multi-threaded environment. * It is meant to be used in a multi-threaded environment.
*/ */
public abstract class BulletinClientWorker<IN> { public abstract class BulletinClientWorker<IN> {
protected final IN payload; // Payload of the job protected final IN payload; // Payload of the job
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
public BulletinClientWorker(IN payload, int maxRetry) { public BulletinClientWorker(IN payload, int maxRetry) {
this.payload = payload; this.payload = payload;
this.maxRetry = maxRetry; this.maxRetry = maxRetry;
} }
public IN getPayload() { public IN getPayload() {
return payload; return payload;
} }
public int getMaxRetry() { public int getMaxRetry() {
return maxRetry; return maxRetry;
} }
public void decMaxRetry(){ public void decMaxRetry(){
if (maxRetry > 0) { if (maxRetry > 0) {
maxRetry--; maxRetry--;
} }
} }
public boolean isRetry(){ public boolean isRetry(){
return (maxRetry != 0); return (maxRetry != 0);
} }
} }

View File

@ -1,168 +1,490 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.protobuf.Timestamp;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.Voting.*; import meerkat.protobuf.Voting.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors;
/** /**
* Created by Arbel Deutsch Peled on 03-Mar-16. * Created by Arbel Deutsch Peled on 03-Mar-16.
* This is a full-fledged implementation of a Bulletin Board Client * This is a full-fledged implementation of a Bulletin Board Client
* It provides asynchronous access to several remote servers, as well as a local cache * It provides asynchronous access to several remote servers, as well as a local cache
* Read/write operations are performed on the local server * Read operations are performed on the local server
* Batch reads are performed on the local server and, if they fail, also on the remote servers
* Write operations are performed on the local server
* A Synchronizer is employed in order to keep the remote server up to date
* After any read is carried out, a subscription is made for the specific query to make sure the local DB will be updated * After any read is carried out, a subscription is made for the specific query to make sure the local DB will be updated
* The database also employs a synchronizer which makes sure local data is sent to the remote servers * The database also employs a synchronizer which makes sure local data is sent to the remote servers
*/ */
public class CachedBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient { public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClient {
private final BulletinBoardClient localClient; private final AsyncBulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient; private final AsyncBulletinBoardClient remoteClient;
private BulletinBoardSubscriber subscriber; private final AsyncBulletinBoardClient queueClient;
private final BulletinBoardSubscriber subscriber;
private final BulletinBoardSynchronizer synchronizer;
private final int threadPoolSize; private Thread syncThread;
private final long failDelayInMilliseconds;
private final long subscriptionIntervalInMilliseconds;
public CachedBulletinBoardClient(BulletinBoardClient localClient, private final static int DEFAULT_WAIT_CAP = 3000;
int threadPoolSize, private final static int DEFAULT_SLEEP_INTERVAL = 3000;
long failDelayInMilliseconds,
long subscriptionIntervalInMilliseconds) private class SubscriptionStoreCallback implements FutureCallback<List<BulletinBoardMessage>> {
throws IllegalAccessException, InstantiationException {
private final FutureCallback<List<BulletinBoardMessage>> callback;
public SubscriptionStoreCallback(){
callback = null;
}
public SubscriptionStoreCallback(FutureCallback<List<BulletinBoardMessage>> callback){
this.callback = callback;
}
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
for (BulletinBoardMessage msg : result) {
try {
if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// This is a batch message: need to upload batch data as well as the message itself
BulletinBoardMessage completeMessage = localClient.readBatchData(msg);
localClient.postMessage(completeMessage);
} else {
// This is a regular message: post it
localClient.postMessage(msg);
}
} catch (CommunicationException ignored) {
// TODO: log
}
}
}
@Override
public void onFailure(Throwable t) {
if (callback != null) {
callback.onFailure(t); // This is some hard error that cannot be dealt with
}
}
}
/**
* Creates a Cached Client
* Assumes all parameters are initialized
* @param localClient is a Client for the local instance
* @param remoteClient is a Client for the remote instance(s); Should have endless retries for post operations
* @param subscriber is a subscription service to the remote instance(s)
* @param queueClient is a client for a local deletable server to be used as a queue for not-yet-uploaded messages
*/
public CachedBulletinBoardClient(AsyncBulletinBoardClient localClient,
AsyncBulletinBoardClient remoteClient,
BulletinBoardSubscriber subscriber,
DeletableSubscriptionBulletinBoardClient queueClient,
int sleepInterval,
int waitCap) {
this.localClient = localClient; this.localClient = localClient;
this.threadPoolSize = threadPoolSize; this.remoteClient = remoteClient;
this.failDelayInMilliseconds = failDelayInMilliseconds; this.subscriber = subscriber;
this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds; this.queueClient = queueClient;
remoteClient = new ThreadedBulletinBoardClient(); this.synchronizer = new SimpleBulletinBoardSynchronizer(sleepInterval,waitCap);
synchronizer.init(queueClient, remoteClient);
syncThread = new Thread(synchronizer);
syncThread.start();
}
/**
* Creates a Cached Client
* Used default values foe the time caps
* */
public CachedBulletinBoardClient(AsyncBulletinBoardClient localClient,
AsyncBulletinBoardClient remoteClient,
BulletinBoardSubscriber subscriber,
DeletableSubscriptionBulletinBoardClient queue) {
this(localClient, remoteClient, subscriber, queue, DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
}
@Override
public MessageID postMessage(final BulletinBoardMessage msg, final FutureCallback<Boolean> callback) {
return localClient.postMessage(msg, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postMessage(msg, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) { public MessageID postAsBatch(final BulletinBoardMessage msg, final int chunkSize, final FutureCallback<Boolean> callback) {
return null;
}
@Override return localClient.postAsBatch(msg, chunkSize, new FutureCallback<Boolean>() {
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { @Override
return null; public void onSuccess(Boolean result) {
} remoteClient.postAsBatch(msg, chunkSize, callback);
}
@Override @Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) { public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) { public void beginBatch(final Iterable<String> tags, final FutureCallback<BatchIdentifier> callback) {
localClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
private BatchIdentifier localIdentifier;
@Override
public void onSuccess(BatchIdentifier result) {
localIdentifier = result;
remoteClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
@Override
public void onSuccess(BatchIdentifier result) {
if (callback != null)
callback.onSuccess(new CachedClientBatchIdentifier(localIdentifier, result));
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList,
final int startPosition, final FutureCallback<Boolean> callback) throws IllegalArgumentException{
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, startPosition, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, startPosition, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) { public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList, final FutureCallback<Boolean> callback)
throws IllegalArgumentException{
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { public void closeBatch(final BatchIdentifier batchIdentifier, final Timestamp timestamp, final Iterable<Signature> signatures,
final FutureCallback<Boolean> callback) {
} if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
@Override final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
localClient.closeBatch(identifier.getLocalIdentifier(), timestamp, signatures, new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
remoteClient.closeBatch(identifier.getRemoteIdentifier(), timestamp, signatures, callback);
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void getRedundancy(MessageID id, FutureCallback<Float> callback) { public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
} remoteClient.getRedundancy(id, callback);
@Override
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
} }
@Override @Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { public void readMessages(MessageFilterList filterList, final FutureCallback<List<BulletinBoardMessage>> callback) {
localClient.readMessages(filterList, callback);
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
}
@Override
public void readMessage(final MessageID msgID, final FutureCallback<BulletinBoardMessage> callback) {
localClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
if (callback != null)
callback.onSuccess(result); // Read from local client was successful
}
@Override
public void onFailure(Throwable t) {
// Read from local unsuccessful: try to read from remote
remoteClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
// Read from remote was successful: store in local and return result
localClient.postMessage(result, null);
if (callback != null)
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
// Read from remote was unsuccessful: report error
if (callback != null)
callback.onFailure(t);
}
});
}
});
}
@Override
public void readBatchData(final BulletinBoardMessage stub, final FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
localClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
if (callback != null)
callback.onSuccess(result); // Read from local client was successful
}
@Override
public void onFailure(Throwable t) {
// Read from local unsuccessful: try to read from remote
remoteClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
@Override
public void onSuccess(BulletinBoardMessage result) {
// Read from remote was successful: store in local and return result
localClient.postMessage(result, null);
if (callback != null)
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
// Read from remote was unsuccessful: report error
if (callback != null)
callback.onFailure(t);
}
});
}
});
} }
@Override @Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) { public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
localClient.querySync(syncQuery, callback);
} }
@Override @Override
public void init(BulletinBoardClientParams clientParams) { /**
* This is a stub method
remoteClient.init(clientParams); * All resources are assumed to be initialized
*/
ListeningScheduledExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize)); public void init(BulletinBoardClientParams clientParams) {}
List<SubscriptionAsyncBulletinBoardClient> subscriberClients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
for (String address : clientParams.getBulletinBoardAddressList()){
SubscriptionAsyncBulletinBoardClient newClient =
new SingleServerBulletinBoardClient(executorService, failDelayInMilliseconds, subscriptionIntervalInMilliseconds);
newClient.init(clientParams.toBuilder().clearBulletinBoardAddress().addBulletinBoardAddress(address).build());
subscriberClients.add(newClient);
}
subscriber = new ThreadedBulletinBoardSubscriber(subscriberClients, localClient);
}
@Override @Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
return null; return localClient.postMessage(msg);
} }
@Override @Override
public float getRedundancy(MessageID id) { public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
return 0; MessageID result = localClient.postAsBatch(msg, chunkSize);
remoteClient.postAsBatch(msg, chunkSize);
return result;
} }
@Override @Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) { public float getRedundancy(MessageID id) throws CommunicationException {
return null; return remoteClient.getRedundancy(id);
} }
@Override @Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException { public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException {
return null; subscriber.subscribe(filterList, new SubscriptionStoreCallback());
return localClient.readMessages(filterList);
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
BulletinBoardMessage result = null;
try {
result = localClient.readMessage(msgID);
} catch (CommunicationException e) {
//TODO: log
}
if (result == null){
result = remoteClient.readMessage(msgID);
if (result != null){
localClient.postMessage(result);
}
}
return result;
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
BulletinBoardMessage result = null;
try {
result = localClient.readBatchData(stub);
} catch (CommunicationException e) {
//TODO: log
}
if (result == null){
result = remoteClient.readBatchData(stub);
if (result != null){
localClient.postMessage(result);
}
}
return result;
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
return localClient.generateSyncQuery(generateSyncQueryParams);
} }
@Override @Override
public void close() { public void close() {
localClient.close();
remoteClient.close();
synchronizer.stop();
try {
syncThread.join();
} catch (InterruptedException e) {
//TODO: log interruption
}
} }
@Override @Override
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) { public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
} }
@Override @Override
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) { public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
subscriber.subscribe(filterList, startEntry, new SubscriptionStoreCallback(callback));
} }
}
}

View File

@ -0,0 +1,29 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.Arrays;
/**
* Created by Arbel Deutsch Peled on 17-Jun-16.
*/
public final class CachedClientBatchIdentifier implements BatchIdentifier {
// Per-server identifiers
private final BatchIdentifier localIdentifier;
private final BatchIdentifier remoteIdentifier;
public CachedClientBatchIdentifier(BatchIdentifier localIdentifier, BatchIdentifier remoteIdentifier) {
this.localIdentifier = localIdentifier;
this.remoteIdentifier = remoteIdentifier;
}
public BatchIdentifier getLocalIdentifier() {
return localIdentifier;
}
public BatchIdentifier getRemoteIdentifier() {
return remoteIdentifier;
}
}

View File

@ -1,20 +1,21 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.google.protobuf.ByteString; import com.google.protobuf.Int64Value;
import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream; import meerkat.comm.MessageInputStream;
import meerkat.comm.MessageInputStream.MessageInputStreamFactory; import meerkat.comm.MessageInputStream.MessageInputStreamFactory;
import meerkat.comm.MessageOutputStream; import meerkat.comm.MessageOutputStream;
import meerkat.crypto.concrete.SHA256Digest; import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.Voting.*; import meerkat.protobuf.Voting.*;
import meerkat.util.BulletinBoardUtils; import meerkat.util.BulletinBoardUtils;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -22,28 +23,27 @@ import java.util.concurrent.TimeUnit;
/** /**
* Created by Arbel Deutsch Peled on 15-Mar-16. * Created by Arbel Deutsch Peled on 15-Mar-16.
* This client is to be used mainly for testing. * This client wraps a BulletinBoardServer in an asynchronous client.
* It wraps a BulletinBoardServer in an asynchronous client. * It is meant to be used as a local cache handler and for testing purposes.
* This means the access to the server is direct (via method calls) instead of through a TCP connection. * This means the access to the server is direct (via method calls) instead of through a TCP connection.
* The client implements both synchronous and asynchronous method calls, but calls to the server itself are performed synchronously. * The client implements both synchronous and asynchronous method calls, but calls to the server itself are performed synchronously.
*/ */
public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient{ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBoardClient {
private final BulletinBoardServer server; private final DeletableBulletinBoardServer server;
private final ListeningScheduledExecutorService executorService; private final ListeningScheduledExecutorService executorService;
private final BatchDigest digest; private final BulletinBoardDigest digest;
private final int subsrciptionDelay; private final long subsrciptionDelay;
/** /**
* Initializes an instance of the client * Initializes an instance of the client
* @param server an initialized Bulletin Board Server instance which will perform the actual processing of the requests * @param server an initialized Bulletin Board Server instance which will perform the actual processing of the requests
* @param threadNum is the number of concurrent threads to allocate for the client * @param threadNum is the number of concurrent threads to allocate for the client
* @param subscriptionDelay is the required delay between subscription calls in milliseconds
*/ */
public LocalBulletinBoardClient(BulletinBoardServer server, int threadNum, int subscriptionDelay) { public LocalBulletinBoardClient(DeletableBulletinBoardServer server, int threadNum, int subscriptionDelay) {
this.server = server; this.server = server;
this.executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadNum)); this.executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadNum));
this.digest = new GenericBatchDigest(new SHA256Digest()); this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
this.subsrciptionDelay = subscriptionDelay; this.subsrciptionDelay = subscriptionDelay;
} }
@ -57,7 +57,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws CommunicationException {
return server.postMessage(msg).getValue(); return server.postMessage(msg).getValue();
} }
@ -75,51 +75,57 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
private class CompleteBatchPoster implements Callable<Boolean> { private class CompleteBatchPoster implements Callable<Boolean> {
private final CompleteBatch completeBatch; private final BulletinBoardMessage msg;
private final int chunkSize;
public CompleteBatchPoster(CompleteBatch completeBatch) { public CompleteBatchPoster(BulletinBoardMessage msg, int chunkSize) {
this.completeBatch = completeBatch; this.msg = msg;
this.chunkSize = chunkSize;
} }
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws CommunicationException {
if (!server.beginBatch(completeBatch.getBeginBatchMessage()).getValue()) BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
return false; .addAllTag(msg.getMsg().getTagList())
.build();
Int64Value batchId = server.beginBatch(beginBatchMessage);
BatchMessage.Builder builder = BatchMessage.newBuilder()
.setBatchId(batchId.getValue());
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
int i=0; int i=0;
for (BatchData data : completeBatch.getBatchDataList()){ for (BatchChunk chunk : batchChunkList){
BatchMessage message = BatchMessage.newBuilder() server.postBatchMessage(builder.setSerialNum(i).setData(chunk).build());
.setSignerId(completeBatch.getSignature().getSignerId())
.setBatchId(completeBatch.getBeginBatchMessage().getBatchId())
.setSerialNum(i)
.setData(data)
.build();
if (!server.postBatchMessage(message).getValue())
return false;
i++; i++;
} }
return server.closeBatchMessage(completeBatch.getCloseBatchMessage()).getValue(); CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, batchChunkList.size(), msg);
return server.closeBatch(closeBatchMessage).getValue();
} }
} }
@Override @Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
Futures.addCallback(executorService.schedule(new CompleteBatchPoster(completeBatch), subsrciptionDelay, TimeUnit.MILLISECONDS), callback); Futures.addCallback(executorService.submit(new CompleteBatchPoster(msg, chunkSize)), callback);
digest.update(completeBatch); digest.reset();
digest.update(msg);
return digest.digestAsMessageID(); return digest.digestAsMessageID();
} }
private class BatchBeginner implements Callable<Boolean> { private class BatchBeginner implements Callable<SingleServerBatchIdentifier> {
private final BeginBatchMessage msg; private final BeginBatchMessage msg;
@ -129,28 +135,31 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override @Override
public Boolean call() throws Exception { public SingleServerBatchIdentifier call() throws Exception {
return server.beginBatch(msg).getValue(); return new SingleServerBatchIdentifier(server.beginBatch(msg));
} }
} }
@Override @Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) { public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(tags)
.build();
Futures.addCallback(executorService.submit(new BatchBeginner(beginBatchMessage)), callback); Futures.addCallback(executorService.submit(new BatchBeginner(beginBatchMessage)), callback);
} }
private class BatchDataPoster implements Callable<Boolean> { private class BatchDataPoster implements Callable<Boolean> {
private final ByteString signerId; private final SingleServerBatchIdentifier batchId;
private final int batchId; private final List<BatchChunk> batchChunkList;
private final List<BatchData> batchDataList;
private final int startPosition; private final int startPosition;
public BatchDataPoster(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition) { public BatchDataPoster(SingleServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
this.signerId = signerId;
this.batchId = batchId; this.batchId = batchId;
this.batchDataList = batchDataList; this.batchChunkList = batchChunkList;
this.startPosition = startPosition; this.startPosition = startPosition;
} }
@ -159,11 +168,10 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public Boolean call() throws Exception { public Boolean call() throws Exception {
BatchMessage.Builder msgBuilder = BatchMessage.newBuilder() BatchMessage.Builder msgBuilder = BatchMessage.newBuilder()
.setSignerId(signerId) .setBatchId(batchId.getBatchId().getValue());
.setBatchId(batchId);
int i = startPosition; int i = startPosition;
for (BatchData data : batchDataList){ for (BatchChunk data : batchChunkList){
msgBuilder.setSerialNum(i) msgBuilder.setSerialNum(i)
.setData(data); .setData(data);
@ -175,6 +183,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
} }
batchId.setLength(i);
return true; return true;
} }
@ -182,24 +192,28 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
} }
@Override @Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) { public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition, FutureCallback<Boolean> callback)
postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); throws IllegalArgumentException{
// Cast identifier to usable form
if (!(batchId instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
// Add worker
Futures.addCallback(executorService.submit(new BatchDataPoster(identifier, batchChunkList, startPosition)), callback);
} }
@Override @Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) throws IllegalArgumentException{
postBatchData(signerId, batchId, batchDataList, 0, callback); postBatchData(batchId, batchChunkList, 0, callback);
} }
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
Futures.addCallback(executorService.submit(new BatchDataPoster(signerId, batchId, batchDataList, startPosition)), callback);
}
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(signerId, batchId, batchDataList, 0, callback);
}
private class BatchCloser implements Callable<Boolean> { private class BatchCloser implements Callable<Boolean> {
@ -212,14 +226,33 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws Exception {
return server.closeBatchMessage(msg).getValue(); return server.closeBatch(msg).getValue();
} }
} }
@Override @Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) { public void closeBatch(BatchIdentifier batchId, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback) {
// Cast identifier to usable form
if (!(batchId instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
// Add worker
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
.setBatchId(identifier.getBatchId().getValue())
.setBatchLength(identifier.getLength())
.setTimestamp(timestamp)
.addAllSig(signatures)
.build();
Futures.addCallback(executorService.submit(new BatchCloser(closeBatchMessage)), callback); Futures.addCallback(executorService.submit(new BatchCloser(closeBatchMessage)), callback);
} }
private class RedundancyGetter implements Callable<Float> { private class RedundancyGetter implements Callable<Float> {
@ -310,7 +343,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user // Report new messages to user
callback.onSuccess(result); if (callback != null)
callback.onSuccess(result);
MessageFilterList.Builder filterBuilder = filterList.toBuilder(); MessageFilterList.Builder filterBuilder = filterList.toBuilder();
@ -331,7 +365,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
filterList = filterBuilder.build(); filterList = filterBuilder.build();
// Reschedule job // Reschedule job
Futures.addCallback(executorService.submit(new MessageReader(filterList)), this); Futures.addCallback(executorService.schedule(new MessageReader(filterList), subsrciptionDelay, TimeUnit.MILLISECONDS), this);
} }
@ -339,7 +373,8 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
// Notify caller about failure and terminate subscription // Notify caller about failure and terminate subscription
callback.onFailure(t); if (callback != null)
callback.onFailure(t);
} }
} }
@ -364,83 +399,122 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
subscribe(filterList, 0, callback); subscribe(filterList, 0, callback);
} }
private class CompleteBatchReader implements Callable<CompleteBatch> { private class BatchDataReader implements Callable<List<BatchChunk>> {
private final BatchSpecificationMessage batchSpecificationMessage; private final MessageID msgID;
public CompleteBatchReader(BatchSpecificationMessage batchSpecificationMessage) { public BatchDataReader(MessageID msgID) {
this.batchSpecificationMessage = batchSpecificationMessage; this.msgID = msgID;
}
@Override
public List<BatchChunk> call() throws Exception {
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BatchChunk> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readBatch(batchQuery,batchOutputStream);
MessageInputStream<BatchChunk> inputStream =
MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()),
BatchChunk.class);
return inputStream.asList();
}
}
private class CompleteBatchReader implements Callable<BulletinBoardMessage> {
private final MessageID msgID;
public CompleteBatchReader(MessageID msgID) {
this.msgID = msgID;
} }
@Override @Override
public CompleteBatch call() throws Exception { public BulletinBoardMessage call() throws Exception {
final String[] TAGS_TO_REMOVE = {BulletinBoardConstants.BATCH_TAG, BulletinBoardConstants.BATCH_ID_TAG_PREFIX}; // Read message (mat be a stub)
CompleteBatch completeBatch = new CompleteBatch(BeginBatchMessage.newBuilder()
.setSignerId(batchSpecificationMessage.getSignerId())
.setBatchId(batchSpecificationMessage.getBatchId())
.build());
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BatchData> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readBatch(batchSpecificationMessage,batchOutputStream);
MessageInputStream<BatchData> batchInputStream =
MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()),
BatchData.class);
completeBatch.appendBatchData(batchInputStream.asList());
MessageFilterList filterList = MessageFilterList.newBuilder() MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder() .addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG) .setType(FilterType.MSG_ID)
.setTag(BulletinBoardConstants.BATCH_TAG) .setId(msgID.getID())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + completeBatch.getBeginBatchMessage().getBatchId())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.SIGNER_ID)
.setId(completeBatch.getBeginBatchMessage().getSignerId())
.build()) .build())
.build(); .build();
byteOutputStream = new ByteArrayOutputStream(); MessageReader messageReader = new MessageReader(filterList);
MessageOutputStream<BulletinBoardMessage> messageOutputStream = new MessageOutputStream<>(byteOutputStream); List<BulletinBoardMessage> bulletinBoardMessages = messageReader.call();
server.readMessages(filterList,messageOutputStream);
MessageInputStream<BulletinBoardMessage> messageInputStream = if (bulletinBoardMessages.size() <= 0) {
MessageInputStreamFactory.createMessageInputStream( throw new NotFoundException("Message does not exist");
new ByteArrayInputStream(byteOutputStream.toByteArray()), }
BulletinBoardMessage.class);
if (!messageInputStream.isAvailable()) BulletinBoardMessage msg = bulletinBoardMessages.get(0);
throw new NotFoundException("Batch does not exist");
BulletinBoardMessage message = messageInputStream.readMessage(); if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder() // Read data
.addAllTag(BulletinBoardUtils.removePrefixTags(message, Arrays.asList(TAGS_TO_REMOVE)))
.setSignerId(message.getSig(0).getSignerId())
.setBatchId(Integer.parseInt(BulletinBoardUtils.findTagWithPrefix(message, BulletinBoardConstants.BATCH_ID_TAG_PREFIX)))
.build());
completeBatch.setSignature(message.getSig(0)); BatchDataReader batchDataReader = new BatchDataReader(msgID);
completeBatch.setTimestamp(message.getMsg().getTimestamp()); List<BatchChunk> batchChunkList = batchDataReader.call();
return completeBatch; // Combine and return
return BulletinBoardUtils.gatherBatch(msg, batchChunkList);
} else {
return msg;
}
}
}
private class BatchDataCombiner implements Callable<BulletinBoardMessage> {
private final BulletinBoardMessage stub;
public BatchDataCombiner(BulletinBoardMessage stub) {
this.stub = stub;
}
@Override
public BulletinBoardMessage call() throws Exception {
MessageID msgID = MessageID.newBuilder().setID(stub.getMsg().getMsgId()).build();
BatchDataReader batchDataReader = new BatchDataReader(msgID);
List<BatchChunk> batchChunkList = batchDataReader.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
} }
} }
@Override @Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
Futures.addCallback(executorService.submit(new CompleteBatchReader(batchSpecificationMessage)), callback); Futures.addCallback(executorService.submit(new CompleteBatchReader(msgID)), callback);
}
@Override
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
Futures.addCallback(executorService.submit(new BatchDataCombiner(stub)),callback);
} }
private class SyncQueryHandler implements Callable<SyncQueryResponse> { private class SyncQueryHandler implements Callable<SyncQueryResponse> {
@ -474,17 +548,26 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
@Override @Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
try {
MessagePoster poster = new MessagePoster(msg); MessagePoster poster = new MessagePoster(msg);
poster.call(); poster.call();
digest.update(msg); digest.update(msg.getMsg());
return digest.digestAsMessageID(); return digest.digestAsMessageID();
} catch (Exception e) { }
return null;
} @Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
CompleteBatchPoster poster = new CompleteBatchPoster(msg, chunkSize);
Boolean result = poster.call();
if (!result)
throw new CommunicationException("Batch post failed");
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
} }
@ -503,7 +586,7 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
} }
@Override @Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) { public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
try { try {
@ -511,14 +594,89 @@ public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardC
return reader.call(); return reader.call();
} catch (Exception e){ } catch (Exception e){
return null; throw new CommunicationException("Error reading from server");
} }
} }
@Override @Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException { public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
return server.generateSyncQuery(GenerateSyncQueryParams);
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
CompleteBatchReader completeBatchReader = new CompleteBatchReader(msgID);
try {
return completeBatchReader.call();
} catch (Exception e) {
throw new CommunicationException(e.getMessage() + " " + e.getMessage());
}
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
BatchDataCombiner combiner = new BatchDataCombiner(stub);
try {
return combiner.call();
} catch (Exception e) {
throw new CommunicationException(e.getCause() + " " + e.getMessage());
}
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
return server.generateSyncQuery(generateSyncQueryParams);
}
@Override
public void deleteMessage(MessageID msgID, FutureCallback<Boolean> callback) {
try {
Boolean deleted = server.deleteMessage(msgID).getValue();
if (callback != null)
callback.onSuccess(deleted);
} catch (CommunicationException e) {
if (callback != null)
callback.onFailure(e);
}
}
@Override
public void deleteMessage(long entryNum, FutureCallback<Boolean> callback) {
try {
Boolean deleted = server.deleteMessage(entryNum).getValue();
if (callback != null)
callback.onSuccess(deleted);
} catch (CommunicationException e) {
if (callback != null)
callback.onFailure(e);
}
}
@Override
public boolean deleteMessage(MessageID msgID) throws CommunicationException {
return server.deleteMessage(msgID).getValue();
}
@Override
public boolean deleteMessage(long entryNum) throws CommunicationException {
return server.deleteMessage(entryNum).getValue();
} }
@Override @Override

View File

@ -0,0 +1,27 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.util.Arrays;
/**
* Created by Arbel Deutsch Peled on 17-Jun-16.
*/
public final class MultiServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
// Per-server identifiers
private final Iterable<BatchIdentifier> identifiers;
public MultiServerBatchIdentifier(Iterable<BatchIdentifier> identifiers) {
this.identifiers = identifiers;
}
public MultiServerBatchIdentifier(BatchIdentifier[] identifiers) {
this.identifiers = Arrays.asList(identifiers);
}
public Iterable<BatchIdentifier> getIdentifiers() {
return identifiers;
}
}

View File

@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*/ */
public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{
private final List<SingleServerBulletinBoardClient> clients; protected final List<SingleServerBulletinBoardClient> clients;
protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed
@ -74,7 +74,8 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN
*/ */
protected void succeed(OUT result){ protected void succeed(OUT result){
if (returnedResult.compareAndSet(false, true)) { if (returnedResult.compareAndSet(false, true)) {
futureCallback.onSuccess(result); if (futureCallback != null)
futureCallback.onSuccess(result);
} }
} }
@ -85,18 +86,11 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN
*/ */
protected void fail(Throwable t){ protected void fail(Throwable t){
if (returnedResult.compareAndSet(false, true)) { if (returnedResult.compareAndSet(false, true)) {
futureCallback.onFailure(t); if (futureCallback != null)
futureCallback.onFailure(t);
} }
} }
/**
* Used by implementations to get a Single Server Client iterator
* @return the requested iterator
*/
protected Iterator<SingleServerBulletinBoardClient> getClientIterator() {
return clients.iterator();
}
protected int getClientNumber() { protected int getClientNumber() {
return clients.size(); return clients.size();
} }

View File

@ -1,192 +1,329 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.protobuf.BoolValue; import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import meerkat.comm.CommunicationException; import com.google.protobuf.Int64Value;
import meerkat.crypto.Digest; import meerkat.bulletinboard.workers.singleserver.*;
import meerkat.crypto.concrete.SHA256Digest; import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.Comm.*; import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.Voting.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.rest.*; import meerkat.protobuf.Voting.*;
import meerkat.rest.*;
import java.util.List; import meerkat.util.BulletinBoardUtils;
import javax.ws.rs.client.Client; import java.util.List;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Client;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response; import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import static meerkat.bulletinboard.BulletinBoardConstants.*; import javax.ws.rs.core.Response;
/** import static meerkat.bulletinboard.BulletinBoardConstants.*;
* Created by Arbel Deutsch Peled on 05-Dec-15.
* Implements BulletinBoardClient interface in a simple, straightforward manner /**
*/ * Created by Arbel Deutsch Peled on 05-Dec-15.
public class SimpleBulletinBoardClient implements BulletinBoardClient{ * Implements BulletinBoardClient interface in a simple, straightforward manner
*/
protected List<String> meerkatDBs; public class SimpleBulletinBoardClient implements BulletinBoardClient{
protected Client client; protected List<String> meerkatDBs;
protected Digest digest; protected Client client;
/** protected BulletinBoardDigest digest;
* Stores database locations and initializes the web Client
* @param clientParams contains the data needed to access the DBs /**
*/ * Stores database locations and initializes the web Client
@Override * @param clientParams contains the data needed to access the DBs
public void init(BulletinBoardClientParams clientParams) { */
@Override
this.meerkatDBs = clientParams.getBulletinBoardAddressList(); public void init(BulletinBoardClientParams clientParams) {
client = ClientBuilder.newClient(); this.meerkatDBs = clientParams.getBulletinBoardAddressList();
client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class); client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class);
digest = new SHA256Digest(); client.register(ProtobufMessageBodyWriter.class);
} // Wrap the Digest into a BatchDigest
digest = new GenericBulletinBoardDigest(new SHA256Digest());
/**
* Post message to all DBs }
* Make only one try per DB.
* @param msg is the message, /**
* @return the message ID for later retrieval * Post message to all DBs
* @throws CommunicationException * Make only one try per DB.
*/ * @param msg is the message,
@Override * @return the message ID for later retrieval
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { * @throws CommunicationException
*/
WebTarget webTarget; @Override
Response response; public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
// Post message to all databases WebTarget webTarget;
try { Response response = null;
for (String db : meerkatDBs) {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH); // Post message to all databases
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); try {
for (String db : meerkatDBs) {
// Only consider valid responses
if (response.getStatusInfo() == Response.Status.OK SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(db, msg, 0);
|| response.getStatusInfo() == Response.Status.CREATED) {
response.readEntity(BoolValue.class).getValue(); worker.call();
}
} }
} catch (Exception e) { // Occurs only when server replies with valid status but invalid data } catch (Exception e) { // Occurs only when server replies with valid status but invalid data
throw new CommunicationException("Error accessing database: " + e.getMessage()); throw new CommunicationException("Error accessing database: " + e.getMessage());
} }
// Calculate the correct message ID and return it // Calculate the correct message ID and return it
digest.reset(); digest.reset();
digest.update(msg.getMsg()); digest.update(msg.getMsg());
return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build(); return MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
} }
/** /**
* Access each database and search for a given message ID * Access each database and search for a given message ID
* Return the number of databases in which the message was found * Return the number of databases in which the message was found
* Only try once per DB * Only try once per DB
* Ignore communication exceptions in specific databases * Ignore communication exceptions in specific databases
* @param id is the requested message ID * @param id is the requested message ID
* @return the number of DBs in which retrieval was successful * @return the number of DBs in which retrieval was successful
*/ */
@Override @Override
public float getRedundancy(MessageID id) { public float getRedundancy(MessageID id) {
WebTarget webTarget; WebTarget webTarget;
Response response; Response response;
MessageFilterList filterList = MessageFilterList.newBuilder() MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder() .addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID) .setType(FilterType.MSG_ID)
.setId(id.getID()) .setId(id.getID())
.build()) .build())
.build(); .build();
float count = 0; float count = 0;
for (String db : meerkatDBs) { for (String db : meerkatDBs) {
try { try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){ if (response.readEntity(BulletinBoardMessageList.class).getMessageCount() > 0){
count++; count++;
} }
} catch (Exception e) {} } catch (Exception e) {}
} }
return count / ((float) meerkatDBs.size()); return count / ((float) meerkatDBs.size());
} }
/** /**
* Go through the DBs and try to retrieve messages according to the specified filter * Go through the DBs and try to retrieve messages according to the specified filter
* If at the operation is successful for some DB: return the results and stop iterating * If at the operation is successful for some DB: return the results and stop iterating
* If no operation is successful: return null (NOT blank list) * If no operation is successful: return null (NOT blank list)
* @param filterList return only messages that match the filters (null means no filtering). * @param filterList return only messages that match the filters (null means no filtering).
* @return the list of Bulletin Board messages that are returned from a server * @return the list of Bulletin Board messages that are returned from a server
*/ */
@Override @Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) { public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
WebTarget webTarget; // Replace null filter list with blank one.
Response response; if (filterList == null){
BulletinBoardMessageList messageList; filterList = MessageFilterList.getDefaultInstance();
}
// Replace null filter list with blank one.
if (filterList == null){ String exceptionString = "";
filterList = MessageFilterList.newBuilder().build();
} for (String db : meerkatDBs) {
for (String db : meerkatDBs) { try {
try { SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0);
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
List<BulletinBoardMessage> result = worker.call();
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
return result;
messageList = response.readEntity(BulletinBoardMessageList.class);
} catch (Exception e) {
if (messageList != null){ //TODO: log
return messageList.getMessageList(); exceptionString += e.getMessage() + "\n";
} }
}
} catch (Exception e) {}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
}
return null;
@Override
} public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
@Override List<BatchChunk> chunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
BeginBatchMessage beginBatchMessage = BulletinBoardUtils.generateBeginBatchMessage(msg);
WebTarget webTarget;
Response response; boolean posted = false;
for (String db : meerkatDBs) { // Post message to all databases
try { for (String db : meerkatDBs) {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
try {
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF));
int pos = 0;
return response.readEntity(SyncQuery.class);
SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(db, beginBatchMessage, 0);
} catch (Exception e) {}
Int64Value batchId = beginBatchWorker.call();
}
BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(batchId.getValue());
throw new CommunicationException("Could not contact any server");
for (BatchChunk batchChunk : chunkList) {
}
SingleServerPostBatchWorker postBatchWorker =
public void close() { new SingleServerPostBatchWorker(
client.close(); db,
} builder.setData(batchChunk).setSerialNum(pos).build(),
0);
}
postBatchWorker.call();
pos++;
}
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, chunkList.size(), msg);
SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(db, closeBatchMessage, 0);
closeBatchWorker.call();
posted = true;
} catch(Exception ignored) {}
}
if (!posted){
throw new CommunicationException("Could not post to any server");
}
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
String exceptionString = "";
for (String db : meerkatDBs) {
try {
SingleServerReadMessagesWorker messagesWorker = new SingleServerReadMessagesWorker(db, filterList, 0);
List<BulletinBoardMessage> messages = messagesWorker.call();
if (messages == null || messages.size() < 1)
continue;
BulletinBoardMessage stub = messages.get(0);
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
List<BatchChunk> batchChunkList = batchWorker.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
} catch (Exception e) {
//TODO: log
exceptionString += e.getMessage() + "\n";
}
}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
String exceptionString = "";
for (String db : meerkatDBs) {
try {
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
List<BatchChunk> batchChunkList = batchWorker.call();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
} catch (Exception e) {
//TODO: log
exceptionString += e.getMessage() + "\n";
}
}
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
WebTarget webTarget;
Response response;
for (String db : meerkatDBs) {
try {
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF));
return response.readEntity(SyncQuery.class);
} catch (Exception e) {}
}
throw new CommunicationException("Could not contact any server");
}
public void close() {
client.close();
}
}

View File

@ -0,0 +1,241 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.util.BulletinBoardUtils;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by Arbel on 13/04/2016.
* Simple, straightforward implementation of the {@link BulletinBoardSynchronizer} interface
*/
public class SimpleBulletinBoardSynchronizer implements BulletinBoardSynchronizer {
private DeletableSubscriptionBulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient;
private AtomicBoolean running;
private volatile SyncStatus syncStatus;
private List<FutureCallback<Integer>> messageCountCallbacks;
private List<FutureCallback<SyncStatus>> syncStatusCallbacks;
private static final MessageFilterList EMPTY_FILTER = MessageFilterList.getDefaultInstance();
private static final int DEFAULT_SLEEP_INTERVAL = 10000; // 10 Seconds
private static final int DEFAULT_WAIT_CAP = 300000; // 5 minutes wait before deciding that the sync has failed fatally
private final int SLEEP_INTERVAL;
private final int WAIT_CAP;
private Semaphore semaphore;
private class SyncCallback implements FutureCallback<List<BulletinBoardMessage>> {
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
// Notify Message Count callbacks if needed
if (syncStatus != SyncStatus.SYNCHRONIZED || result.size() > 0) {
for (FutureCallback<Integer> callback : messageCountCallbacks){
callback.onSuccess(result.size());
}
}
// Handle upload and status change
SyncStatus newStatus = SyncStatus.PENDING;
if (result.size() == 0) {
newStatus = SyncStatus.SYNCHRONIZED;
semaphore.release();
}
else{ // Upload messages
for (BulletinBoardMessage message : result){
try {
if (message.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// This is a batch message: need to upload batch data as well as the message itself
BulletinBoardMessage completeMsg = localClient.readBatchData(message);
remoteClient.postMessage(completeMsg);
localClient.deleteMessage(completeMsg.getEntryNum());
} else {
// This is a regular message: post it
remoteClient.postMessage(message);
localClient.deleteMessage(message.getEntryNum());
}
} catch (CommunicationException e) {
// This is an error with the local server
// TODO: log
updateSyncStatus(SyncStatus.SERVER_ERROR);
}
}
}
updateSyncStatus(newStatus);
}
@Override
public void onFailure(Throwable t) {
updateSyncStatus(SyncStatus.SERVER_ERROR);
}
}
public SimpleBulletinBoardSynchronizer(int sleepInterval, int waitCap) {
this.syncStatus = SyncStatus.STOPPED;
this.SLEEP_INTERVAL = sleepInterval;
this.WAIT_CAP = waitCap;
this.running = new AtomicBoolean(false);
}
public SimpleBulletinBoardSynchronizer() {
this(DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
}
private synchronized void updateSyncStatus(SyncStatus newStatus) {
if (!running.get()) {
newStatus = SyncStatus.STOPPED;
}
if (newStatus != syncStatus){
syncStatus = newStatus;
for (FutureCallback<SyncStatus> callback : syncStatusCallbacks){
if (callback != null)
callback.onSuccess(syncStatus);
}
}
}
@Override
public void init(DeletableSubscriptionBulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient) {
updateSyncStatus(SyncStatus.STOPPED);
this.localClient = localClient;
this.remoteClient = remoteClient;
messageCountCallbacks = new LinkedList<>();
syncStatusCallbacks = new LinkedList<>();
semaphore = new Semaphore(0);
}
@Override
public SyncStatus getSyncStatus() {
return syncStatus;
}
@Override
public void subscribeToSyncStatus(FutureCallback<SyncStatus> callback) {
syncStatusCallbacks.add(callback);
}
@Override
public List<BulletinBoardMessage> getRemainingMessages() throws CommunicationException{
return localClient.readMessages(EMPTY_FILTER);
}
@Override
public void getRemainingMessages(FutureCallback<List<BulletinBoardMessage>> callback) {
localClient.readMessages(EMPTY_FILTER, callback);
}
@Override
public long getRemainingMessagesCount() throws CommunicationException {
return localClient.readMessages(EMPTY_FILTER).size();
}
@Override
public void subscribeToRemainingMessagesCount(FutureCallback<Integer> callback) {
messageCountCallbacks.add(callback);
}
@Override
public void run() {
if (running.compareAndSet(false,true)){
updateSyncStatus(SyncStatus.PENDING);
SyncCallback callback = new SyncCallback();
while (syncStatus != SyncStatus.STOPPED) {
do {
localClient.readMessages(EMPTY_FILTER, callback);
try {
semaphore.tryAcquire(WAIT_CAP, TimeUnit.MILLISECONDS);
//TODO: log hard error. Too much time trying to upload data.
} catch (InterruptedException ignored) {
// We expect an interruption when the upload will complete
}
} while (syncStatus == SyncStatus.PENDING);
// Database is synced. Wait for new data.
try {
semaphore.tryAcquire(SLEEP_INTERVAL, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//TODO: log (probably nudged)
}
}
}
}
@Override
public void nudge() {
semaphore.release();
}
@Override
public void stop() {
running.set(false);
updateSyncStatus(SyncStatus.STOPPED);
}
}

View File

@ -0,0 +1,42 @@
package meerkat.bulletinboard;
import com.google.protobuf.Int64Value;
/**
* Created by Arbel Deutsch Peled on 16-Jun-16.
* Single-server implementation of the BatchIdentifier interface
*/
final class SingleServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
private final Int64Value batchId;
private int length;
public SingleServerBatchIdentifier(Int64Value batchId) {
this.batchId = batchId;
length = 0;
}
public SingleServerBatchIdentifier(long batchId) {
this(Int64Value.newBuilder().setValue(batchId).build());
}
public Int64Value getBatchId() {
return batchId;
}
/**
* Overrides the existing length with the new one only if the new length is longer
* @param newLength
*/
public void setLength(int newLength) {
if (newLength > length) {
length = newLength;
}
}
public int getLength() {
return length;
}
}

View File

@ -4,16 +4,19 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString; import com.google.protobuf.Int64Value;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.workers.singleserver.*; import meerkat.bulletinboard.workers.singleserver.*;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Voting.BulletinBoardClientParams; import meerkat.protobuf.Voting.BulletinBoardClientParams;
import meerkat.util.BulletinBoardUtils; import meerkat.util.BulletinBoardUtils;
import java.util.Arrays; import javax.ws.rs.client.Client;
import java.lang.Iterable;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -28,19 +31,23 @@ import java.util.concurrent.atomic.AtomicInteger;
* If the list of servers contains more than one server: the server actually used is the first one * If the list of servers contains more than one server: the server actually used is the first one
* The class further implements a delayed access to the server after a communication error occurs * The class further implements a delayed access to the server after a communication error occurs
*/ */
public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient { public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoardClient {
protected Client client;
protected BulletinBoardDigest digest;
private String dbAddress;
private final int MAX_RETRIES = 11; private final int MAX_RETRIES = 11;
private ListeningScheduledExecutorService executorService; private final ListeningScheduledExecutorService executorService;
protected BatchDigest batchDigest;
private long lastServerErrorTime; private long lastServerErrorTime;
private final long failDelayInMilliseconds; private final long FAIL_DELAY_IN_MILLISECONDS;
private final long subscriptionIntervalInMilliseconds; private final long SUBSCRIPTION_INTERVAL_IN_MILLISECONDS;
/** /**
* Notify the client that a job has failed * Notify the client that a job has failed
@ -53,6 +60,43 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
} }
private class SynchronousRetry<OUT> {
private final SingleServerWorker<?,OUT> worker;
private String thrown;
public SynchronousRetry(SingleServerWorker<?,OUT> worker) {
this.worker = worker;
this.thrown = "Could not contact server. Errors follow:\n";
}
OUT run() throws CommunicationException {
do {
try {
return worker.call();
} catch (Exception e) {
thrown += e.getCause() + " " + e.getMessage() + "\n";
}
try {
Thread.sleep(FAIL_DELAY_IN_MILLISECONDS);
} catch (InterruptedException e) {
//TODO: log
}
worker.decMaxRetry();
} while (worker.isRetry());
throw new CommunicationException(thrown);
}
}
/** /**
* This method adds a worker to the scheduled queue of the threadpool * This method adds a worker to the scheduled queue of the threadpool
* If the server is in an accessible state: the job is submitted for immediate handling * If the server is in an accessible state: the job is submitted for immediate handling
@ -64,7 +108,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime; long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime;
if (timeSinceLastServerError >= failDelayInMilliseconds) { if (timeSinceLastServerError >= FAIL_DELAY_IN_MILLISECONDS) {
// Schedule for immediate processing // Schedule for immediate processing
Futures.addCallback(executorService.submit(worker), callback); Futures.addCallback(executorService.submit(worker), callback);
@ -74,7 +118,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
// Schedule for processing immediately following delay expiry // Schedule for processing immediately following delay expiry
Futures.addCallback(executorService.schedule( Futures.addCallback(executorService.schedule(
worker, worker,
failDelayInMilliseconds - timeSinceLastServerError, FAIL_DELAY_IN_MILLISECONDS - timeSinceLastServerError,
TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS),
callback); callback);
@ -97,7 +141,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override @Override
public void onSuccess(T result) { public void onSuccess(T result) {
futureCallback.onSuccess(result); if (futureCallback != null)
futureCallback.onSuccess(result);
} }
@Override @Override
@ -115,7 +160,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
scheduleWorker(worker, this); scheduleWorker(worker, this);
} else { } else {
// No more retries: notify caller about failure // No more retries: notify caller about failure
futureCallback.onFailure(t); if (futureCallback != null)
futureCallback.onFailure(t);
} }
} }
@ -127,14 +173,14 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
* It reports success back to the user only if all of the batch-data were successfully posted * It reports success back to the user only if all of the batch-data were successfully posted
* If any batch-data fails to post: this callback reports failure * If any batch-data fails to post: this callback reports failure
*/ */
class PostBatchDataListCallback implements FutureCallback<Boolean> { class PostBatchChunkListCallback implements FutureCallback<Boolean> {
private final FutureCallback<Boolean> callback; private final FutureCallback<Boolean> callback;
private AtomicInteger batchDataRemaining; private AtomicInteger batchDataRemaining;
private AtomicBoolean aggregatedResult; private AtomicBoolean aggregatedResult;
public PostBatchDataListCallback(int batchDataLength, FutureCallback<Boolean> callback) { public PostBatchChunkListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
this.callback = callback; this.callback = callback;
this.batchDataRemaining = new AtomicInteger(batchDataLength); this.batchDataRemaining = new AtomicInteger(batchDataLength);
@ -150,7 +196,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
} }
if (batchDataRemaining.decrementAndGet() == 0){ if (batchDataRemaining.decrementAndGet() == 0){
callback.onSuccess(this.aggregatedResult.get()); if (callback != null)
callback.onSuccess(this.aggregatedResult.get());
} }
} }
@ -158,110 +205,80 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
// Notify caller about failure // Notify caller about failure
callback.onFailure(t); if (callback != null)
callback.onFailure(t);
} }
} }
private class ReadBatchCallback implements FutureCallback<List<BatchChunk>> {
private final BulletinBoardMessage stub;
private final FutureCallback<BulletinBoardMessage> callback;
public ReadBatchCallback(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) {
this.stub = stub;
this.callback = callback;
}
@Override
public void onSuccess(List<BatchChunk> result) {
callback.onSuccess(BulletinBoardUtils.gatherBatch(stub, result));
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}
/** /**
* This callback ties together the different parts of a CompleteBatch as they arrive from the server * This callback receives a message which may be a stub
* It assembles a CompleteBatch from the parts and sends it to the user if all parts arrived * If the message is not a stub: it returns it as is to a callback function
* If any part fails to arrive: it invokes the onFailure method * If it is a stub: it schedules a read of the batch data which will return a complete message to the callback function
*/ */
class CompleteBatchReadCallback { class CompleteMessageReadCallback implements FutureCallback<List<BulletinBoardMessage>>{
private final FutureCallback<CompleteBatch> callback; private final FutureCallback<BulletinBoardMessage> callback;
private List<BatchData> batchDataList; public CompleteMessageReadCallback(FutureCallback<BulletinBoardMessage> callback) {
private BulletinBoardMessage batchMessage;
private AtomicInteger remainingQueries;
private AtomicBoolean failed;
public CompleteBatchReadCallback(FutureCallback<CompleteBatch> callback) {
this.callback = callback; this.callback = callback;
remainingQueries = new AtomicInteger(2);
failed = new AtomicBoolean(false);
} }
protected void combineAndReturn() { @Override
public void onSuccess(List<BulletinBoardMessage> result) {
if (result.size() <= 0) {
onFailure(new CommunicationException("Could not find required message on the server."));
} else {
final String[] prefixes = { BulletinBoardMessage msg = result.get(0);
BulletinBoardConstants.BATCH_ID_TAG_PREFIX,
BulletinBoardConstants.BATCH_TAG};
if (remainingQueries.decrementAndGet() == 0){ if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
callback.onSuccess(msg);
} else {
String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX); // Create job with MAX retries for retrieval of the Batch Data List
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(msg.getMsg().getMsgId())
.build())
.build();
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
scheduleWorker(batchWorker, new ReadBatchCallback(msg, callback));
if (batchIdStr == null){
callback.onFailure(new CommunicationException("Server returned invalid message with no Batch ID tag"));
} }
BeginBatchMessage beginBatchMessage =
BeginBatchMessage.newBuilder()
.setSignerId(batchMessage.getSig(0).getSignerId())
.setBatchId(Integer.parseInt(batchIdStr))
.addAllTag(BulletinBoardUtils.removePrefixTags(batchMessage, Arrays.asList(prefixes)))
.build();
callback.onSuccess(new CompleteBatch(beginBatchMessage, batchDataList, batchMessage.getSig(0)));
}
}
protected void fail(Throwable t) {
if (failed.compareAndSet(false, true)) {
callback.onFailure(t);
} }
} }
/** @Override
* @return a FutureCallback for the Batch Data List that ties to this object public void onFailure(Throwable t) {
*/ callback.onFailure(t);
public FutureCallback<List<BatchData>> asBatchDataListFutureCallback() {
return new FutureCallback<List<BatchData>>() {
@Override
public void onSuccess(List<BatchData> result) {
batchDataList = result;
combineAndReturn();
}
@Override
public void onFailure(Throwable t) {
fail(t);
}
};
}
/**
* @return a FutureCallback for the Bulletin Board Message that ties to this object
*/
public FutureCallback<List<BulletinBoardMessage>> asBulletinBoardMessageListFutureCallback() {
return new FutureCallback<List<BulletinBoardMessage>>() {
@Override
public void onSuccess(List<BulletinBoardMessage> result) {
if (result.size() < 1){
onFailure(new IllegalArgumentException("Server returned empty message list"));
return;
}
batchMessage = result.get(0);
combineAndReturn();
}
@Override
public void onFailure(Throwable t) {
fail(t);
}
};
} }
} }
@ -289,22 +306,31 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user // Report new messages to user
callback.onSuccess(result); if (callback != null)
callback.onSuccess(result);
// Remove last filter from list (MIN_ENTRY one) // Update filter if needed
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
// Add updated MIN_ENTRY filter (entry number is successor of last received entry's number) if (result.size() > 0) {
filterBuilder.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MIN_ENTRY) // Remove last filter from list (MIN_ENTRY one)
.setEntry(result.get(result.size() - 1).getEntryNum() + 1) filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
.build());
// Add updated MIN_ENTRY filter (entry number is successor of last received entry's number)
filterBuilder.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MIN_ENTRY)
.setEntry(result.get(result.size() - 1).getEntryNum() + 1)
.build());
}
// Create new worker with updated task // Create new worker with updated task
worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), 1); worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), MAX_RETRIES);
// Schedule the worker RetryCallback<List<BulletinBoardMessage>> retryCallback = new RetryCallback<>(worker, this);
scheduleWorker(worker, this);
// Schedule the worker to run after the given interval has elapsed
Futures.addCallback(executorService.schedule(worker, SUBSCRIPTION_INTERVAL_IN_MILLISECONDS, TimeUnit.MILLISECONDS), retryCallback);
} }
@ -315,7 +341,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
fail(); fail();
// Notify caller about failure and terminate subscription // Notify caller about failure and terminate subscription
callback.onFailure(t); if (callback != null)
callback.onFailure(t);
} }
} }
@ -325,8 +352,8 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
this.executorService = executorService; this.executorService = executorService;
this.failDelayInMilliseconds = failDelayInMilliseconds; this.FAIL_DELAY_IN_MILLISECONDS = failDelayInMilliseconds;
this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds; this.SUBSCRIPTION_INTERVAL_IN_MILLISECONDS = subscriptionIntervalInMilliseconds;
// Set server error time to a time sufficiently in the past to make new jobs go through // Set server error time to a time sufficiently in the past to make new jobs go through
lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds; lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds;
@ -348,79 +375,289 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override @Override
public void init(BulletinBoardClientParams clientParams) { public void init(BulletinBoardClientParams clientParams) {
// Perform usual setup this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
super.init(clientParams);
// Wrap the Digest into a BatchDigest
batchDigest = new GenericBatchDigest(digest);
// Remove all but first DB address // Remove all but first DB address
String dbAddress = meerkatDBs.get(0); this.dbAddress = clientParams.getBulletinBoardAddress(0);
meerkatDBs = new LinkedList<>();
meerkatDBs.add(dbAddress);
} }
// Synchronous methods
@Override
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES);
SynchronousRetry<Boolean> retry = new SynchronousRetry<>(worker);
retry.run();
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public float getRedundancy(MessageID id) throws CommunicationException {
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, MAX_RETRIES);
SynchronousRetry<Float> retry = new SynchronousRetry<>(worker);
return retry.run();
}
@Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException {
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(worker);
return retry.run();
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
// Begin the batch and obtain identifier
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
.addAllTag(msg.getMsg().getTagList())
.build();
SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
SynchronousRetry<Int64Value> beginRetry = new SynchronousRetry<>(beginBatchWorker);
Int64Value identifier = beginRetry.run();
// Post data chunks
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(identifier.getValue());
int position = 0;
for (BatchChunk data : batchChunkList) {
builder.setSerialNum(position).setData(data);
SingleServerPostBatchWorker dataWorker = new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES);
SynchronousRetry<Boolean> dataRetry = new SynchronousRetry<>(dataWorker);
dataRetry.run();
// Increment position in batch
position++;
}
// Close batch
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
.setBatchId(identifier.getValue())
.addAllSig(msg.getSigList())
.setTimestamp(msg.getMsg().getTimestamp())
.setBatchLength(position)
.build();
SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES);
SynchronousRetry<Boolean> retry = new SynchronousRetry<>(closeBatchWorker);
retry.run();
// Calculate ID and return
digest.reset();
digest.update(msg);
return digest.digestAsMessageID();
}
@Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
// Retrieve message (which may be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build();
SingleServerReadMessagesWorker stubWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(stubWorker);
List<BulletinBoardMessage> messages = retry.run();
if (messages.size() <= 0) {
throw new CommunicationException("Could not find message in database.");
}
BulletinBoardMessage msg = messages.get(0);
if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
// We retrieved a complete message. Return it.
return msg;
} else {
// We retrieved a stub. Retrieve data.
return readBatchData(msg);
}
}
@Override
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
SingleServerReadBatchWorker readBatchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
SynchronousRetry<List<BatchChunk>> batchRetry = new SynchronousRetry<>(readBatchWorker);
List<BatchChunk> batchChunkList = batchRetry.run();
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
}
@Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
SingleServerGenerateSyncQueryWorker worker =
new SingleServerGenerateSyncQueryWorker(dbAddress, generateSyncQueryParams, MAX_RETRIES);
SynchronousRetry<SyncQuery> retry = new SynchronousRetry<>(worker);
return retry.run();
}
// Asynchronous methods
@Override @Override
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) { public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
// Create worker with redundancy 1 and MAX_RETRIES retries // Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(meerkatDBs.get(0), msg, MAX_RETRIES); SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES);
// Submit worker and create callback // Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
// Calculate the correct message ID and return it // Calculate the correct message ID and return it
batchDigest.reset(); digest.reset();
batchDigest.update(msg.getMsg()); digest.update(msg.getMsg());
return batchDigest.digestAsMessageID(); return digest.digestAsMessageID();
} }
private class PostBatchDataCallback implements FutureCallback<Boolean> { private class PostBatchDataCallback implements FutureCallback<Boolean> {
private final CompleteBatch completeBatch; private final BulletinBoardMessage msg;
private final BatchIdentifier identifier;
private final FutureCallback<Boolean> callback; private final FutureCallback<Boolean> callback;
public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { public PostBatchDataCallback(BulletinBoardMessage msg, BatchIdentifier identifier, FutureCallback<Boolean> callback) {
this.completeBatch = completeBatch; this.msg = msg;
this.identifier = identifier;
this.callback = callback; this.callback = callback;
} }
@Override @Override
public void onSuccess(Boolean msg) { public void onSuccess(Boolean result) {
closeBatch( closeBatch(
completeBatch.getCloseBatchMessage(), identifier,
msg.getMsg().getTimestamp(),
msg.getSigList(),
callback callback
); );
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
callback.onFailure(t); if (callback != null)
callback.onFailure(t);
} }
} }
private class BeginBatchCallback implements FutureCallback<Boolean> { private class ContinueBatchCallback implements FutureCallback<BatchIdentifier> {
private final CompleteBatch completeBatch; private final BulletinBoardMessage msg;
private final int chunkSize;
private final FutureCallback<Boolean> callback; private final FutureCallback<Boolean> callback;
public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { public ContinueBatchCallback(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
this.completeBatch = completeBatch; this.msg = msg;
this.chunkSize = chunkSize;
this.callback = callback; this.callback = callback;
} }
@Override @Override
public void onSuccess(Boolean msg) { public void onSuccess(BatchIdentifier identifier) {
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
postBatchData( postBatchData(
completeBatch.getBeginBatchMessage().getSignerId(), identifier,
completeBatch.getBeginBatchMessage().getBatchId(), batchChunkList,
completeBatch.getBatchDataList(),
0, 0,
new PostBatchDataCallback(completeBatch,callback)); new PostBatchDataCallback(msg, identifier, callback));
}
@Override
public void onFailure(Throwable t) {
if (callback != null)
callback.onFailure(t);
}
}
@Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
beginBatch(
msg.getMsg().getTagList(),
new ContinueBatchCallback(msg, chunkSize, callback)
);
digest.update(msg);
return digest.digestAsMessageID();
}
private class BeginBatchCallback implements FutureCallback<Int64Value> {
private final FutureCallback<BatchIdentifier> callback;
public BeginBatchCallback(FutureCallback<BatchIdentifier> callback) {
this.callback = callback;
}
@Override
public void onSuccess(Int64Value result) {
callback.onSuccess(new SingleServerBatchIdentifier(result));
} }
@Override @Override
@ -430,51 +667,53 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
} }
@Override @Override
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
beginBatch( BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
completeBatch.getBeginBatchMessage(), .addAllTag(tags)
new BeginBatchCallback(completeBatch, callback) .build();
);
batchDigest.update(completeBatch);
return batchDigest.digestAsMessageID();
}
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
// Create worker with redundancy 1 and MAX_RETRIES retries // Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerBeginBatchWorker worker = SingleServerBeginBatchWorker worker =
new SingleServerBeginBatchWorker(meerkatDBs.get(0), beginBatchMessage, MAX_RETRIES); new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
// Submit worker and create callback // Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, new BeginBatchCallback(callback)));
} }
@Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) {
BatchMessage.Builder builder = BatchMessage.newBuilder() @Override
.setSignerId(signerId) public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
.setBatchId(batchId); int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException{
// Cast identifier to usable form
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
// Update batch size
identifier.setLength(startPosition + batchChunkList.size());
// Create a unified callback to aggregate successful posts // Create a unified callback to aggregate successful posts
PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback); PostBatchChunkListCallback listCallback = new PostBatchChunkListCallback(batchChunkList.size(), callback);
// Iterate through data list // Iterate through data list
for (BatchData data : batchDataList) { BatchMessage.Builder builder = BatchMessage.newBuilder()
.setBatchId(identifier.getBatchId().getValue());
for (BatchChunk data : batchChunkList) {
builder.setSerialNum(startPosition).setData(data); builder.setSerialNum(startPosition).setData(data);
// Create worker with redundancy 1 and MAX_RETRIES retries // Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerPostBatchWorker worker = SingleServerPostBatchWorker worker =
new SingleServerPostBatchWorker(meerkatDBs.get(0), builder.build(), MAX_RETRIES); new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES);
// Create worker with redundancy 1 and MAX_RETRIES retries // Create worker with redundancy 1 and MAX_RETRIES retries
scheduleWorker(worker, new RetryCallback<>(worker, listCallback)); scheduleWorker(worker, new RetryCallback<>(worker, listCallback));
@ -486,33 +725,33 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
} }
@Override @Override
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
throws IllegalArgumentException {
postBatchData(signerId, batchId, batchDataList, 0, callback); postBatchData(batchIdentifier, batchChunkList, 0, callback);
} }
@Override @Override
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Crypto.Signature> signatures, FutureCallback<Boolean> callback)
int startPosition, FutureCallback<Boolean> callback) { throws IllegalArgumentException {
postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
} SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
@Override CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { .setBatchId(identifier.getBatchId().getValue())
.setBatchLength(identifier.getLength())
postBatchData(signerId, batchId, batchDataList, 0, callback); .setTimestamp(timestamp)
.addAllSig(signatures)
} .build();
@Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) {
// Create worker with redundancy 1 and MAX_RETRIES retries // Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerCloseBatchWorker worker = SingleServerCloseBatchWorker worker =
new SingleServerCloseBatchWorker(meerkatDBs.get(0), closeBatchMessage, MAX_RETRIES); new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES);
// Submit worker and create callback // Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -523,7 +762,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void getRedundancy(MessageID id, FutureCallback<Float> callback) { public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
// Create worker with no retries // Create worker with no retries
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(meerkatDBs.get(0), id, 1); SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, 1);
// Submit job and create callback // Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -534,7 +773,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) { public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
// Create job with no retries // Create job with no retries
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1); SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, 1);
// Submit job and create callback // Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -542,43 +781,55 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
} }
@Override @Override
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
// Create job with no retries for retrieval of the Bulletin Board Message that defines the batch // Create job with MAX retries for retrieval of the Bulletin Board Message (which may be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder() MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder() .addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG) .setType(FilterType.MSG_ID)
.setTag(BulletinBoardConstants.BATCH_TAG) .setId(msgID.getID())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.TAG)
.setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + batchSpecificationMessage.getBatchId())
.build())
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.SIGNER_ID)
.setId(batchSpecificationMessage.getSignerId())
.build()) .build())
.build(); .build();
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1); BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(msgID)
.setStartPosition(0)
.build();
// Create job with no retries for retrieval of the Batch Data List SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(meerkatDBs.get(0), batchSpecificationMessage, 1);
// Create callback that will combine the two worker products
CompleteBatchReadCallback completeBatchReadCallback = new CompleteBatchReadCallback(callback);
// Submit jobs with wrapped callbacks // Submit jobs with wrapped callbacks
scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, completeBatchReadCallback.asBulletinBoardMessageListFutureCallback())); scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, new CompleteMessageReadCallback(callback)));
scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, completeBatchReadCallback.asBatchDataListFutureCallback()));
}
@Override
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException{
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
}
// Create job with MAX retries for retrieval of the Batch Data List
BatchQuery batchQuery = BatchQuery.newBuilder()
.setMsgID(MessageID.newBuilder()
.setID(stub.getMsg().getMsgId())
.build())
.setStartPosition(0)
.build();
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, new ReadBatchCallback(stub, callback)));
} }
@Override @Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) { public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(meerkatDBs.get(0), syncQuery, MAX_RETRIES); SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(dbAddress, syncQuery, MAX_RETRIES);
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -604,7 +855,7 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
.build()); .build());
// Create job with no retries // Create job with no retries
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterListBuilder.build(), MAX_RETRIES); SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterListBuilder.build(), MAX_RETRIES);
// Submit job and create callback that retries on failure and handles repeated subscription // Submit job and create callback that retries on failure and handles repeated subscription
scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback))); scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback)));
@ -618,8 +869,6 @@ public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient i
@Override @Override
public void close() { public void close() {
super.close();
executorService.shutdown(); executorService.shutdown();
} }

View File

@ -1,255 +1,286 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.workers.multiserver.*; import meerkat.bulletinboard.workers.multiserver.*;
import meerkat.comm.CommunicationException; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardAPI; import meerkat.protobuf.Crypto.Signature;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.Voting.*;
import meerkat.protobuf.Voting.*;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List; import java.util.concurrent.ExecutorService;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeUnit;
/**
/** * Created by Arbel Deutsch Peled on 05-Dec-15.
* Created by Arbel Deutsch Peled on 05-Dec-15. * Thread-based implementation of a Async Bulletin Board Client.
* Thread-based implementation of a Async Bulletin Board Client. * Features:
* Features: * 1. Handles tasks concurrently.
* 1. Handles tasks concurrently. * 2. Retries submitting
* 2. Retries submitting */
*/ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
// Executor service for handling jobs
// Executor service for handling jobs private final static int JOBS_THREAD_NUM = 5;
private final static int JOBS_THREAD_NUM = 5; private ExecutorService executorService;
private ExecutorService executorService;
// Per-server clients
// Per-server clients private List<SingleServerBulletinBoardClient> clients;
private List<SingleServerBulletinBoardClient> clients;
private BulletinBoardDigest batchDigest;
private BatchDigest batchDigest;
private final static int POST_MESSAGE_RETRY_NUM = 3;
private final static int POST_MESSAGE_RETRY_NUM = 3; private final static int READ_MESSAGES_RETRY_NUM = 1;
private final static int READ_MESSAGES_RETRY_NUM = 1; private final static int GET_REDUNDANCY_RETRY_NUM = 1;
private final static int GET_REDUNDANCY_RETRY_NUM = 1;
private final int SERVER_THREADPOOL_SIZE;
private static final int SERVER_THREADPOOL_SIZE = 5; private final long FAIL_DELAY;
private static final long FAIL_DELAY = 5000; private final long SUBSCRIPTION_INTERVAL;
private static final long SUBSCRIPTION_INTERVAL = 10000;
private static final int DEFAULT_SERVER_THREADPOOL_SIZE = 5;
private int minAbsoluteRedundancy; private static final long DEFAULT_FAIL_DELAY = 5000;
private static final long DEFAULT_SUBSCRIPTION_INTERVAL = 10000;
/**
* Stores database locations and initializes the web Client private int minAbsoluteRedundancy;
* Stores the required minimum redundancy.
* Starts the Thread Pool.
* @param clientParams contains the required information public ThreadedBulletinBoardClient(int serverThreadpoolSize, long failDelay, long subscriptionInterval) {
*/ SERVER_THREADPOOL_SIZE = serverThreadpoolSize;
@Override FAIL_DELAY = failDelay;
public void init(BulletinBoardClientParams clientParams) { SUBSCRIPTION_INTERVAL = subscriptionInterval;
}
super.init(clientParams);
public ThreadedBulletinBoardClient() {
batchDigest = new GenericBatchDigest(digest); this(DEFAULT_SERVER_THREADPOOL_SIZE, DEFAULT_FAIL_DELAY, DEFAULT_SUBSCRIPTION_INTERVAL);
}
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount());
/**
executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM); * Stores database locations and initializes the web Client
* Stores the required minimum redundancy.
clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount()); * Starts the Thread Pool.
for (String address : clientParams.getBulletinBoardAddressList()){ * @param clientParams contains the required information
*/
SingleServerBulletinBoardClient client = @Override
new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL); public void init(BulletinBoardClientParams clientParams) {
client.init(BulletinBoardClientParams.newBuilder() super.init(clientParams);
.addBulletinBoardAddress(address)
.build()); batchDigest = new GenericBulletinBoardDigest(digest);
clients.add(client); minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount());
} executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM);
} clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
for (String address : clientParams.getBulletinBoardAddressList()){
/**
* Post message to all DBs SingleServerBulletinBoardClient client =
* Retry failed DBs new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
* @param msg is the message,
* @return the message ID for later retrieval client.init(BulletinBoardClientParams.newBuilder()
*/ .addBulletinBoardAddress(address)
@Override .build());
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
clients.add(client);
// Create job
MultiServerPostMessageWorker worker = }
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
}
// Submit job
executorService.submit(worker); /**
* Post message to all DBs
// Calculate the correct message ID and return it * Retry failed DBs
batchDigest.reset(); * @param msg is the message,
batchDigest.update(msg.getMsg()); * @return the message ID for later retrieval
return batchDigest.digestAsMessageID(); */
@Override
} public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
@Override // Create job
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { MultiServerPostMessageWorker worker =
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
// Create job
MultiServerPostBatchWorker worker = // Submit job
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, completeBatch, POST_MESSAGE_RETRY_NUM, callback); executorService.submit(worker);
// Submit job // Calculate the correct message ID and return it
executorService.submit(worker); batchDigest.reset();
batchDigest.update(msg.getMsg());
// Calculate the correct message ID and return it return batchDigest.digestAsMessageID();
batchDigest.reset();
batchDigest.update(completeBatch); }
return batchDigest.digestAsMessageID();
@Override
} public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
@Override // Create job
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) { MultiServerPostBatchWorker worker =
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, msg, chunkSize, POST_MESSAGE_RETRY_NUM, callback);
// Create job
MultiServerBeginBatchWorker worker = // Submit job
new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, beginBatchMessage, POST_MESSAGE_RETRY_NUM, callback); executorService.submit(worker);
// Submit job // Calculate the correct message ID and return it
executorService.submit(worker); batchDigest.reset();
batchDigest.update(msg);
} return batchDigest.digestAsMessageID();
@Override }
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) { @Override
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchDataList, startPosition);
// Create job
// Create job MultiServerBeginBatchWorker worker =
MultiServerPostBatchDataWorker worker = new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, tags, POST_MESSAGE_RETRY_NUM, callback);
new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
// Submit job
// Submit job executorService.submit(worker);
executorService.submit(worker);
}
}
@Override
@Override public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException {
postBatchData(signerId, batchId, batchDataList, 0, callback); // Cast identifier to usable form
} if (!(batchIdentifier instanceof MultiServerBatchIdentifier)){
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
@Override }
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) { MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) batchIdentifier;
postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback); BatchDataContainer batchDataContainer = new BatchDataContainer(identifier, batchChunkList, startPosition);
} // Create job
MultiServerPostBatchDataWorker worker =
@Override new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
// Submit job
postBatchData(signerId, batchId, batchDataList, 0, callback); executorService.submit(worker);
} }
@Override @Override
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) { public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
throws IllegalArgumentException {
// Create job
MultiServerCloseBatchWorker worker = postBatchData(batchIdentifier, batchChunkList, 0, callback);
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, closeBatchMessage, POST_MESSAGE_RETRY_NUM, callback);
}
// Submit job
executorService.submit(worker); @Override
public void closeBatch(BatchIdentifier payload, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback)
} throws IllegalArgumentException{
/** if (!(payload instanceof MultiServerBatchIdentifier)) {
* Access each database and search for a given message ID throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
* Return the number of databases in which the message was found }
* Only try once per DB
* Ignore communication exceptions in specific databases MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) payload;
*/
@Override // Create job
public void getRedundancy(MessageID id, FutureCallback<Float> callback) { MultiServerCloseBatchWorker worker =
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, identifier, timestamp, signatures, POST_MESSAGE_RETRY_NUM, callback);
// Create job
MultiServerGetRedundancyWorker worker = // Submit job
new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback); executorService.submit(worker);
// Submit job }
executorService.submit(worker);
/**
} * Access each database and search for a given message ID
* Return the number of databases in which the message was found
/** * Only try once per DB
* Go through the DBs and try to retrieve messages according to the specified filter * Ignore communication exceptions in specific databases
* If at the operation is successful for some DB: return the results and stop iterating */
* If no operation is successful: return null (NOT blank list) @Override
*/ public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
@Override
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) { // Create job
MultiServerGetRedundancyWorker worker =
// Create job new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback);
MultiServerReadMessagesWorker worker =
new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback); // Submit job
executorService.submit(worker);
// Submit job
executorService.submit(worker); }
} /**
* Go through the DBs and try to retrieve messages according to the specified filter
@Override * If at the operation is successful for some DB: return the results and stop iterating
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { * If no operation is successful: return null (NOT blank list)
*/
// Create job @Override
MultiServerReadBatchWorker worker = public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
new MultiServerReadBatchWorker(clients, minAbsoluteRedundancy, batchSpecificationMessage, READ_MESSAGES_RETRY_NUM, callback);
// Create job
// Submit job MultiServerReadMessagesWorker worker =
executorService.submit(worker); new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback);
} // Submit job
executorService.submit(worker);
/**
* This method is not supported by this class! }
* This is because it has no meaning when considering more than one server without knowing which server will be contacted
*/ @Override
@Override public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
callback.onFailure(new IllegalAccessError("querySync is not supported by this class")); //Create job
} MultiServerReadMessageWorker worker =
new MultiServerReadMessageWorker(clients, minAbsoluteRedundancy, msgID, READ_MESSAGES_RETRY_NUM, callback);
@Override
public void close() { // Submit job
super.close(); executorService.submit(worker);
try { }
for (SingleServerBulletinBoardClient client : clients){ @Override
client.close(); public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
}
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
executorService.shutdown(); throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
while (! executorService.isShutdown()) { }
executorService.awaitTermination(10, TimeUnit.SECONDS);
} // Create job
} catch (InterruptedException e) { MultiServerReadBatchDataWorker worker =
System.err.println(e.getCause() + " " + e.getMessage()); new MultiServerReadBatchDataWorker(clients, minAbsoluteRedundancy, stub, READ_MESSAGES_RETRY_NUM, callback);
}
} // Submit job
executorService.submit(worker);
}
}
/**
* This method is not supported by this class!
* This is because it has no meaning when considering more than one server without knowing which server will be contacted
*/
@Override
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
callback.onFailure(new IllegalAccessError("querySync is not supported by this class"));
}
@Override
public void close() {
super.close();
try {
for (SingleServerBulletinBoardClient client : clients){
client.close();
}
executorService.shutdown();
while (! executorService.isShutdown()) {
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
} catch (InterruptedException e) {
System.err.println(e.getCause() + " " + e.getMessage());
}
}
}

View File

@ -8,7 +8,6 @@ import meerkat.util.BulletinBoardUtils;
import static meerkat.protobuf.BulletinBoardAPI.FilterType.*; import static meerkat.protobuf.BulletinBoardAPI.FilterType.*;
import java.sql.Time;
import java.util.*; import java.util.*;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -19,20 +18,22 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber { public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber {
protected final Collection<SubscriptionAsyncBulletinBoardClient> clients; protected final Collection<SubscriptionBulletinBoardClient> clients;
protected final BulletinBoardClient localClient; protected final BulletinBoardClient localClient;
protected Iterator<SubscriptionAsyncBulletinBoardClient> clientIterator; protected Iterator<SubscriptionBulletinBoardClient> clientIterator;
protected SubscriptionAsyncBulletinBoardClient currentClient; protected SubscriptionBulletinBoardClient currentClient;
private long lastServerSwitchTime; private long lastServerSwitchTime;
private AtomicBoolean isSyncInProgress; private AtomicBoolean isSyncInProgress;
private Semaphore rescheduleSemaphore; private Semaphore rescheduleSemaphore;
private AtomicBoolean stopped;
private static final Float[] BREAKPOINTS = {0.5f, 0.75f, 0.9f, 0.95f, 0.99f, 0.999f}; private static final Float[] BREAKPOINTS = {0.5f, 0.75f, 0.9f, 0.95f, 0.99f, 0.999f};
public ThreadedBulletinBoardSubscriber(Collection<SubscriptionAsyncBulletinBoardClient> clients, BulletinBoardClient localClient) { public ThreadedBulletinBoardSubscriber(Collection<SubscriptionBulletinBoardClient> clients, BulletinBoardClient localClient) {
this.clients = clients; this.clients = clients;
this.localClient = localClient; this.localClient = localClient;
@ -45,6 +46,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
isSyncInProgress = new AtomicBoolean(false); isSyncInProgress = new AtomicBoolean(false);
rescheduleSemaphore = new Semaphore(1); rescheduleSemaphore = new Semaphore(1);
stopped = new AtomicBoolean(false);
} }
/** /**
@ -132,7 +135,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
//TODO: log //TODO: log
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety if (callback != null)
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
} }
@ -218,7 +222,8 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Propagate result to caller // Propagate result to caller
callback.onSuccess(result); if (callback != null)
callback.onSuccess(result);
// Renew subscription // Renew subscription
@ -245,12 +250,12 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
super(filterList, callback); super(filterList, callback);
} }
@Override @Override
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Propagate result to caller // Propagate result to caller
callback.onSuccess(result); if (callback != null)
callback.onSuccess(result);
} }
@ -268,5 +273,4 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
subscribe(filterList, 0, callback); subscribe(filterList, 0, callback);
} }
} }

View File

@ -1,28 +1,96 @@
package meerkat.bulletinboard.workers.multiserver; package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.MultiServerBatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage; import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.comm.CommunicationException;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
*/ */
public class MultiServerBeginBatchWorker extends MultiServerGenericPostWorker<BeginBatchMessage> { public class MultiServerBeginBatchWorker extends MultiServerWorker<Iterable<String>, BatchIdentifier> {
private BatchIdentifier[] identifiers;
private AtomicInteger remainingServers;
public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BeginBatchMessage payload, int maxRetry, int minServers, Iterable<String> payload, int maxRetry,
FutureCallback<Boolean> futureCallback) { FutureCallback<BatchIdentifier> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback); super(clients, minServers, payload, maxRetry, futureCallback);
identifiers = new BatchIdentifier[clients.size()];
for (int i = 0 ; i < identifiers.length ; i++) {
identifiers[i] = null;
}
remainingServers = new AtomicInteger(clients.size());
}
private class BeginBatchCallback implements FutureCallback<BatchIdentifier> {
private final int clientNum;
public BeginBatchCallback(int clientNum) {
this.clientNum = clientNum;
}
private void finishPost() {
if (remainingServers.decrementAndGet() <= 0){
if (minServers.decrementAndGet() <= 0) {
MultiServerBeginBatchWorker.this.onSuccess(new MultiServerBatchIdentifier(identifiers));
} else {
MultiServerBeginBatchWorker.this.onFailure(new CommunicationException("Could not open batch in enough servers"));
}
}
}
@Override
public void onSuccess(BatchIdentifier result) {
identifiers[clientNum] = result;
finishPost();
}
@Override
public void onFailure(Throwable t) {
finishPost();
}
} }
@Override @Override
protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) { public void onSuccess(BatchIdentifier result) {
client.beginBatch(payload, this); succeed(result);
} }
@Override
public void onFailure(Throwable t) {
fail(t);
}
@Override
public void run() {
int clientNum = 0;
for (SingleServerBulletinBoardClient client : clients){
client.beginBatch(payload, new BeginBatchCallback(clientNum));
clientNum++;
}
}
} }

View File

@ -1,27 +1,83 @@
package meerkat.bulletinboard.workers.multiserver; package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.bulletinboard.BatchDataContainer;
import meerkat.bulletinboard.MultiServerBatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage; import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.Crypto;
import meerkat.protobuf.Crypto.Signature;
import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
*/ */
public class MultiServerCloseBatchWorker extends MultiServerGenericPostWorker<CloseBatchMessage> { public class MultiServerCloseBatchWorker extends MultiServerWorker<MultiServerBatchIdentifier, Boolean> {
private final Timestamp timestamp;
private final Iterable<Crypto.Signature> signatures;
public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, CloseBatchMessage payload, int maxRetry, int minServers, MultiServerBatchIdentifier payload, Timestamp timestamp, Iterable<Crypto.Signature> signatures,
FutureCallback<Boolean> futureCallback) { int maxRetry, FutureCallback<Boolean> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback); super(clients, minServers, payload, maxRetry, futureCallback);
this.timestamp = timestamp;
this.signatures = signatures;
} }
@Override @Override
protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) { public void run() {
client.closeBatch(payload, this);
Iterator<BatchIdentifier> identifierIterator = payload.getIdentifiers().iterator();
// Iterate through client
for (SingleServerBulletinBoardClient client : clients) {
if (identifierIterator.hasNext()) {
// Fetch the batch identifier supplied by the specific client (may be null if batch open failed on client
BatchIdentifier identifier = identifierIterator.next();
if (identifier != null) {
// Post the data with the matching identifier to the client
client.closeBatch(identifier, timestamp, signatures, this);
} else {
// Count servers with no batch identifier as failed
maxFailedServers.decrementAndGet();
}
}
}
}
@Override
public void onSuccess(Boolean result) {
if (minServers.decrementAndGet() <= 0){
succeed(result);
}
}
@Override
public void onFailure(Throwable t) {
if (maxFailedServers.decrementAndGet() <= 0){
fail(t);
}
} }

View File

@ -35,14 +35,9 @@ public abstract class MultiServerGenericPostWorker<T> extends MultiServerWorker<
public void run() { public void run() {
// Iterate through servers // Iterate through servers
for (SingleServerBulletinBoardClient client : clients) {
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
while (clientIterator.hasNext()) {
// Send request to Server // Send request to Server
SingleServerBulletinBoardClient client = clientIterator.next();
doPost(client, payload); doPost(client, payload);
} }

View File

@ -14,7 +14,9 @@ import java.util.List;
*/ */
public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{
private final Iterator<SingleServerBulletinBoardClient> clientIterator; private Iterator<SingleServerBulletinBoardClient> clientIterator;
private String errorString;
public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, IN payload, int maxRetry, int minServers, IN payload, int maxRetry,
@ -22,7 +24,8 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
clientIterator = getClientIterator(); clientIterator = clients.iterator();
errorString = "";
} }
@ -46,7 +49,7 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
doRead(payload, client); doRead(payload, client);
} else { } else {
fail(new CommunicationException("Could not contact any server")); fail(new CommunicationException("Could not contact any server. Errors follow:\n" + errorString));
} }
} }
@ -58,6 +61,8 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
//TODO: log
errorString += t.getCause() + " " + t.getMessage() + "\n";
run(); // Retry with next server run(); // Retry with next server
} }

View File

@ -36,13 +36,8 @@ public class MultiServerGetRedundancyWorker extends MultiServerWorker<MessageID,
*/ */
public void run(){ public void run(){
Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator();
// Iterate through clients // Iterate through clients
for (SingleServerBulletinBoardClient client : clients) {
while (clientIterator.hasNext()) {
SingleServerBulletinBoardClient client = clientIterator.next();
// Send request to client // Send request to client
client.getRedundancy(payload,this); client.getRedundancy(payload,this);

View File

@ -1,15 +1,18 @@
package meerkat.bulletinboard.workers.multiserver; package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.bulletinboard.BatchDataContainer; import meerkat.bulletinboard.BatchDataContainer;
import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
*/ */
public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker<BatchDataContainer> { public class MultiServerPostBatchDataWorker extends MultiServerWorker<BatchDataContainer, Boolean> {
public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BatchDataContainer payload, int maxRetry, int minServers, BatchDataContainer payload, int maxRetry,
@ -20,9 +23,50 @@ public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker
} }
@Override @Override
protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) { public void run() {
client.postBatchData(payload.signerId, payload.batchId, payload.batchDataList, payload.startPosition, this);
Iterator<BatchIdentifier> identifierIterator = payload.batchId.getIdentifiers().iterator();
// Iterate through client
for (SingleServerBulletinBoardClient client : clients) {
if (identifierIterator.hasNext()) {
// Fetch the batch identifier supplied by the specific client (may be null if batch open failed on client
BatchIdentifier identifier = identifierIterator.next();
if (identifier != null) {
// Post the data with the matching identifier to the client
client.postBatchData(identifier, payload.batchChunkList, payload.startPosition, this);
} else {
// Count servers with no batch identifier as failed
maxFailedServers.decrementAndGet();
}
}
}
} }
@Override
public void onSuccess(Boolean result) {
if (minServers.decrementAndGet() <= 0){
succeed(result);
}
}
@Override
public void onFailure(Throwable t) {
if (maxFailedServers.decrementAndGet() <= 0){
fail(t);
}
}
} }

View File

@ -1,27 +1,33 @@
package meerkat.bulletinboard.workers.multiserver; package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import java.util.List; import java.util.List;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
*/ */
public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<CompleteBatch> { public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<BulletinBoardMessage> {
private final int chunkSize;
public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, CompleteBatch payload, int maxRetry, int minServers, BulletinBoardMessage payload, int chunkSize, int maxRetry,
FutureCallback<Boolean> futureCallback) { FutureCallback<Boolean> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback); super(clients, minServers, payload, maxRetry, futureCallback);
this.chunkSize = chunkSize;
} }
@Override @Override
protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) { protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) {
client.postBatch(payload, this);
client.postAsBatch(payload, chunkSize, this);
} }

View File

@ -0,0 +1,29 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadBatchDataWorker extends MultiServerGenericReadWorker<BulletinBoardMessage, BulletinBoardMessage> {
public MultiServerReadBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BulletinBoardMessage payload, int maxRetry,
FutureCallback<BulletinBoardMessage> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(BulletinBoardMessage payload, SingleServerBulletinBoardClient client) {
client.readBatchData(payload, this);
}
}

View File

@ -1,30 +0,0 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BatchSpecificationMessage;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadBatchWorker extends MultiServerGenericReadWorker<BatchSpecificationMessage, CompleteBatch> {
public MultiServerReadBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BatchSpecificationMessage payload, int maxRetry,
FutureCallback<CompleteBatch> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(BatchSpecificationMessage payload, SingleServerBulletinBoardClient client) {
client.readBatch(payload, this);
}
}

View File

@ -0,0 +1,30 @@
package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.MessageID;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
*/
public class MultiServerReadMessageWorker extends MultiServerGenericReadWorker<MessageID, BulletinBoardMessage> {
public MultiServerReadMessageWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, MessageID payload, int maxRetry,
FutureCallback<BulletinBoardMessage> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback);
}
@Override
protected void doRead(MessageID payload, SingleServerBulletinBoardClient client) {
client.readMessage(payload, this);
}
}

View File

@ -1,17 +1,53 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage; import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.BEGIN_BATCH_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.BEGIN_BATCH_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
* Tries to contact server once and perform a post operation * Tries to contact server once and perform a post operation
*/ */
public class SingleServerBeginBatchWorker extends SingleServerGenericPostWorker<BeginBatchMessage> { public class SingleServerBeginBatchWorker extends SingleServerWorker<BeginBatchMessage,Int64Value> {
public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) { public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) {
super(serverAddress, BEGIN_BATCH_PATH, payload, maxRetry); super(serverAddress, payload, maxRetry);
} }
@Override
public Int64Value call() throws Exception {
Client client = clientLocal.get();
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(BEGIN_BATCH_PATH);
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(
Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
try {
Int64Value result = response.readEntity(Int64Value.class);
return result;
} catch (ProcessingException | IllegalStateException e) {
// Post to this server failed
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
} catch (Exception e) {
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
}
finally {
response.close();
}
}
} }

View File

@ -0,0 +1,55 @@
package meerkat.bulletinboard.workers.singleserver;
import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.SyncQuery;
import meerkat.protobuf.BulletinBoardAPI.GenerateSyncQueryParams;
import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.GENERATE_SYNC_QUERY_PATH;
/**
* Created by Arbel Deutsch Peled on 27-Dec-15.
* Tries to contact server once and perform a Sync Query Generation operation
*/
public class SingleServerGenerateSyncQueryWorker extends SingleServerWorker<GenerateSyncQueryParams,SyncQuery> {
public SingleServerGenerateSyncQueryWorker(String serverAddress, GenerateSyncQueryParams payload, int maxRetry) {
super(serverAddress, payload, maxRetry);
}
@Override
public SyncQuery call() throws Exception {
Client client = clientLocal.get();
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
try {
SyncQuery result = response.readEntity(SyncQuery.class);
return result;
} catch (ProcessingException | IllegalStateException e) {
// Post to this server failed
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
} catch (Exception e) {
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
}
finally {
response.close();
}
}
}

View File

@ -6,7 +6,6 @@ import meerkat.comm.MessageInputStream;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
@ -14,7 +13,6 @@ import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;

View File

@ -1,35 +1,28 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.CompleteBatch;
import meerkat.bulletinboard.SingleServerWorker; import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.comm.MessageInputStream; import meerkat.comm.MessageInputStream;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.List; import java.util.List;
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH;
import static meerkat.bulletinboard.BulletinBoardConstants.BATCH_ID_TAG_PREFIX;
/** /**
* Created by Arbel Deutsch Peled on 27-Dec-15. * Created by Arbel Deutsch Peled on 27-Dec-15.
*/ */
public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecificationMessage, List<BatchData>> { public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, List<BatchChunk>> {
public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) { public SingleServerReadBatchWorker(String serverAddress, BatchQuery payload, int maxRetry) {
super(serverAddress, payload, maxRetry); super(serverAddress, payload, maxRetry);
} }
@ -39,7 +32,7 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecifi
* @return the complete batch as read from the server * @return the complete batch as read from the server
* @throws CommunicationException if the server's response is invalid * @throws CommunicationException if the server's response is invalid
*/ */
public List<BatchData> call() throws CommunicationException{ public List<BatchChunk> call() throws CommunicationException{
Client client = clientLocal.get(); Client client = clientLocal.get();
@ -50,11 +43,11 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecifi
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_BATCH_PATH); webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_BATCH_PATH);
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class); InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
MessageInputStream<BatchData> inputStream = null; MessageInputStream<BatchChunk> inputStream = null;
try { try {
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class); inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class);
return inputStream.asList(); return inputStream.asList();

View File

@ -0,0 +1,318 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.*;
import com.google.protobuf.Timestamp;
import static meerkat.bulletinboard.BulletinBoardSynchronizer.SyncStatus;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator;
import org.junit.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;
/**
* Created by Arbel on 6/1/2016.
*/
public class BulletinBoardSynchronizerTest {
private static final String REMOTE_SERVER_ADDRESS = "remoteDB";
private static final String LOCAL_SERVER_ADDRESS = "localDB";
private static int testCount;
private static final int THREAD_NUM = 3;
private static final int SUBSCRIPTION_INTERVAL = 1000;
private static final int SYNC_SLEEP_INTERVAL = 500;
private static final int SYNC_WAIT_CAP = 1000;
private DeletableSubscriptionBulletinBoardClient localClient;
private AsyncBulletinBoardClient remoteClient;
private BulletinBoardSynchronizer synchronizer;
private static BulletinBoardMessageGenerator messageGenerator;
private static BulletinBoardMessageComparator messageComparator;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
private static String KEYFILE_PASSWORD1 = "secret";
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
private static BulletinBoardSignature[] signers;
private static ByteString[] signerIDs;
private Semaphore semaphore;
private List<Throwable> thrown;
@BeforeClass
public static void build() {
messageGenerator = new BulletinBoardMessageGenerator(new Random(0));
messageComparator = new BulletinBoardMessageComparator();
signers = new BulletinBoardSignature[1];
signerIDs = new ByteString[1];
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signerIDs[0] = signers[0].getSignerID();
InputStream keyStream = BulletinBoardSynchronizerTest.class.getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray();
try {
KeyStore.Builder keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
signers[0].loadSigningCertificate(keyStoreBuilder);
signers[0].loadVerificationCertificates(BulletinBoardSynchronizerTest.class.getResourceAsStream(CERT1_PEM_EXAMPLE));
} catch (IOException e) {
System.err.println("Failed reading from signature file " + e.getMessage());
fail("Failed reading from signature file " + e.getMessage());
} catch (CertificateException e) {
System.err.println("Failed reading certificate " + e.getMessage());
fail("Failed reading certificate " + e.getMessage());
} catch (KeyStoreException e) {
System.err.println("Failed reading keystore " + e.getMessage());
fail("Failed reading keystore " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
System.err.println("Couldn't find signing algorithm " + e.getMessage());
fail("Couldn't find signing algorithm " + e.getMessage());
} catch (UnrecoverableKeyException e) {
System.err.println("Couldn't find signing key " + e.getMessage());
fail("Couldn't find signing key " + e.getMessage());
}
signerIDs[0] = signers[0].getSignerID();
testCount = 0;
}
@Before
public void init() throws CommunicationException {
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_SERVER_ADDRESS + testCount));
remoteServer.init();
remoteClient = new LocalBulletinBoardClient(
remoteServer,
THREAD_NUM,
SUBSCRIPTION_INTERVAL);
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_SERVER_ADDRESS + testCount));
localServer.init();
localClient = new LocalBulletinBoardClient(
localServer,
THREAD_NUM,
SUBSCRIPTION_INTERVAL);
synchronizer = new SimpleBulletinBoardSynchronizer(SYNC_SLEEP_INTERVAL, SYNC_WAIT_CAP);
synchronizer.init(localClient, remoteClient);
semaphore = new Semaphore(0);
thrown = new LinkedList<>();
testCount++;
}
private class SyncStatusCallback implements FutureCallback<SyncStatus> {
private final SyncStatus statusToWaitFor;
private AtomicBoolean stillWaiting;
public SyncStatusCallback(SyncStatus statusToWaitFor) {
this.statusToWaitFor = statusToWaitFor;
stillWaiting = new AtomicBoolean(true);
}
@Override
public void onSuccess(SyncStatus result) {
if (result == statusToWaitFor && stillWaiting.compareAndSet(true, false)){
semaphore.release();
}
}
@Override
public void onFailure(Throwable t) {
thrown.add(t);
if (stillWaiting.compareAndSet(true,false)) {
semaphore.release();
}
}
}
private class MessageCountCallback implements FutureCallback<Integer> {
private int[] expectedCounts;
private int currentIteration;
public MessageCountCallback(int[] expectedCounts) {
this.expectedCounts = expectedCounts;
this.currentIteration = 0;
}
@Override
public void onSuccess(Integer result) {
if (currentIteration < expectedCounts.length){
if (result != expectedCounts[currentIteration]){
onFailure(new AssertionError("Wrong message count. Expected " + expectedCounts[currentIteration] + " but received " + result));
currentIteration = expectedCounts.length;
return;
}
}
currentIteration++;
if (currentIteration == expectedCounts.length)
semaphore.release();
}
@Override
public void onFailure(Throwable t) {
thrown.add(t);
semaphore.release();
}
}
@Test
public void testSync() throws SignatureException, CommunicationException, InterruptedException {
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(15252162)
.setNanos(85914)
.build();
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
MessageID msgID = localClient.postMessage(msg);
timestamp = Timestamp.newBuilder()
.setSeconds(51511653)
.setNanos(3625)
.build();
BulletinBoardMessage batchMessage = messageGenerator.generateRandomMessage(signers,timestamp, 100, 10);
MessageID batchMsgID = localClient.postAsBatch(batchMessage, 10);
BulletinBoardMessage test = localClient.readMessage(batchMsgID);
BulletinBoardMessage stub = localClient.readMessages(MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(batchMsgID.getID())
.build())
.build()).get(0);
BulletinBoardMessage test2 = localClient.readBatchData(stub);
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SYNCHRONIZED));
int[] expectedCounts = {2,0};
synchronizer.subscribeToRemainingMessagesCount(new MessageCountCallback(expectedCounts));
Thread syncThread = new Thread(synchronizer);
syncThread.start();
if (!semaphore.tryAcquire(2, 4000, TimeUnit.MILLISECONDS)) {
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
}
synchronizer.stop();
syncThread.join();
if (thrown.size() > 0) {
for (Throwable t : thrown)
System.err.println(t.getMessage());
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
}
List<BulletinBoardMessage> msgList = remoteClient.readMessages(MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID)
.setId(msgID.getID())
.build())
.build());
assertThat("Wrong number of messages returned.", msgList.size() == 1);
assertThat("Returned message is not equal to original one", messageComparator.compare(msgList.get(0),msg) == 0);
BulletinBoardMessage returnedBatchMsg = remoteClient.readMessage(batchMsgID);
assertThat("Returned batch does not equal original one.", messageComparator.compare(returnedBatchMsg, batchMessage) == 0);
}
@Test
public void testServerError() throws SignatureException, CommunicationException, InterruptedException {
Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(945736256)
.setNanos(276788)
.build();
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
remoteClient.close();
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SERVER_ERROR));
localClient.postMessage(msg);
Thread thread = new Thread(synchronizer);
thread.start();
if (!semaphore.tryAcquire(4000, TimeUnit.MILLISECONDS)) {
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
}
synchronizer.stop();
thread.join();
}
@After
public void close() {
if (thrown.size() > 0) {
for (Throwable t : thrown) {
System.err.println(t.getMessage());
}
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
}
synchronizer.stop();
localClient.close();
remoteClient.close();
}
}

View File

@ -0,0 +1,111 @@
package meerkat.bulletinboard;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.security.SignatureException;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Arbel on 6/27/2016.
*/
public class CachedBulletinBoardClientTest {
private static final int THREAD_NUM = 3;
private static final String LOCAL_DB_NAME = "localDB";
private static final String REMOTE_DB_NAME = "remoteDB";
private static final String QUEUE_DB_NAME = "queueDB";
private static final int SUBSRCIPTION_DELAY = 500;
private static final int SYNC_DELAY = 500;
// Testers
private CachedBulletinBoardClient cachedClient;
private GenericBulletinBoardClientTester clientTest;
private GenericSubscriptionClientTester subscriptionTester;
public CachedBulletinBoardClientTest() throws CommunicationException {
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_DB_NAME));
localServer.init();
LocalBulletinBoardClient localClient = new LocalBulletinBoardClient(localServer, THREAD_NUM, SUBSRCIPTION_DELAY);
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_DB_NAME));
remoteServer.init();
LocalBulletinBoardClient remoteClient = new LocalBulletinBoardClient(remoteServer, THREAD_NUM, SUBSRCIPTION_DELAY);
DeletableBulletinBoardServer queueServer = new BulletinBoardSQLServer(new H2QueryProvider(QUEUE_DB_NAME));
queueServer.init();
LocalBulletinBoardClient queueClient = new LocalBulletinBoardClient(queueServer, THREAD_NUM, SUBSRCIPTION_DELAY);
List<SubscriptionBulletinBoardClient> clientList = new LinkedList<>();
clientList.add(remoteClient);
BulletinBoardSubscriber subscriber = new ThreadedBulletinBoardSubscriber(clientList, localClient);
cachedClient = new CachedBulletinBoardClient(localClient, remoteClient, subscriber, queueClient, SYNC_DELAY, SYNC_DELAY);
subscriptionTester = new GenericSubscriptionClientTester(cachedClient);
clientTest = new GenericBulletinBoardClientTester(cachedClient, 87351);
}
// Test methods
/**
* Takes care of initializing the client and the test resources
*/
@Before
public void init(){
clientTest.init();
}
/**
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
*/
@After
public void close() {
cachedClient.close();
clientTest.close();
}
@Test
public void testPost() {
clientTest.testPost();
}
@Test
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost();
}
@Test
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testCompleteBatchPost();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
// subscriptionTester.init();
// subscriptionTester.subscriptionTest();
// subscriptionTester.close();
}
}

View File

@ -5,10 +5,13 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.Timestamp; import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.crypto.concrete.ECDSASignature; import meerkat.crypto.concrete.ECDSASignature;
import meerkat.crypto.concrete.SHA256Digest;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto; import meerkat.protobuf.Crypto;
import meerkat.util.BulletinBoardMessageComparator; import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator; import meerkat.util.BulletinBoardMessageGenerator;
import meerkat.util.BulletinBoardUtils;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -28,7 +31,7 @@ public class GenericBulletinBoardClientTester {
// Signature resources // Signature resources
private GenericBatchDigitalSignature signers[]; private BulletinBoardSignature signers[];
private ByteString[] signerIDs; private ByteString[] signerIDs;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
@ -45,28 +48,29 @@ public class GenericBulletinBoardClientTester {
private AsyncBulletinBoardClient bulletinBoardClient; private AsyncBulletinBoardClient bulletinBoardClient;
private PostCallback postCallback; private PostCallback postCallback;
private PostCallback failPostCallback = new PostCallback(true,false);
private RedundancyCallback redundancyCallback; private RedundancyCallback redundancyCallback;
private ReadCallback readCallback; private ReadCallback readCallback;
private ReadBatchCallback readBatchCallback;
// Sync and misc // Sync and misc
private Semaphore jobSemaphore; private Semaphore jobSemaphore;
private Vector<Throwable> thrown; private Vector<Throwable> thrown;
private Random random; private Random random;
private BulletinBoardMessageGenerator generator;
private BulletinBoardDigest digest;
// Constructor // Constructor
public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient){ public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient, int seed){
this.bulletinBoardClient = bulletinBoardClient; this.bulletinBoardClient = bulletinBoardClient;
signers = new GenericBatchDigitalSignature[2]; signers = new GenericBulletinBoardSignature[2];
signerIDs = new ByteString[signers.length]; signerIDs = new ByteString[signers.length];
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature()); signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature()); signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray(); char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -108,6 +112,10 @@ public class GenericBulletinBoardClientTester {
fail("Couldn't find signing key " + e.getMessage()); fail("Couldn't find signing key " + e.getMessage());
} }
this.random = new Random(seed);
this.generator = new BulletinBoardMessageGenerator(random);
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
} }
// Callback definitions // Callback definitions
@ -138,16 +146,21 @@ public class GenericBulletinBoardClientTester {
@Override @Override
public void onSuccess(Boolean msg) { public void onSuccess(Boolean msg) {
System.err.println("Post operation completed"); System.err.println("Post operation completed");
jobSemaphore.release();
//TODO: Change Assert mechanism to exception one
if (isAssert) { if (isAssert) {
if (assertValue) { if (assertValue && !msg) {
assertThat("Post operation failed", msg, is(Boolean.TRUE)); genericHandleFailure(new AssertionError("Post operation failed"));
} else if (!assertValue && msg){
genericHandleFailure(new AssertionError("Post operation succeeded unexpectedly"));
} else { } else {
assertThat("Post operation succeeded unexpectedly", msg, is(Boolean.FALSE)); jobSemaphore.release();
} }
} else {
jobSemaphore.release();
} }
} }
@Override @Override
@ -210,21 +223,24 @@ public class GenericBulletinBoardClientTester {
} }
} }
private class ReadBatchCallback implements FutureCallback<CompleteBatch> { private class ReadBatchCallback implements FutureCallback<BulletinBoardMessage>{
private CompleteBatch expectedBatch; private BulletinBoardMessage expectedMsg;
public ReadBatchCallback(CompleteBatch expectedBatch) { public ReadBatchCallback(BulletinBoardMessage expectedMsg) {
this.expectedBatch = expectedBatch; this.expectedMsg = expectedMsg;
} }
@Override @Override
public void onSuccess(CompleteBatch batch) { public void onSuccess(BulletinBoardMessage msg) {
System.err.println(batch); BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
jobSemaphore.release();
assertThat("Batch returned is incorrect", batch, is(equalTo(expectedBatch))); if (msgComparator.compare(msg, expectedMsg) != 0) {
genericHandleFailure(new AssertionError("Batch read returned different message.\nExpected:" + expectedMsg + "\nRecieved:" + msg + "\n"));
} else {
jobSemaphore.release();
}
} }
@ -234,59 +250,6 @@ public class GenericBulletinBoardClientTester {
} }
} }
// Randomness generators
private byte randomByte(){
return (byte) random.nextInt();
}
private byte[] randomByteArray(int length) {
byte[] randomBytes = new byte[length];
for (int i = 0; i < length ; i++){
randomBytes[i] = randomByte();
}
return randomBytes;
}
private CompleteBatch createRandomBatch(int signer, int batchId, int length) throws SignatureException {
CompleteBatch completeBatch = new CompleteBatch();
// Create data
completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
.setSignerId(signerIDs[signer])
.setBatchId(batchId)
.addTag("Test")
.build());
for (int i = 0 ; i < length ; i++){
BatchData batchData = BatchData.newBuilder()
.setData(ByteString.copyFrom(randomByteArray(i)))
.build();
completeBatch.appendBatchData(batchData);
}
completeBatch.setTimestamp(Timestamp.newBuilder()
.setSeconds(Math.abs(90))
.setNanos(50)
.build());
signers[signer].updateContent(completeBatch);
completeBatch.setSignature(signers[signer].sign());
return completeBatch;
}
// Test methods // Test methods
/** /**
@ -311,7 +274,13 @@ public class GenericBulletinBoardClientTester {
public void close() { public void close() {
if (thrown.size() > 0) { if (thrown.size() > 0) {
for (Throwable t : thrown){
System.err.println(t.getMessage());
}
assert false; assert false;
} }
} }
@ -319,7 +288,7 @@ public class GenericBulletinBoardClientTester {
/** /**
* Tests the standard post, redundancy and read methods * Tests the standard post, redundancy and read methods
*/ */
public void postTest() { public void testPost() {
byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4}; byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14}; byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
@ -395,59 +364,69 @@ public class GenericBulletinBoardClientTester {
/** /**
* Tests posting a batch by parts * Tests posting a batch by parts
* Also tests not being able to post to a closed batch
* @throws CommunicationException, SignatureException, InterruptedException * @throws CommunicationException, SignatureException, InterruptedException
*/ */
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
final int SIGNER = 1;
final int BATCH_ID = 100;
final int BATCH_LENGTH = 100; final int BATCH_LENGTH = 100;
final int CHUNK_SIZE = 10;
final int TAG_NUM = 10;
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH); final Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(141515)
.setNanos(859018)
.build();
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
// Begin batch // Begin batch
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), postCallback); bulletinBoardClient.beginBatch(msg.getMsg().getTagList(), new FutureCallback<BatchIdentifier>() {
@Override
public void onSuccess(final BatchIdentifier identifier) {
bulletinBoardClient.postBatchData(identifier, BulletinBoardUtils.breakToBatch(msg, CHUNK_SIZE), new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
bulletinBoardClient.closeBatch(identifier, msg.getMsg().getTimestamp(), msg.getSigList(), new FutureCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
jobSemaphore.release();
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
}
@Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
jobSemaphore.acquire(); jobSemaphore.acquire();
// Post data digest.reset();
digest.update(msg);
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), postCallback); bulletinBoardClient.readMessage(digest.digestAsMessageID(), new ReadBatchCallback(msg));
jobSemaphore.acquire();
// Close batch
CloseBatchMessage closeBatchMessage = completeBatch.getCloseBatchMessage();
bulletinBoardClient.closeBatch(closeBatchMessage, postCallback);
jobSemaphore.acquire();
// Attempt to open batch again
bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), failPostCallback);
// Attempt to add batch data
bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), failPostCallback);
jobSemaphore.acquire(2);
// Read batch data
BatchSpecificationMessage batchSpecificationMessage =
BatchSpecificationMessage.newBuilder()
.setSignerId(signerIDs[SIGNER])
.setBatchId(BATCH_ID)
.setStartPosition(0)
.build();
readBatchCallback = new ReadBatchCallback(completeBatch);
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
jobSemaphore.acquire(); jobSemaphore.acquire();
@ -455,62 +434,61 @@ public class GenericBulletinBoardClientTester {
/** /**
* Posts a complete batch message * Posts a complete batch message
* Checks reading of the message * Checks reading of the message in two parts
* @throws CommunicationException, SignatureException, InterruptedException * @throws CommunicationException, SignatureException, InterruptedException
*/ */
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
final int SIGNER = 0; final int BATCH_LENGTH = 100;
final int BATCH_ID = 101; final int CHUNK_SIZE = 99;
final int BATCH_LENGTH = 50; final int TAG_NUM = 8;
final Timestamp timestamp = Timestamp.newBuilder()
.setSeconds(7776151)
.setNanos(252616)
.build();
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
// Post batch // Post batch
CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH); MessageID msgID = bulletinBoardClient.postAsBatch(msg, CHUNK_SIZE, postCallback);
bulletinBoardClient.postBatch(completeBatch,postCallback);
jobSemaphore.acquire(); jobSemaphore.acquire();
// Read batch // Read batch
BatchSpecificationMessage batchSpecificationMessage = MessageFilterList filterList = MessageFilterList.newBuilder()
BatchSpecificationMessage.newBuilder() .addFilter(MessageFilter.newBuilder()
.setSignerId(signerIDs[SIGNER]) .setType(FilterType.MSG_ID)
.setBatchId(BATCH_ID) .setId(msgID.getID())
.setStartPosition(0) .build())
.build(); .build();
readBatchCallback = new ReadBatchCallback(completeBatch); bulletinBoardClient.readMessages(filterList, new FutureCallback<List<BulletinBoardMessage>>() {
bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback); @Override
public void onSuccess(List<BulletinBoardMessage> msgList) {
jobSemaphore.acquire(); if (msgList.size() != 1) {
} genericHandleFailure(new AssertionError("Wrong number of stubs returned. Expected: 1; Found: " + msgList.size()));
/** } else {
* Tests that an unopened batch cannot be closed
* @throws CommunicationException, InterruptedException
*/
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
final int NON_EXISTENT_BATCH_ID = 999; BulletinBoardMessage retrievedMsg = msgList.get(0);
bulletinBoardClient.readBatchData(retrievedMsg, new ReadBatchCallback(msg));
CloseBatchMessage closeBatchMessage = }
CloseBatchMessage.newBuilder()
.setBatchId(NON_EXISTENT_BATCH_ID)
.setBatchLength(1)
.setSig(Crypto.Signature.getDefaultInstance())
.setTimestamp(Timestamp.newBuilder()
.setSeconds(9)
.setNanos(12)
.build())
.build();
// Try to stop the (unopened) batch; }
bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback); @Override
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
jobSemaphore.acquire(); jobSemaphore.acquire();

View File

@ -16,9 +16,6 @@ import java.security.cert.CertificateException;
import java.util.*; import java.util.*;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
@ -26,7 +23,7 @@ import static org.junit.Assert.fail;
*/ */
public class GenericSubscriptionClientTester { public class GenericSubscriptionClientTester {
private GenericBatchDigitalSignature signers[]; private BulletinBoardSignature signers[];
private ByteString[] signerIDs; private ByteString[] signerIDs;
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
@ -38,7 +35,7 @@ public class GenericSubscriptionClientTester {
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt";
private SubscriptionAsyncBulletinBoardClient bulletinBoardClient; private SubscriptionBulletinBoardClient bulletinBoardClient;
private Random random; private Random random;
private BulletinBoardMessageGenerator generator; private BulletinBoardMessageGenerator generator;
@ -46,14 +43,14 @@ public class GenericSubscriptionClientTester {
private Semaphore jobSemaphore; private Semaphore jobSemaphore;
private Vector<Throwable> thrown; private Vector<Throwable> thrown;
public GenericSubscriptionClientTester(SubscriptionAsyncBulletinBoardClient bulletinBoardClient){ public GenericSubscriptionClientTester(SubscriptionBulletinBoardClient bulletinBoardClient){
this.bulletinBoardClient = bulletinBoardClient; this.bulletinBoardClient = bulletinBoardClient;
signers = new GenericBatchDigitalSignature[2]; signers = new BulletinBoardSignature[2];
signerIDs = new ByteString[signers.length]; signerIDs = new ByteString[signers.length];
signers[0] = new GenericBatchDigitalSignature(new ECDSASignature()); signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
signers[1] = new GenericBatchDigitalSignature(new ECDSASignature()); signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray(); char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -181,17 +178,15 @@ public class GenericSubscriptionClientTester {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
System.err.println(t.getCause() + " " + t.getMessage()); System.err.println(t.getCause() + " " + t.getMessage());
thrown.add(t); thrown.add(t);
jobSemaphore.release(expectedMessages.size()); jobSemaphore.release();
stage = expectedMessages.size(); stage = expectedMessages.size();
} }
} }
public void subscriptionTest() throws SignatureException, CommunicationException { public void subscriptionTest() throws SignatureException, CommunicationException {
final int FIRST_POST_ID = 201;
final int SECOND_POST_ID = 202;
final String COMMON_TAG = "SUBSCRIPTION_TEST"; final String COMMON_TAG = "SUBSCRIPTION_TEST";
List<String> tags = new LinkedList<>(); List<String> tags = new LinkedList<>();
tags.add(COMMON_TAG); tags.add(COMMON_TAG);
@ -207,9 +202,9 @@ public class GenericSubscriptionClientTester {
.build(); .build();
List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3); List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3);
expectedMessages.add(new LinkedList<BulletinBoardMessage>()); expectedMessages.add(new LinkedList<>());
expectedMessages.add(new LinkedList<BulletinBoardMessage>()); expectedMessages.add(new LinkedList<>());
expectedMessages.add(new LinkedList<BulletinBoardMessage>()); expectedMessages.add(new LinkedList<>());
expectedMessages.get(0).add(msg1); expectedMessages.get(0).add(msg1);
expectedMessages.get(2).add(msg3); expectedMessages.get(2).add(msg3);

View File

@ -7,12 +7,6 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.security.SignatureException; import java.security.SignatureException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import static org.junit.Assert.fail;
/** /**
* Created by Arbel Deutsch Peled on 05-Dec-15. * Created by Arbel Deutsch Peled on 05-Dec-15.
@ -30,30 +24,14 @@ public class LocalBulletinBoardClientTest {
public LocalBulletinBoardClientTest() throws CommunicationException { public LocalBulletinBoardClientTest() throws CommunicationException {
H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME) ; H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME);
try { DeletableBulletinBoardServer server = new BulletinBoardSQLServer(queryProvider);
server.init();
Connection conn = queryProvider.getDataSource().getConnection();
Statement stmt = conn.createStatement();
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands();
for (String deletionQuery : deletionQueries) {
stmt.execute(deletionQuery);
}
} catch (SQLException e) {
System.err.println(e.getMessage());
throw new CommunicationException(e.getCause() + " " + e.getMessage());
}
BulletinBoardServer server = new BulletinBoardSQLServer(queryProvider);
server.init(DB_NAME);
LocalBulletinBoardClient client = new LocalBulletinBoardClient(server, THREAD_NUM, SUBSRCIPTION_DELAY); LocalBulletinBoardClient client = new LocalBulletinBoardClient(server, THREAD_NUM, SUBSRCIPTION_DELAY);
subscriptionTester = new GenericSubscriptionClientTester(client); subscriptionTester = new GenericSubscriptionClientTester(client);
clientTest = new GenericBulletinBoardClientTester(client); clientTest = new GenericBulletinBoardClientTester(client, 98354);
} }
@ -81,9 +59,9 @@ public class LocalBulletinBoardClientTest {
} }
@Test @Test
public void postTest() { public void testPost() {
clientTest.postTest(); clientTest.testPost();
} }
@ -100,15 +78,9 @@ public class LocalBulletinBoardClientTest {
} }
@Test
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
@Test @Test
public void testSubscription() throws SignatureException, CommunicationException { public void testSubscription() throws SignatureException, CommunicationException {
subscriptionTester.init(); subscriptionTester.init();
subscriptionTester.subscriptionTest(); subscriptionTester.subscriptionTest();
subscriptionTester.close(); subscriptionTester.close();

View File

@ -0,0 +1,104 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.Voting.BulletinBoardClientParams;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.security.SignatureException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15.
*/
public class SingleServerBulletinBoardClientIntegrationTest {
// Server data
private static final String PROP_GETTY_URL = "gretty.httpBaseURI";
private static final String DEFAULT_BASE_URL = "http://localhost:8081";
private static final String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
private static final int THREAD_NUM = 3;
private static final long FAIL_DELAY = 3000;
private static final long SUBSCRIPTION_INTERVAL = 500;
// Testers
private GenericBulletinBoardClientTester clientTest;
private GenericSubscriptionClientTester subscriptionTester;
public SingleServerBulletinBoardClientIntegrationTest(){
SingleServerBulletinBoardClient client = new SingleServerBulletinBoardClient(THREAD_NUM, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
List<String> testDB = new LinkedList<>();
testDB.add(BASE_URL);
client.init(BulletinBoardClientParams.newBuilder()
.addAllBulletinBoardAddress(testDB)
.setMinRedundancy((float) 1.0)
.build());
clientTest = new GenericBulletinBoardClientTester(client, 981541);
subscriptionTester = new GenericSubscriptionClientTester(client);
}
// Test methods
/**
* Takes care of initializing the client and the test resources
*/
@Before
public void init(){
clientTest.init();
}
/**
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
*/
@After
public void close() {
clientTest.close();
}
@Test
public void testPost() {
clientTest.testPost();
}
@Test
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost();
}
@Test
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testCompleteBatchPost();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
subscriptionTester.init();
subscriptionTester.subscriptionTest();
subscriptionTester.close();
}
}

View File

@ -28,7 +28,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
public ThreadedBulletinBoardClientIntegrationTest(){ public ThreadedBulletinBoardClientIntegrationTest(){
ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient(); ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient(3,0,500);
List<String> testDB = new LinkedList<>(); List<String> testDB = new LinkedList<>();
testDB.add(BASE_URL); testDB.add(BASE_URL);
@ -38,7 +38,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
.setMinRedundancy((float) 1.0) .setMinRedundancy((float) 1.0)
.build()); .build());
clientTest = new GenericBulletinBoardClientTester(client); clientTest = new GenericBulletinBoardClientTester(client, 52351);
} }
@ -66,9 +66,9 @@ public class ThreadedBulletinBoardClientIntegrationTest {
} }
@Test @Test
public void postTest() { public void testPost() {
clientTest.postTest(); clientTest.testPost();
} }
@ -76,6 +76,7 @@ public class ThreadedBulletinBoardClientIntegrationTest {
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost(); clientTest.testBatchPost();
} }
@Test @Test
@ -85,11 +86,4 @@ public class ThreadedBulletinBoardClientIntegrationTest {
} }
@Test
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
} }

View File

@ -1 +1 @@
/bin/ /bin/

View File

@ -259,4 +259,3 @@ publishing {
} }
} }

View File

@ -1,29 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<root> <root>
<setting name="task-tab"> <setting name="task-tab">
<setting name="show-description" value="true"/> <setting name="show-description" value="true"/>
</setting> </setting>
<setting name="favorites-tab"/> <setting name="favorites-tab"/>
<setting name="command_line-tab"/> <setting name="command_line-tab"/>
<setting name="setup-tab"> <setting name="setup-tab">
<setting name="setup"> <setting name="setup">
<setting name="custom-gradle-executor"/> <setting name="custom-gradle-executor"/>
<setting name="current-directory" value="/home/talm/proj/meerkat/bulletin-board-server"/> <setting name="current-directory" value="/home/talm/proj/meerkat/bulletin-board-server"/>
<setting name="log-level" value="LIFECYCLE"/> <setting name="log-level" value="LIFECYCLE"/>
</setting> </setting>
</setting> </setting>
<setting name="SinglePaneUIInstance_splitter-id"> <setting name="SinglePaneUIInstance_splitter-id">
<setting name="divider_location" value="387"/> <setting name="divider_location" value="387"/>
</setting> </setting>
<setting name="main_panel"> <setting name="main_panel">
<setting name="current-tab" value="Task Tree"/> <setting name="current-tab" value="Task Tree"/>
</setting> </setting>
<setting name="Application_window-id"> <setting name="Application_window-id">
<setting name="window_x" value="306"/> <setting name="window_x" value="306"/>
<setting name="window_y" value="1042"/> <setting name="window_y" value="1042"/>
<setting name="window_width" value="800"/> <setting name="window_width" value="800"/>
<setting name="window_height" value="800"/> <setting name="window_height" value="800"/>
<setting name="extended-state" value="0"/> <setting name="extended-state" value="0"/>
</setting> </setting>
</root> </root>

View File

@ -1,30 +1,30 @@
package meerkat.bulletinboard.service; package meerkat.bulletinboard.service;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.Message; import com.google.protobuf.Message;
import meerkat.protobuf.Crypto; import meerkat.protobuf.Crypto;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
* Created by talm on 10/11/15. * Created by talm on 10/11/15.
*/ */
public class HelloProtoBuf { public class HelloProtoBuf {
public Message sayHello() { public Message sayHello() {
BulletinBoardMessage.Builder msg = BulletinBoardMessage.newBuilder(); BulletinBoardMessage.Builder msg = BulletinBoardMessage.newBuilder();
UnsignedBulletinBoardMessage.Builder unsigned = UnsignedBulletinBoardMessage.newBuilder(); UnsignedBulletinBoardMessage.Builder unsigned = UnsignedBulletinBoardMessage.newBuilder();
unsigned.setData(ByteString.copyFromUtf8("Hello World!")); unsigned.setData(ByteString.copyFromUtf8("Hello World!"));
List<String> tags = Arrays.asList("Greetings", "FirstPrograms"); List<String> tags = Arrays.asList("Greetings", "FirstPrograms");
unsigned.addAllTag(tags); unsigned.addAllTag(tags);
msg.setMsg(unsigned); msg.setMsg(unsigned);
Crypto.Signature.Builder sig = Crypto.Signature.newBuilder(); Crypto.Signature.Builder sig = Crypto.Signature.newBuilder();
sig.setData(ByteString.copyFromUtf8("deadbeef")); sig.setData(ByteString.copyFromUtf8("deadbeef"));
msg.addSig(sig); msg.addSig(sig);
return msg.build(); return msg.build();
} }
} }

View File

@ -1,266 +1,291 @@
package meerkat.bulletinboard.sqlserver; package meerkat.bulletinboard.sqlserver;
import meerkat.protobuf.BulletinBoardAPI.FilterType; import meerkat.protobuf.BulletinBoardAPI.FilterType;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.h2.jdbcx.JdbcDataSource;
import javax.naming.Context; import javax.sql.DataSource;
import javax.naming.InitialContext; import java.text.MessageFormat;
import java.util.LinkedList;
import javax.naming.NamingException; import java.util.List;
import javax.sql.DataSource;
import java.text.MessageFormat; /**
import java.util.LinkedList; * Created by Arbel Deutsch Peled on 09-Dec-15.
import java.util.List; */
/** public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider {
* Created by Arbel Deutsch Peled on 09-Dec-15.
*/ private String dbName;
public class H2QueryProvider implements BulletinBoardSQLServer.SQLQueryProvider { public H2QueryProvider(String dbName) {
this.dbName = dbName;
private String dbName; }
public H2QueryProvider(String dbName) {
this.dbName = dbName; @Override
} public String getSQLString(QueryType queryType) throws IllegalArgumentException{
switch(queryType) {
@Override case ADD_SIGNATURE:
public String getSQLString(QueryType queryType) throws IllegalArgumentException{ return MessageFormat.format(
"INSERT INTO SignatureTable (EntryNum, SignerId, Signature)"
switch(queryType) { + " SELECT DISTINCT :{0} AS Entry, :{1} AS Id, :{2} AS Sig FROM UtilityTable AS Temp"
case ADD_SIGNATURE: + " WHERE NOT EXISTS"
return "INSERT INTO SignatureTable (EntryNum, SignerId, Signature)" + " (SELECT 1 FROM SignatureTable AS SubTable WHERE SubTable.EntryNum = :{0} AND SubTable.SignerId = :{1})",
+ " SELECT DISTINCT :EntryNum AS Entry, :SignerId AS Id, :Signature AS Sig FROM UtilityTable AS Temp" QueryType.ADD_SIGNATURE.getParamName(0),
+ " WHERE NOT EXISTS" QueryType.ADD_SIGNATURE.getParamName(1),
+ " (SELECT 1 FROM SignatureTable AS SubTable WHERE SubTable.SignerId = :SignerId AND SubTable.EntryNum = :EntryNum)"; QueryType.ADD_SIGNATURE.getParamName(2));
case CONNECT_TAG: case CONNECT_TAG:
return "INSERT INTO MsgTagTable (TagId, EntryNum)" return MessageFormat.format(
+ " SELECT DISTINCT TagTable.TagId, :EntryNum AS NewEntry FROM TagTable WHERE Tag = :Tag" "INSERT INTO MsgTagTable (TagId, EntryNum)"
+ " AND NOT EXISTS (SELECT 1 FROM MsgTagTable AS SubTable WHERE SubTable.TagId = TagTable.TagId" + " SELECT DISTINCT TagTable.TagId, :{0} AS NewEntry FROM TagTable WHERE Tag = :{1}"
+ " AND SubTable.EntryNum = :EntryNum)"; + " AND NOT EXISTS (SELECT 1 FROM MsgTagTable AS SubTable WHERE SubTable.TagId = TagTable.TagId"
+ " AND SubTable.EntryNum = :{0})",
case FIND_MSG_ID: QueryType.CONNECT_TAG.getParamName(0),
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; QueryType.CONNECT_TAG.getParamName(1));
case FIND_TAG_ID: case FIND_MSG_ID:
return MessageFormat.format( return MessageFormat.format(
"SELECT TagId FROM TagTable WHERE Tag = :{0}", "SELECT EntryNum From MsgTable WHERE MsgId = :{0}",
QueryType.FIND_TAG_ID.getParamName(0)); QueryType.FIND_MSG_ID.getParamName(0));
case GET_MESSAGES: case FIND_TAG_ID:
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; return MessageFormat.format(
"SELECT TagId FROM TagTable WHERE Tag = :{0}",
case COUNT_MESSAGES: QueryType.FIND_TAG_ID.getParamName(0));
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
case GET_MESSAGES:
case GET_MESSAGE_STUBS: return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
case COUNT_MESSAGES:
case GET_SIGNATURES: return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
case GET_MESSAGE_STUBS:
case INSERT_MSG: return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
return "INSERT INTO MsgTable (MsgId, Msg, ExactTime) VALUES(:MsgId,:Msg,:TimeStamp)";
case GET_SIGNATURES:
case INSERT_NEW_TAG: return MessageFormat.format(
return "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE" "SELECT Signature FROM SignatureTable WHERE EntryNum = :{0}",
+ " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :Tag)"; QueryType.GET_SIGNATURES.getParamName(0));
case GET_LAST_MESSAGE_ENTRY: case INSERT_MSG:
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable"; return MessageFormat.format(
"INSERT INTO MsgTable (MsgId, ExactTime, Msg) VALUES(:{0}, :{1}, :{2})",
case GET_BATCH_MESSAGE_ENTRY: QueryType.INSERT_MSG.getParamName(0),
return MessageFormat.format( QueryType.INSERT_MSG.getParamName(1),
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable" QueryType.INSERT_MSG.getParamName(2));
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum"
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum" case DELETE_MSG_BY_ENTRY:
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId" return MessageFormat.format(
+ " WHERE SignatureTable.SignerId = :{0}" "DELETE FROM MsgTable WHERE EntryNum = :{0}",
+ " AND TagTable.Tag = :{1}", QueryType.DELETE_MSG_BY_ENTRY.getParamName(0));
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0),
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1)); case DELETE_MSG_BY_ID:
return MessageFormat.format(
case GET_BATCH_MESSAGE_DATA: "DELETE FROM MsgTable WHERE MsgId = :{0}",
return MessageFormat.format( QueryType.DELETE_MSG_BY_ID.getParamName(0));
"SELECT Data FROM BatchTable"
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" case INSERT_NEW_TAG:
+ " ORDER BY SerialNum ASC", return MessageFormat.format(
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0), "INSERT INTO TagTable(Tag) SELECT DISTINCT :Tag AS NewTag FROM UtilityTable WHERE"
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1), + " NOT EXISTS (SELECT 1 FROM TagTable AS SubTable WHERE SubTable.Tag = :{0})",
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); QueryType.INSERT_NEW_TAG.getParamName(0));
case INSERT_BATCH_DATA: case GET_LAST_MESSAGE_ENTRY:
return MessageFormat.format( return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)"
+ " VALUES (:{0}, :{1}, :{2}, :{3})", case GET_BATCH_MESSAGE_DATA_BY_MSG_ID:
QueryType.INSERT_BATCH_DATA.getParamName(0), return MessageFormat.format(
QueryType.INSERT_BATCH_DATA.getParamName(1), "SELECT Data FROM BatchTable"
QueryType.INSERT_BATCH_DATA.getParamName(2), + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum"
QueryType.INSERT_BATCH_DATA.getParamName(3)); + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}"
+ " ORDER BY BatchTable.SerialNum ASC",
case CHECK_BATCH_LENGTH: QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(0),
return MessageFormat.format( QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(1));
"SELECT COUNT(Data) AS BatchLength FROM BatchTable"
+ " WHERE SignerId = :{0} AND BatchId = :{1}", case GET_BATCH_MESSAGE_DATA_BY_BATCH_ID:
QueryType.CHECK_BATCH_LENGTH.getParamName(0), return MessageFormat.format(
QueryType.CHECK_BATCH_LENGTH.getParamName(1)); "SELECT Data FROM BatchTable"
+ " WHERE BatchId = :{0} AND SerialNum >= :{1}"
case CONNECT_BATCH_TAG: + " ORDER BY BatchTable.SerialNum ASC",
return MessageFormat.format( QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(0),
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable" QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(1));
+ " WHERE Tag = :{2}",
QueryType.CONNECT_BATCH_TAG.getParamName(0), case INSERT_BATCH_DATA:
QueryType.CONNECT_BATCH_TAG.getParamName(1), return MessageFormat.format(
QueryType.CONNECT_BATCH_TAG.getParamName(2)); "INSERT INTO BatchTable (BatchId, SerialNum, Data) VALUES (:{0}, :{1}, :{2})",
QueryType.INSERT_BATCH_DATA.getParamName(0),
case GET_BATCH_TAGS: QueryType.INSERT_BATCH_DATA.getParamName(1),
return MessageFormat.format( QueryType.INSERT_BATCH_DATA.getParamName(2));
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId"
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC", case CHECK_BATCH_LENGTH:
QueryType.GET_BATCH_TAGS.getParamName(0), return MessageFormat.format(
QueryType.GET_BATCH_TAGS.getParamName(1)); "SELECT COUNT(Data) AS BatchLength FROM BatchTable WHERE BatchId = :{0}",
QueryType.CHECK_BATCH_LENGTH.getParamName(0));
case REMOVE_BATCH_TAGS:
return MessageFormat.format( case CHECK_BATCH_OPEN:
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}", return MessageFormat.format(
QueryType.REMOVE_BATCH_TAGS.getParamName(0), "SELECT COUNT(BatchId) AS batchCount FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.REMOVE_BATCH_TAGS.getParamName(1)); QueryType.CHECK_BATCH_OPEN.getParamName(0));
default: case STORE_BATCH_TAGS:
throw new IllegalArgumentException("Cannot serve a query of type " + queryType); return MessageFormat.format(
} "INSERT INTO BatchTagTable (Tags) VALUES (:{0})",
QueryType.STORE_BATCH_TAGS.getParamName(0));
}
case GET_BATCH_TAGS:
@Override return MessageFormat.format(
public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { "SELECT Tags FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.GET_BATCH_TAGS.getParamName(0));
String serialString = Integer.toString(serialNum);
case ADD_ENTRY_NUM_TO_BATCH:
switch(filterType) { return MessageFormat.format(
case EXACT_ENTRY: "UPDATE BatchTable SET EntryNum = :{1} WHERE BatchId = :{0}",
return "MsgTable.EntryNum = :EntryNum" + serialString; QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0),
case MAX_ENTRY: QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1));
return "MsgTable.EntryNum <= :EntryNum" + serialString;
case MIN_ENTRY: default:
return "MsgTable.EntryNum >= :EntryNum" + serialString; throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
case MAX_MESSAGES: }
return "LIMIT :Limit" + serialString;
case MSG_ID: }
return "MsgTable.MsgId = :MsgId" + serialString;
case SIGNER_ID: @Override
return "EXISTS (SELECT 1 FROM SignatureTable" public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException {
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
case TAG: String serialString = Integer.toString(serialNum);
return "EXISTS (SELECT 1 FROM TagTable"
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" switch(filterType) {
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; case EXACT_ENTRY:
return "MsgTable.EntryNum = :EntryNum" + serialString;
case BEFORE_TIME: case MAX_ENTRY:
return "MsgTable.ExactTime <= :TimeStamp" + serialString; return "MsgTable.EntryNum <= :EntryNum" + serialString;
case MIN_ENTRY:
case AFTER_TIME: return "MsgTable.EntryNum >= :EntryNum" + serialString;
return "MsgTable.ExactTime >= :TimeStamp" + serialString; case MAX_MESSAGES:
return "LIMIT :Limit" + serialString;
default: case MSG_ID:
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); return "MsgTable.MsgId = :MsgId" + serialString;
} case SIGNER_ID:
return "EXISTS (SELECT 1 FROM SignatureTable"
} + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
case TAG:
@Override return "EXISTS (SELECT 1 FROM TagTable"
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
switch(filterType) {
case EXACT_ENTRY: // Go through case BEFORE_TIME:
case MAX_ENTRY: // Go through return "MsgTable.ExactTime <= :TimeStamp" + serialString;
case MIN_ENTRY: // Go through
case MAX_MESSAGES: case AFTER_TIME:
return "INT"; return "MsgTable.ExactTime >= :TimeStamp" + serialString;
case MSG_ID: // Go through default:
case SIGNER_ID: throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
return "TINYBLOB"; }
case TAG: }
return "VARCHAR";
@Override
case AFTER_TIME: // Go through public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException {
case BEFORE_TIME:
return "TIMESTAMP"; switch(filterType) {
case EXACT_ENTRY: // Go through
case MAX_ENTRY: // Go through
default: case MIN_ENTRY: // Go through
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); case MAX_MESSAGES:
} return "INT";
} case MSG_ID: // Go through
case SIGNER_ID:
@Override return "TINYBLOB";
public DataSource getDataSource() {
case TAG:
BasicDataSource dataSource = new BasicDataSource(); return "VARCHAR";
dataSource.setDriverClassName("org.h2.Driver"); case AFTER_TIME: // Go through
dataSource.setUrl("jdbc:h2:~/" + dbName); case BEFORE_TIME:
return "TIMESTAMP";
return dataSource;
} default:
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
}
@Override
public List<String> getSchemaCreationCommands() { }
List<String> list = new LinkedList<String>();
@Override
list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY, MsgId TINYBLOB UNIQUE, ExactTime TIMESTAMP, Msg BLOB)"); public DataSource getDataSource() {
list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50) UNIQUE)"); BasicDataSource dataSource = new BasicDataSource();
list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT," dataSource.setDriverClassName("org.h2.Driver");
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," dataSource.setUrl("jdbc:h2:mem:" + dbName);
+ " FOREIGN KEY (TagId) REFERENCES TagTable(TagId),"
+ " UNIQUE (EntryNum, TagID))"); return dataSource;
list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB UNIQUE," }
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))");
list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); @Override
list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId, EntryNum)"); public List<String> getSchemaCreationCommands() {
List<String> list = new LinkedList<String>();
list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB,"
+ " UNIQUE(SignerId, BatchId, SerialNum))"); list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
+ " MsgId TINYBLOB UNIQUE, ExactTime TIMESTAMP, Msg BLOB)");
list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, TagId INT,"
+ " FOREIGN KEY (TagId) REFERENCES TagTable(TagId))"); list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50) UNIQUE)");
list.add("CREATE INDEX IF NOT EXISTS BatchIndex ON BatchTagTable(SignerId, BatchId)"); list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT,"
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE,"
// This is used to create a simple table with one entry. + " FOREIGN KEY (TagId) REFERENCES TagTable(TagId) ON DELETE CASCADE,"
// It is used for implementing a workaround for the missing INSERT IGNORE syntax + " UNIQUE (EntryNum, TagID))");
list.add("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)");
list.add("INSERT INTO UtilityTable (Entry) VALUES (1)"); list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB UNIQUE,"
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE)");
return list;
} list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)");
list.add("CREATE UNIQUE INDEX IF NOT EXISTS SignatureIndex ON SignatureTable(SignerId, EntryNum)");
@Override
public List<String> getSchemaDeletionCommands() { list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (BatchId INT AUTO_INCREMENT PRIMARY KEY, Tags BLOB)");
List<String> list = new LinkedList<String>();
list.add("CREATE TABLE IF NOT EXISTS BatchTable (BatchId INT, EntryNum INT, SerialNum INT, Data BLOB,"
list.add("DROP TABLE IF EXISTS UtilityTable"); + " UNIQUE(BatchId, SerialNum),"
list.add("DROP INDEX IF EXISTS BatchIndex"); + " FOREIGN KEY (BatchId) REFERENCES BatchTagTable(BatchId) ON DELETE CASCADE)");
list.add("DROP TABLE IF EXISTS BatchTagTable"); list.add("CREATE INDEX IF NOT EXISTS BatchDataIndex ON BatchTable(EntryNum, SerialNum)");
list.add("DROP TABLE IF EXISTS BatchTable");
list.add("DROP INDEX IF EXISTS SignerIdIndex");
list.add("DROP TABLE IF EXISTS MsgTagTable");
list.add("DROP TABLE IF EXISTS SignatureTable"); // This is used to create a simple table with one entry.
list.add("DROP TABLE IF EXISTS TagTable"); // It is used for implementing a workaround for the missing INSERT IGNORE syntax
list.add("DROP TABLE IF EXISTS MsgTable"); list.add("CREATE TABLE IF NOT EXISTS UtilityTable (Entry INT)");
list.add("INSERT INTO UtilityTable (Entry) VALUES (1)");
return list;
} return list;
}
}
@Override
public List<String> getSchemaDeletionCommands() {
List<String> list = new LinkedList<String>();
list.add("DROP TABLE IF EXISTS UtilityTable");
list.add("DROP INDEX IF EXISTS BatchDataIndex");
list.add("DROP TABLE IF EXISTS BatchTable");
list.add("DROP INDEX IF EXISTS BatchTagIndex");
list.add("DROP TABLE IF EXISTS BatchTagTable");
list.add("DROP TABLE IF EXISTS MsgTagTable");
list.add("DROP INDEX IF EXISTS SignerIdIndex");
list.add("DROP TABLE IF EXISTS SignatureTable");
list.add("DROP TABLE IF EXISTS TagTable");
list.add("DROP TABLE IF EXISTS MsgTable");
return list;
}
}

View File

@ -1,273 +1,276 @@
package meerkat.bulletinboard.sqlserver; package meerkat.bulletinboard.sqlserver;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider;
import meerkat.bulletinboard.BulletinBoardConstants; import meerkat.protobuf.BulletinBoardAPI.FilterType;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; import org.apache.commons.dbcp2.BasicDataSource;
import meerkat.protobuf.BulletinBoardAPI.FilterType;
import org.apache.commons.dbcp2.BasicDataSource; import javax.sql.DataSource;
import java.text.MessageFormat;
import javax.sql.DataSource; import java.util.LinkedList;
import java.text.MessageFormat; import java.util.List;
import java.util.LinkedList;
import java.util.List; /**
* Created by Arbel Deutsch Peled on 09-Dec-15.
/** */
* Created by Arbel Deutsch Peled on 09-Dec-15.
*/ public class MySQLQueryProvider implements SQLQueryProvider {
public class MySQLQueryProvider implements SQLQueryProvider { private String dbAddress;
private int dbPort;
private String dbAddress; private String dbName;
private int dbPort; private String username;
private String dbName; private String password;
private String username;
private String password; public MySQLQueryProvider(String dbAddress, int dbPort, String dbName, String username, String password) {
this.dbAddress = dbAddress;
public MySQLQueryProvider(String dbAddress, int dbPort, String dbName, String username, String password) { this.dbPort = dbPort;
this.dbAddress = dbAddress; this.dbName = dbName;
this.dbPort = dbPort; this.username = username;
this.dbName = dbName; this.password = password;
this.username = username; }
this.password = password;
} @Override
public String getSQLString(QueryType queryType) throws IllegalArgumentException{
@Override
public String getSQLString(QueryType queryType) throws IllegalArgumentException{ switch(queryType) {
switch(queryType) { case ADD_SIGNATURE:
return MessageFormat.format(
case ADD_SIGNATURE: "INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:{0}, :{1}, :{2})",
return MessageFormat.format( QueryType.ADD_SIGNATURE.getParamName(0),
"INSERT IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:{0}, :{1}, :{2})", QueryType.ADD_SIGNATURE.getParamName(1),
QueryType.ADD_SIGNATURE.getParamName(0), QueryType.ADD_SIGNATURE.getParamName(2));
QueryType.ADD_SIGNATURE.getParamName(1),
QueryType.ADD_SIGNATURE.getParamName(2)); case CONNECT_TAG:
return MessageFormat.format(
case CONNECT_TAG: "INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)"
return MessageFormat.format( + " SELECT TagTable.TagId, :{0} AS EntryNum FROM TagTable WHERE Tag = :{1}",
"INSERT IGNORE INTO MsgTagTable (TagId, EntryNum)" QueryType.CONNECT_TAG.getParamName(0),
+ " SELECT TagTable.TagId, :{0} AS EntryNum FROM TagTable WHERE Tag = :{1}", QueryType.CONNECT_TAG.getParamName(1));
QueryType.CONNECT_TAG.getParamName(0),
QueryType.CONNECT_TAG.getParamName(1)); case FIND_MSG_ID:
return MessageFormat.format(
case FIND_MSG_ID: "SELECT EntryNum From MsgTable WHERE MsgId = :{0}",
return MessageFormat.format( QueryType.FIND_MSG_ID.getParamName(0));
"SELECT EntryNum From MsgTable WHERE MsgId = :{0}",
QueryType.FIND_MSG_ID.getParamName(0)); case FIND_TAG_ID:
return MessageFormat.format(
case FIND_TAG_ID: "SELECT TagId FROM TagTable WHERE Tag = :{0}",
return MessageFormat.format( QueryType.FIND_TAG_ID.getParamName(0));
"SELECT TagId FROM TagTable WHERE Tag = :{0}",
QueryType.FIND_TAG_ID.getParamName(0)); case GET_MESSAGES:
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
case GET_MESSAGES:
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; case COUNT_MESSAGES:
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
case COUNT_MESSAGES:
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable"; case GET_MESSAGE_STUBS:
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
case GET_MESSAGE_STUBS:
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable"; case GET_SIGNATURES:
return MessageFormat.format(
case GET_SIGNATURES: "SELECT Signature FROM SignatureTable WHERE EntryNum = :{0}",
return MessageFormat.format( QueryType.GET_SIGNATURES.getParamName(0));
"SELECT Signature FROM SignatureTable WHERE EntryNum = :{0}",
QueryType.GET_SIGNATURES.getParamName(0)); case INSERT_MSG:
return MessageFormat.format(
case INSERT_MSG: "INSERT INTO MsgTable (MsgId, ExactTime, Msg) VALUES(:{0}, :{1}, :{2})",
return MessageFormat.format( QueryType.INSERT_MSG.getParamName(0),
"INSERT INTO MsgTable (MsgId, ExactTime, Msg) VALUES(:{0}, :{1}, :{2})", QueryType.INSERT_MSG.getParamName(1),
QueryType.INSERT_MSG.getParamName(0), QueryType.INSERT_MSG.getParamName(2));
QueryType.INSERT_MSG.getParamName(1),
QueryType.INSERT_MSG.getParamName(2)); case DELETE_MSG_BY_ENTRY:
return MessageFormat.format(
case INSERT_NEW_TAG: "DELETE IGNORE FROM MsgTable WHERE EntryNum = :{0}",
return MessageFormat.format( QueryType.DELETE_MSG_BY_ENTRY.getParamName(0));
"INSERT IGNORE INTO TagTable(Tag) VALUES (:{0})",
QueryType.INSERT_NEW_TAG.getParamName(0)); case DELETE_MSG_BY_ID:
return MessageFormat.format(
case GET_LAST_MESSAGE_ENTRY: "DELETE IGNORE FROM MsgTable WHERE MsgId = :{0}",
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable"; QueryType.DELETE_MSG_BY_ID.getParamName(0));
case GET_BATCH_MESSAGE_ENTRY: case INSERT_NEW_TAG:
return MessageFormat.format( return MessageFormat.format(
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable" "INSERT IGNORE INTO TagTable(Tag) VALUES (:{0})",
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum" QueryType.INSERT_NEW_TAG.getParamName(0));
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum"
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId" case GET_LAST_MESSAGE_ENTRY:
+ " WHERE SignatureTable.SignerId = :{0}" return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
+ " AND TagTable.Tag = :{1}",
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0), case GET_BATCH_MESSAGE_DATA_BY_MSG_ID:
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1)); return MessageFormat.format(
"SELECT Data FROM BatchTable"
case GET_BATCH_MESSAGE_DATA: + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum"
return MessageFormat.format( + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}"
"SELECT Data FROM BatchTable" + " ORDER BY BatchTable.SerialNum ASC",
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(0),
+ " ORDER BY SerialNum ASC", QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(1));
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0),
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1), case GET_BATCH_MESSAGE_DATA_BY_BATCH_ID:
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); return MessageFormat.format(
"SELECT Data FROM BatchTable"
case INSERT_BATCH_DATA: + " WHERE BatchId = :{0} AND SerialNum >= :{1}"
return MessageFormat.format( + " ORDER BY BatchTable.SerialNum ASC",
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)" QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(0),
+ " VALUES (:{0}, :{1}, :{2}, :{3})", QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(1));
QueryType.INSERT_BATCH_DATA.getParamName(0),
QueryType.INSERT_BATCH_DATA.getParamName(1), case INSERT_BATCH_DATA:
QueryType.INSERT_BATCH_DATA.getParamName(2), return MessageFormat.format(
QueryType.INSERT_BATCH_DATA.getParamName(3)); "INSERT INTO BatchTable (BatchId, SerialNum, Data) VALUES (:{0}, :{1}, :{2})",
QueryType.INSERT_BATCH_DATA.getParamName(0),
case CHECK_BATCH_LENGTH: QueryType.INSERT_BATCH_DATA.getParamName(1),
return MessageFormat.format( QueryType.INSERT_BATCH_DATA.getParamName(2));
"SELECT COUNT(Data) AS BatchLength FROM BatchTable"
+ " WHERE SignerId = :{0} AND BatchId = :{1}", case CHECK_BATCH_LENGTH:
QueryType.CHECK_BATCH_LENGTH.getParamName(0), return MessageFormat.format(
QueryType.CHECK_BATCH_LENGTH.getParamName(1)); "SELECT COUNT(Data) AS BatchLength FROM BatchTable WHERE BatchId = :{0}",
QueryType.CHECK_BATCH_LENGTH.getParamName(0));
case CONNECT_BATCH_TAG:
return MessageFormat.format( case CHECK_BATCH_OPEN:
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable" return MessageFormat.format(
+ " WHERE Tag = :{2}", "SELECT COUNT(BatchId) AS batchCount FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.CONNECT_BATCH_TAG.getParamName(0), QueryType.CHECK_BATCH_OPEN.getParamName(0));
QueryType.CONNECT_BATCH_TAG.getParamName(1),
QueryType.CONNECT_BATCH_TAG.getParamName(2)); case STORE_BATCH_TAGS:
return MessageFormat.format(
case GET_BATCH_TAGS: "INSERT INTO BatchTagTable (Tags) VALUES (:{0})",
return MessageFormat.format( QueryType.STORE_BATCH_TAGS.getParamName(0));
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId"
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC", case GET_BATCH_TAGS:
QueryType.GET_BATCH_TAGS.getParamName(0), return MessageFormat.format(
QueryType.GET_BATCH_TAGS.getParamName(1)); "SELECT Tags FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.GET_BATCH_TAGS.getParamName(0));
case REMOVE_BATCH_TAGS:
return MessageFormat.format( case ADD_ENTRY_NUM_TO_BATCH:
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}", return MessageFormat.format(
QueryType.REMOVE_BATCH_TAGS.getParamName(0), "UPDATE BatchTable SET EntryNum = :{1} WHERE BatchId = :{0}",
QueryType.REMOVE_BATCH_TAGS.getParamName(1)); QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0),
QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1));
default:
throw new IllegalArgumentException("Cannot serve a query of type " + queryType); default:
} throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
}
}
}
@Override
public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { @Override
public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException {
String serialString = Integer.toString(serialNum);
String serialString = Integer.toString(serialNum);
switch(filterType) {
case EXACT_ENTRY: switch(filterType) {
return "MsgTable.EntryNum = :EntryNum" + serialString; case EXACT_ENTRY:
case MAX_ENTRY: return "MsgTable.EntryNum = :EntryNum" + serialString;
return "MsgTable.EntryNum <= :EntryNum" + serialString; case MAX_ENTRY:
case MIN_ENTRY: return "MsgTable.EntryNum <= :EntryNum" + serialString;
return "MsgTable.EntryNum >= :EntryNum" + serialString; case MIN_ENTRY:
case MAX_MESSAGES: return "MsgTable.EntryNum >= :EntryNum" + serialString;
return "LIMIT :Limit" + serialString; case MAX_MESSAGES:
case MSG_ID: return "LIMIT :Limit" + serialString;
return "MsgTable.MsgId = :MsgId" + serialString; case MSG_ID:
case SIGNER_ID: return "MsgTable.MsgId = :MsgId" + serialString;
return "EXISTS (SELECT 1 FROM SignatureTable" case SIGNER_ID:
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)"; return "EXISTS (SELECT 1 FROM SignatureTable"
case TAG: + " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
return "EXISTS (SELECT 1 FROM TagTable" case TAG:
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" return "EXISTS (SELECT 1 FROM TagTable"
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
case BEFORE_TIME:
return "MsgTable.ExactTime <= :TimeStamp"; case BEFORE_TIME:
return "MsgTable.ExactTime <= :TimeStamp";
case AFTER_TIME:
return "MsgTable.ExactTime >= :TimeStamp"; case AFTER_TIME:
return "MsgTable.ExactTime >= :TimeStamp";
default:
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); default:
} throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
}
}
}
@Override
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { @Override
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException {
switch(filterType) {
case EXACT_ENTRY: // Go through switch(filterType) {
case MAX_ENTRY: // Go through case EXACT_ENTRY: // Go through
case MIN_ENTRY: // Go through case MAX_ENTRY: // Go through
case MAX_MESSAGES: case MIN_ENTRY: // Go through
return "INT"; case MAX_MESSAGES:
return "INT";
case MSG_ID: // Go through
case SIGNER_ID: case MSG_ID: // Go through
return "TINYBLOB"; case SIGNER_ID:
return "TINYBLOB";
case TAG:
return "VARCHAR"; case TAG:
return "VARCHAR";
case AFTER_TIME: // Go through
case BEFORE_TIME: case AFTER_TIME: // Go through
return "TIMESTAMP"; case BEFORE_TIME:
return "TIMESTAMP";
default:
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); default:
} throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
}
}
}
@Override
public DataSource getDataSource() { @Override
public DataSource getDataSource() {
BasicDataSource dataSource = new BasicDataSource();
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://" + dbAddress + ":" + dbPort + "/" + dbName); dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://" + dbAddress + ":" + dbPort + "/" + dbName);
dataSource.setUsername(username);
dataSource.setPassword(password); dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
return dataSource;
}
}
@Override
public List<String> getSchemaCreationCommands() { @Override
List<String> list = new LinkedList<String>(); public List<String> getSchemaCreationCommands() {
List<String> list = new LinkedList<String>();
list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
+ " MsgId TINYBLOB, ExactTime TIMESTAMP, Msg BLOB, UNIQUE(MsgId(50)))"); list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"
+ " MsgId TINYBLOB, ExactTime TIMESTAMP, Msg BLOB, UNIQUE(MsgId(50)))");
list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50), UNIQUE(Tag))");
list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INT NOT NULL AUTO_INCREMENT PRIMARY KEY, Tag VARCHAR(50), UNIQUE(Tag))");
list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT,"
+ " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum)," list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum INT, TagId INT,"
+ " CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId)," + " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE,"
+ " CONSTRAINT UNIQUE (EntryNum, TagID))"); + " CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId) ON DELETE CASCADE,"
+ " CONSTRAINT UNIQUE (EntryNum, TagID))");
list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB,"
+ " INDEX(SignerId(32)), CONSTRAINT Unique_Signature UNIQUE(SignerId(32), EntryNum)," list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INT, SignerId TINYBLOB, Signature TINYBLOB,"
+ " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum))"); + " INDEX(SignerId(32)), CONSTRAINT Unique_Signature UNIQUE(SignerId(32), EntryNum),"
+ " CONSTRAINT FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE)");
list.add("CREATE TABLE IF NOT EXISTS BatchTable (SignerId TINYBLOB, BatchId INT, SerialNum INT, Data BLOB,"
+ " CONSTRAINT Unique_Batch UNIQUE(SignerId(32), BatchId, SerialNum))"); list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (BatchId INT AUTO_INCREMENT PRIMARY KEY, Tags BLOB)");
list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (SignerId TINYBLOB, BatchId INT, TagId INT," list.add("CREATE TABLE IF NOT EXISTS BatchTable (BatchId INT, EntryNum INT, SerialNum INT, Data BLOB,"
+ " INDEX(SignerId(32), BatchId), CONSTRAINT FOREIGN KEY (TagId) REFERENCES TagTable(TagId))"); + " CONSTRAINT UNIQUE(BatchId, SerialNum),"
+ " CONSTRAINT FOREIGN KEY (BatchId) REFERENCES BatchTagTable(BatchId) ON DELETE CASCADE)");
return list;
}
@Override return list;
public List<String> getSchemaDeletionCommands() { }
List<String> list = new LinkedList<String>();
@Override
list.add("DROP TABLE IF EXISTS BatchTagTable"); public List<String> getSchemaDeletionCommands() {
list.add("DROP TABLE IF EXISTS BatchTable"); List<String> list = new LinkedList<String>();
list.add("DROP TABLE IF EXISTS MsgTagTable");
list.add("DROP TABLE IF EXISTS SignatureTable"); list.add("DROP TABLE IF EXISTS BatchTable");
list.add("DROP TABLE IF EXISTS TagTable"); list.add("DROP TABLE IF EXISTS BatchTagTable");
list.add("DROP TABLE IF EXISTS MsgTable"); list.add("DROP TABLE IF EXISTS MsgTagTable");
list.add("DROP TABLE IF EXISTS SignatureTable");
return list; list.add("DROP TABLE IF EXISTS TagTable");
} list.add("DROP TABLE IF EXISTS MsgTable");
}
return list;
}
}

View File

@ -1,233 +1,237 @@
package meerkat.bulletinboard.sqlserver; package meerkat.bulletinboard.sqlserver;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import org.sqlite.SQLiteDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.sqlite.SQLiteDataSource;
import javax.sql.DataSource;
import java.text.MessageFormat; import javax.sql.DataSource;
import java.util.LinkedList; import java.text.MessageFormat;
import java.util.List; import java.util.LinkedList;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 09-Dec-15. /**
*/ * Created by Arbel Deutsch Peled on 09-Dec-15.
*/
public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvider {
public class SQLiteQueryProvider implements BulletinBoardSQLServer.SQLQueryProvider {
String dbName;
String dbName;
public SQLiteQueryProvider(String dbName) {
this.dbName = dbName; public SQLiteQueryProvider(String dbName) {
} this.dbName = dbName;
}
@Override
public String getSQLString(QueryType queryType) throws IllegalArgumentException{ @Override
public String getSQLString(QueryType queryType) throws IllegalArgumentException{
switch(queryType) {
case ADD_SIGNATURE: switch(queryType) {
return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)"; case ADD_SIGNATURE:
return "INSERT OR IGNORE INTO SignatureTable (EntryNum, SignerId, Signature) VALUES (:EntryNum,:SignerId,:Signature)";
case CONNECT_TAG:
return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)" case CONNECT_TAG:
+ " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag"; return "INSERT OR IGNORE INTO MsgTagTable (TagId, EntryNum)"
+ " SELECT TagTable.TagId, :EntryNum AS EntryNum FROM TagTable WHERE Tag = :Tag";
case FIND_MSG_ID:
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId"; case FIND_MSG_ID:
return "SELECT EntryNum From MsgTable WHERE MsgId = :MsgId";
case FIND_TAG_ID:
return MessageFormat.format( case FIND_TAG_ID:
"SELECT TagId FROM TagTable WHERE Tag = :{0}", return MessageFormat.format(
QueryType.FIND_TAG_ID.getParamName(0)); "SELECT TagId FROM TagTable WHERE Tag = :{0}",
QueryType.FIND_TAG_ID.getParamName(0));
case GET_MESSAGES:
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable"; case GET_MESSAGES:
return "SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable";
case COUNT_MESSAGES:
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable"; case COUNT_MESSAGES:
return "SELECT COUNT(MsgTable.EntryNum) FROM MsgTable";
case GET_MESSAGE_STUBS:
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable"; case GET_MESSAGE_STUBS:
return "SELECT MsgTable.EntryNum, MsgTable.MsgId, MsgTable.ExactTime FROM MsgTable";
case GET_SIGNATURES:
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum"; case GET_SIGNATURES:
return "SELECT Signature FROM SignatureTable WHERE EntryNum = :EntryNum";
case INSERT_MSG:
return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)"; case INSERT_MSG:
return "INSERT INTO MsgTable (MsgId, Msg) VALUES(:MsgId,:Msg)";
case INSERT_NEW_TAG:
return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)"; case INSERT_NEW_TAG:
return "INSERT OR IGNORE INTO TagTable(Tag) VALUES (:Tag)";
case GET_LAST_MESSAGE_ENTRY:
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable"; case GET_LAST_MESSAGE_ENTRY:
return "SELECT MAX(MsgTable.EntryNum) FROM MsgTable";
case GET_BATCH_MESSAGE_ENTRY:
return MessageFormat.format( case GET_BATCH_MESSAGE_DATA_BY_MSG_ID:
"SELECT MsgTable.EntryNum, MsgTable.Msg FROM MsgTable" return MessageFormat.format(
+ " INNER JOIN SignatureTable ON MsgTable.EntryNum = SignatureTable.EntryNum" "SELECT Data FROM BatchTable"
+ " INNER JOIN MsgTagTable ON MsgTable.EntryNum = MsgTagTable.EntryNum" + " INNER JOIN MsgTable ON MsgTable.EntryNum = BatchTable.EntryNum"
+ " INNER JOIN TagTable ON MsgTagTable.TagId = TagTable.TagId" + " WHERE MsgTable.MsgId = :{0} AND BatchTable.SerialNum >= :{1}"
+ " WHERE SignatureTable.SignerId = :{0}" + " ORDER BY BatchTable.SerialNum ASC",
+ " AND TagTable.Tag = :{1}", QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(0),
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(0), QueryType.GET_BATCH_MESSAGE_DATA_BY_MSG_ID.getParamName(1));
QueryType.GET_BATCH_MESSAGE_ENTRY.getParamName(1));
case GET_BATCH_MESSAGE_DATA_BY_BATCH_ID:
case GET_BATCH_MESSAGE_DATA: return MessageFormat.format(
return MessageFormat.format( "SELECT Data FROM BatchTable"
"SELECT Data FROM BatchTable" + " WHERE BatchId = :{0} AND SerialNum >= :{1}"
+ " WHERE SignerId = :{0} AND BatchId = :{1} AND SerialNum >= :{2}" + " ORDER BY BatchTable.SerialNum ASC",
+ " ORDER BY SerialNum ASC", QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(0),
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(0), QueryType.GET_BATCH_MESSAGE_DATA_BY_BATCH_ID.getParamName(1));
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(1),
QueryType.GET_BATCH_MESSAGE_DATA.getParamName(2)); case INSERT_BATCH_DATA:
return MessageFormat.format(
case INSERT_BATCH_DATA: "INSERT INTO BatchTable (BatchId, SerialNum, Data) VALUES (:{0}, :{1}, :{2})",
return MessageFormat.format( QueryType.INSERT_BATCH_DATA.getParamName(0),
"INSERT INTO BatchTable (SignerId, BatchId, SerialNum, Data)" QueryType.INSERT_BATCH_DATA.getParamName(1),
+ " VALUES (:{0}, :{1}, :{2}, :{3})", QueryType.INSERT_BATCH_DATA.getParamName(2));
QueryType.INSERT_BATCH_DATA.getParamName(0),
QueryType.INSERT_BATCH_DATA.getParamName(1), case CHECK_BATCH_LENGTH:
QueryType.INSERT_BATCH_DATA.getParamName(2), return MessageFormat.format(
QueryType.INSERT_BATCH_DATA.getParamName(3)); "SELECT COUNT(Data) AS BatchLength FROM BatchTable WHERE BatchId = :{0}",
QueryType.CHECK_BATCH_LENGTH.getParamName(0));
case CHECK_BATCH_LENGTH:
return MessageFormat.format( case CHECK_BATCH_OPEN:
"SELECT COUNT(Data) AS BatchLength FROM BatchTable" return MessageFormat.format(
+ " WHERE SignerId = :{0} AND BatchId = :{1}", "SELECT COUNT(BatchId) AS batchCount FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.CHECK_BATCH_LENGTH.getParamName(0), QueryType.CHECK_BATCH_OPEN.getParamName(0));
QueryType.CHECK_BATCH_LENGTH.getParamName(1));
case STORE_BATCH_TAGS:
case CONNECT_BATCH_TAG: return MessageFormat.format(
return MessageFormat.format( "INSERT INTO BatchTagTable (Tags) VALUES (:{0})",
"INSERT INTO BatchTagTable (SignerId, BatchId, TagId) SELECT :{0}, :{1}, TagId FROM TagTable" QueryType.STORE_BATCH_TAGS.getParamName(0));
+ " WHERE Tag = :{2}",
QueryType.CONNECT_BATCH_TAG.getParamName(0), case GET_BATCH_TAGS:
QueryType.CONNECT_BATCH_TAG.getParamName(1), return MessageFormat.format(
QueryType.CONNECT_BATCH_TAG.getParamName(2)); "SELECT Tags FROM BatchTagTable WHERE BatchId = :{0}",
QueryType.GET_BATCH_TAGS.getParamName(0));
case GET_BATCH_TAGS:
return MessageFormat.format( case ADD_ENTRY_NUM_TO_BATCH:
"SELECT Tag FROM TagTable INNER JOIN BatchTagTable ON TagTable.TagId = BatchTagTable.TagId" return MessageFormat.format(
+ " WHERE SignerId = :{0} AND BatchId = :{1} ORDER BY Tag ASC", "UPDATE BatchTable SET EntryNum = :{1} WHERE BatchId = :{0}",
QueryType.GET_BATCH_TAGS.getParamName(0), QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(0),
QueryType.GET_BATCH_TAGS.getParamName(1)); QueryType.ADD_ENTRY_NUM_TO_BATCH.getParamName(1));
case REMOVE_BATCH_TAGS: default:
return MessageFormat.format( throw new IllegalArgumentException("Cannot serve a query of type " + queryType);
"DELETE FROM BatchTagTable WHERE SignerId = :{0} AND BatchId = :{1}", }
QueryType.REMOVE_BATCH_TAGS.getParamName(0),
QueryType.REMOVE_BATCH_TAGS.getParamName(1)); }
default: @Override
throw new IllegalArgumentException("Cannot serve a query of type " + queryType); public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException {
}
String serialString = Integer.toString(serialNum);
}
switch(filterType) {
@Override case EXACT_ENTRY:
public String getCondition(FilterType filterType, int serialNum) throws IllegalArgumentException { return "MsgTable.EntryNum = :EntryNum" + serialString;
String serialString = Integer.toString(serialNum); case MAX_ENTRY:
return "MsgTable.EntryNum <= :EntryNum" + serialString;
switch(filterType) {
case EXACT_ENTRY: case MIN_ENTRY:
return "MsgTable.EntryNum = :EntryNum" + serialString; return "MsgTable.EntryNum <= :EntryNum" + serialString;
case MAX_ENTRY: case MAX_MESSAGES:
return "MsgTable.EntryNum <= :EntryNum" + serialString; return "LIMIT = :Limit" + serialString;
case MIN_ENTRY: case MSG_ID:
return "MsgTable.EntryNum <= :EntryNum" + serialString; return "MsgTable.MsgId = :MsgId" + serialString;
case MAX_MESSAGES: case SIGNER_ID:
return "LIMIT = :Limit" + serialString; return "EXISTS (SELECT 1 FROM SignatureTable"
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
case MSG_ID:
return "MsgTable.MsgId = :MsgId" + serialString; case TAG:
return "EXISTS (SELECT 1 FROM TagTable"
case SIGNER_ID: + " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId"
return "EXISTS (SELECT 1 FROM SignatureTable" + " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)";
+ " WHERE SignatureTable.SignerId = :SignerId" + serialString + " AND SignatureTable.EntryNum = MsgTable.EntryNum)";
case BEFORE_TIME:
case TAG: return "MsgTable.ExactTime <= :TimeStamp";
return "EXISTS (SELECT 1 FROM TagTable"
+ " INNER JOIN MsgTagTable ON TagTable.TagId = MsgTagTable.TagId" case AFTER_TIME:
+ " WHERE TagTable.Tag = :Tag" + serialString + " AND MsgTagTable.EntryNum = MsgTable.EntryNum)"; return "MsgTable.ExactTime >= :TimeStamp";
case BEFORE_TIME: default:
return "MsgTable.ExactTime <= :TimeStamp"; throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
}
case AFTER_TIME:
return "MsgTable.ExactTime >= :TimeStamp"; }
default: @Override
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException {
}
switch(filterType) {
} case EXACT_ENTRY: // Go through
case MAX_ENTRY: // Go through
@Override case MIN_ENTRY: // Go through
public String getConditionParamTypeName(FilterType filterType) throws IllegalArgumentException { case MAX_MESSAGES:
return "INTEGER";
switch(filterType) {
case EXACT_ENTRY: // Go through case MSG_ID: // Go through
case MAX_ENTRY: // Go through case SIGNER_ID:
case MIN_ENTRY: // Go through return "BLOB";
case MAX_MESSAGES:
return "INTEGER"; case TAG:
return "VARCHAR";
case MSG_ID: // Go through
case SIGNER_ID: default:
return "BLOB"; throw new IllegalArgumentException("Cannot serve a filter of type " + filterType);
}
case TAG:
return "VARCHAR"; }
default: @Override
throw new IllegalArgumentException("Cannot serve a filter of type " + filterType); public DataSource getDataSource() {
}
BasicDataSource dataSource = new BasicDataSource();
} dataSource.setDriverClassName("org.sqlite.JDBC");
dataSource.setUrl("jdbc:sqlite:" + dbName);
@Override
public DataSource getDataSource() { return dataSource;
}
SQLiteDataSource dataSource = new SQLiteDataSource();
dataSource.setUrl("jdbc:sqlite:" + dbName);
@Override
return dataSource; public List<String> getSchemaCreationCommands() {
} List<String> list = new LinkedList<String>();
list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)");
@Override
public List<String> getSchemaCreationCommands() { list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)");
List<String> list = new LinkedList<String>(); list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER,"
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE,"
list.add("CREATE TABLE IF NOT EXISTS MsgTable (EntryNum INTEGER PRIMARY KEY, MsgId BLOB UNIQUE, Msg BLOB)"); + " FOREIGN KEY (TagId) REFERENCES TagTable(TagId) ON DELETE CASCADE,"
+ " UNIQUE (EntryNum, TagID))");
list.add("CREATE TABLE IF NOT EXISTS TagTable (TagId INTEGER PRIMARY KEY, Tag varchar(50) UNIQUE)");
list.add("CREATE TABLE IF NOT EXISTS MsgTagTable (EntryNum BLOB, TagId INTEGER, FOREIGN KEY (EntryNum)" list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB,"
+ " REFERENCES MsgTable(EntryNum), FOREIGN KEY (TagId) REFERENCES TagTable(TagId), UNIQUE (EntryNum, TagID))"); + " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum) ON DELETE CASCADE,"
+ " UNIQUE(SignerId, EntryNum))");
list.add("CREATE TABLE IF NOT EXISTS SignatureTable (EntryNum INTEGER, SignerId BLOB, Signature BLOB,"
+ " FOREIGN KEY (EntryNum) REFERENCES MsgTable(EntryNum), UNIQUE(SignerId, EntryNum))"); list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)");
list.add("CREATE INDEX IF NOT EXISTS SignerIndex ON SignatureTable(SignerId)"); list.add("CREATE TABLE IF NOT EXISTS BatchTagTable (BatchId INTEGER PRIMARY KEY, Tags BLOB)");
return list; list.add("CREATE TABLE IF NOT EXISTS BatchTable (BatchId INTEGER, EntryNum INTEGER, SerialNum INTEGER, Data BLOB,"
} + " UNIQUE(BatchId, SerialNum),"
+ " FOREIGN KEY (BatchId) REFERENCES BatchTagTable(BatchId) ON DELETE CASCADE)");
@Override
public List<String> getSchemaDeletionCommands() { return list;
List<String> list = new LinkedList<String>(); }
list.add("DROP TABLE IF EXISTS MsgTagTable"); @Override
public List<String> getSchemaDeletionCommands() {
list.add("DROP INDEX IF EXISTS SignerIndex"); List<String> list = new LinkedList<String>();
list.add("DROP TABLE IF EXISTS SignatureTable");
list.add("DROP TABLE IF EXISTS MsgTagTable");
list.add("DROP TABLE IF EXISTS TagTable");
list.add("DROP TABLE IF EXISTS MsgTable"); list.add("DROP INDEX IF EXISTS SignerIndex");
list.add("DROP TABLE IF EXISTS SignatureTable");
return list;
} list.add("DROP TABLE IF EXISTS TagTable");
} list.add("DROP TABLE IF EXISTS MsgTable");
return list;
}
}

View File

@ -1,10 +1,8 @@
package meerkat.bulletinboard.sqlserver.mappers; package meerkat.bulletinboard.sqlserver.mappers;
import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.comm.MessageOutputStream; import meerkat.comm.MessageOutputStream;
import meerkat.protobuf.BulletinBoardAPI.BatchData; import meerkat.protobuf.BulletinBoardAPI.BatchChunk;
import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import java.io.IOException; import java.io.IOException;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -15,16 +13,16 @@ import java.sql.SQLException;
*/ */
public class BatchDataCallbackHandler implements RowCallbackHandler { public class BatchDataCallbackHandler implements RowCallbackHandler {
private final MessageOutputStream<BatchData> out; private final MessageOutputStream<BatchChunk> out;
public BatchDataCallbackHandler(MessageOutputStream<BatchData> out) { public BatchDataCallbackHandler(MessageOutputStream<BatchChunk> out) {
this.out = out; this.out = out;
} }
@Override @Override
public void processRow(ResultSet rs) throws SQLException { public void processRow(ResultSet rs) throws SQLException {
try { try {
out.writeMessage(BatchData.parseFrom(rs.getBytes(1))); out.writeMessage(BatchChunk.parseFrom(rs.getBytes(1)));
} catch (IOException e) { } catch (IOException e) {
//TODO: Log //TODO: Log
} }

View File

@ -0,0 +1,31 @@
package meerkat.bulletinboard.sqlserver.mappers;
import meerkat.bulletinboard.BulletinBoardDigest;
import meerkat.protobuf.BulletinBoardAPI.BatchChunk;
import org.springframework.jdbc.core.RowCallbackHandler;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by Arbel Deutsch Peled on 19-Dec-15.
*/
public class BatchDataDigestHandler implements RowCallbackHandler {
private final BulletinBoardDigest digest;
public BatchDataDigestHandler(BulletinBoardDigest digest) {
this.digest = digest;
}
@Override
public void processRow(ResultSet rs) throws SQLException {
try {
BatchChunk batchChunk = BatchChunk.parseFrom(rs.getBytes(1));
digest.update(batchChunk.getData().toByteArray());
} catch (IOException e) {
//TODO: Log
}
}
}

View File

@ -1,26 +0,0 @@
package meerkat.bulletinboard.sqlserver.mappers;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.protobuf.BulletinBoardAPI.BatchData;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by Arbel Deutsch Peled on 19-Dec-15.
*/
public class BatchDataMapper implements RowMapper<BatchData> {
@Override
public BatchData mapRow(ResultSet rs, int rowNum) throws SQLException {
try {
return BatchData.parseFrom(rs.getBytes(1));
} catch (InvalidProtocolBufferException e) {
return BatchData.getDefaultInstance();
}
}
}

View File

@ -0,0 +1,24 @@
package meerkat.bulletinboard.sqlserver.mappers;
import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Created by Arbel Deutsch Peled on 20-Dec-15.
*/
public class BeginBatchMessageMapper implements RowMapper<BeginBatchMessage> {
@Override
public BeginBatchMessage mapRow(ResultSet rs, int rowNum) throws SQLException {
try {
return BeginBatchMessage.newBuilder().mergeFrom(rs.getBytes(1)).build();
} catch (InvalidProtocolBufferException e) {
return null;
}
}
}

View File

@ -1,18 +1,18 @@
package meerkat.bulletinboard.sqlserver.mappers; package meerkat.bulletinboard.sqlserver.mappers;
import meerkat.protobuf.BulletinBoardAPI.MessageID; import meerkat.protobuf.BulletinBoardAPI.MessageID;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* Created by Arbel Deutsch Peled on 11-Dec-15. * Created by Arbel Deutsch Peled on 11-Dec-15.
*/ */
public class LongMapper implements RowMapper<Long> { public class LongMapper implements RowMapper<Long> {
@Override @Override
public Long mapRow(ResultSet rs, int rowNum) throws SQLException { public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getLong(1); return rs.getLong(1);
} }
} }

View File

@ -1,32 +1,32 @@
package meerkat.bulletinboard.sqlserver.mappers; package meerkat.bulletinboard.sqlserver.mappers;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* Created by Arbel Deutsch Peled on 11-Dec-15. * Created by Arbel Deutsch Peled on 11-Dec-15.
*/ */
public class MessageMapper implements RowMapper<BulletinBoardMessage.Builder> { public class MessageMapper implements RowMapper<BulletinBoardMessage.Builder> {
@Override @Override
public BulletinBoardMessage.Builder mapRow(ResultSet rs, int rowNum) throws SQLException { public BulletinBoardMessage.Builder mapRow(ResultSet rs, int rowNum) throws SQLException {
BulletinBoardMessage.Builder builder = BulletinBoardMessage.newBuilder(); BulletinBoardMessage.Builder builder = BulletinBoardMessage.newBuilder();
try { try {
builder.setEntryNum(rs.getLong(1)) builder.setEntryNum(rs.getLong(1))
.setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2))); .setMsg(UnsignedBulletinBoardMessage.parseFrom(rs.getBytes(2)));
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new SQLException(e.getMessage(), e); throw new SQLException(e.getMessage(), e);
} }
return builder; return builder;
} }
} }

View File

@ -21,7 +21,7 @@ public class MessageStubMapper implements RowMapper<BulletinBoardMessage> {
return BulletinBoardMessage.newBuilder() return BulletinBoardMessage.newBuilder()
.setEntryNum(rs.getLong(1)) .setEntryNum(rs.getLong(1))
.setMsg(UnsignedBulletinBoardMessage.newBuilder() .setMsg(UnsignedBulletinBoardMessage.newBuilder()
.setData(ByteString.copyFrom(rs.getBytes(2))) .setMsgId(ByteString.copyFrom(rs.getBytes(2)))
.setTimestamp(BulletinBoardUtils.toTimestampProto(rs.getTimestamp(3))) .setTimestamp(BulletinBoardUtils.toTimestampProto(rs.getTimestamp(3)))
.build()) .build())
.build(); .build();

View File

@ -1,28 +1,28 @@
package meerkat.bulletinboard.sqlserver.mappers; package meerkat.bulletinboard.sqlserver.mappers;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
import meerkat.protobuf.Crypto.Signature; import meerkat.protobuf.Crypto.Signature;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
/** /**
* Created by Arbel Deutsch Peled on 11-Dec-15. * Created by Arbel Deutsch Peled on 11-Dec-15.
*/ */
public class SignatureMapper implements RowMapper<Signature> { public class SignatureMapper implements RowMapper<Signature> {
@Override @Override
public Signature mapRow(ResultSet rs, int rowNum) throws SQLException { public Signature mapRow(ResultSet rs, int rowNum) throws SQLException {
try { try {
return Signature.parseFrom(rs.getBytes(1)); return Signature.parseFrom(rs.getBytes(1));
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new SQLException(e.getMessage(), e); throw new SQLException(e.getMessage(), e);
} }
} }
} }

View File

@ -1,266 +1,268 @@
package meerkat.bulletinboard.webapp; package meerkat.bulletinboard.webapp;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener; import javax.servlet.ServletContextListener;
import javax.ws.rs.*; import javax.ws.rs.*;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.StreamingOutput;
import com.google.protobuf.BoolValue; import com.google.protobuf.BoolValue;
import meerkat.bulletinboard.BulletinBoardServer; import com.google.protobuf.Int32Value;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; import com.google.protobuf.Int64Value;
import meerkat.bulletinboard.sqlserver.H2QueryProvider; import meerkat.bulletinboard.BulletinBoardServer;
import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException; import meerkat.bulletinboard.sqlserver.MySQLQueryProvider;
import meerkat.comm.MessageOutputStream; import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.comm.CommunicationException;
import meerkat.protobuf.Comm.*; import meerkat.comm.MessageOutputStream;
import static meerkat.bulletinboard.BulletinBoardConstants.*; import meerkat.protobuf.BulletinBoardAPI.*;
import static meerkat.rest.Constants.*; import static meerkat.bulletinboard.BulletinBoardConstants.*;
import static meerkat.rest.Constants.*;
import java.io.IOException;
import java.io.OutputStream; import java.io.IOException;
import java.util.Collection; import java.io.OutputStream;
/** /**
* An implementation of the BulletinBoardServer which functions as a WebApp * An implementation of the BulletinBoardServer which functions as a WebApp
*/ */
@Path(BULLETIN_BOARD_SERVER_PATH) @Path(BULLETIN_BOARD_SERVER_PATH)
public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{ public class BulletinBoardWebApp implements BulletinBoardServer, ServletContextListener{
private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard"; private static final String BULLETIN_BOARD_ATTRIBUTE_NAME = "bulletinBoard";
@Context ServletContext servletContext; @Context ServletContext servletContext;
BulletinBoardServer bulletinBoard; BulletinBoardServer bulletinBoard;
/** /**
* This is the servlet init method. * This is the servlet init method.
*/ */
public void init(){ public void init(){
bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME);
} }
/** @Override
* This is the BulletinBoard init method. public void contextInitialized(ServletContextEvent servletContextEvent) {
*/ ServletContext servletContext = servletContextEvent.getServletContext();
@Override String dbType = servletContext.getInitParameter("dbType");
public void init(String meerkatDB) throws CommunicationException { String dbName = servletContext.getInitParameter("dbName");
bulletinBoard.init(meerkatDB);
} if ("SQLite".equals(dbType)){
@Override bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName));
public void contextInitialized(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext(); } else if ("H2".equals(dbType)) {
String dbType = servletContext.getInitParameter("dbType");
String dbName = servletContext.getInitParameter("dbName"); bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName));
if ("SQLite".equals(dbType)){ } else if ("MySQL".equals(dbType)) {
bulletinBoard = new BulletinBoardSQLServer(new SQLiteQueryProvider(dbName)); String dbAddress = servletContext.getInitParameter("dbAddress");
int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort"));
} else if ("H2".equals(dbType)) { String username = servletContext.getInitParameter("username");
String password = servletContext.getInitParameter("password");
bulletinBoard = new BulletinBoardSQLServer(new H2QueryProvider(dbName));
bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password));
} else if ("MySQL".equals(dbType)) { }
String dbAddress = servletContext.getInitParameter("dbAddress"); try {
int dbPort = Integer.parseInt(servletContext.getInitParameter("dbPort")); bulletinBoard.init();
String username = servletContext.getInitParameter("username"); servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard);
String password = servletContext.getInitParameter("password"); } catch (CommunicationException e) {
System.err.println(e.getMessage());
bulletinBoard = new BulletinBoardSQLServer(new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password)); }
} }
try { @Path(POST_MESSAGE_PATH)
init(dbName); @POST
servletContext.setAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME, bulletinBoard); @Consumes(MEDIATYPE_PROTOBUF)
} catch (CommunicationException e) { @Produces(MEDIATYPE_PROTOBUF)
System.err.println(e.getMessage()); @Override
} public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException {
} init();
return bulletinBoard.postMessage(msg);
@Path(POST_MESSAGE_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Override
@Produces(MEDIATYPE_PROTOBUF) public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException {
@Override init();
public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException { bulletinBoard.readMessages(filterList, out);
init(); }
return bulletinBoard.postMessage(msg);
} @Path(COUNT_MESSAGES_PATH)
@POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException { @Produces(MEDIATYPE_PROTOBUF)
init(); @Override
bulletinBoard.readMessages(filterList, out); public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException {
} init();
return bulletinBoard.getMessageCount(filterList);
}
@Path(READ_MESSAGES_PATH)
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(READ_MESSAGES_PATH)
/** @POST
* Wrapper for the readMessages method which streams the output into the response @Consumes(MEDIATYPE_PROTOBUF)
*/ /**
public StreamingOutput readMessages(final MessageFilterList filterList) { * Wrapper for the readMessages method which streams the output into the response
*/
return new StreamingOutput() { public StreamingOutput readMessages(final MessageFilterList filterList) {
@Override return new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
MessageOutputStream<BulletinBoardMessage> out = new MessageOutputStream<>(output); @Override
public void write(OutputStream output) throws IOException, WebApplicationException {
try { MessageOutputStream<BulletinBoardMessage> out = new MessageOutputStream<>(output);
init();
bulletinBoard.readMessages(filterList, out); try {
} catch (CommunicationException e) { init();
//TODO: Log bulletinBoard.readMessages(filterList, out);
out.writeMessage(null); } catch (CommunicationException e) {
} //TODO: Log
} out.writeMessage(null);
}
}; }
} };
@Path(BEGIN_BATCH_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(BEGIN_BATCH_PATH)
@Produces(MEDIATYPE_PROTOBUF) @POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public BoolValue beginBatch(BeginBatchMessage message) { @Produces(MEDIATYPE_PROTOBUF)
try { @Override
init(); public Int64Value beginBatch(BeginBatchMessage message) {
return bulletinBoard.beginBatch(message); try {
} catch (CommunicationException e) { init();
System.err.println(e.getMessage()); return bulletinBoard.beginBatch(message);
return null; } catch (CommunicationException e) {
} System.err.println(e.getMessage());
} return null;
}
@Path(POST_BATCH_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(POST_BATCH_PATH)
@Produces(MEDIATYPE_PROTOBUF) @POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public BoolValue postBatchMessage(BatchMessage batchMessage) { @Produces(MEDIATYPE_PROTOBUF)
try { @Override
init(); public BoolValue postBatchMessage(BatchMessage batchMessage) {
return bulletinBoard.postBatchMessage(batchMessage); try {
} catch (CommunicationException e) { init();
System.err.println(e.getMessage()); return bulletinBoard.postBatchMessage(batchMessage);
return null; } catch (CommunicationException e) {
} System.err.println(e.getMessage());
} return null;
}
@Path(CLOSE_BATCH_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(CLOSE_BATCH_PATH)
@Produces(MEDIATYPE_PROTOBUF) @POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public BoolValue closeBatchMessage(CloseBatchMessage message) { @Produces(MEDIATYPE_PROTOBUF)
try { @Override
init(); public BoolValue closeBatch(CloseBatchMessage message) {
return bulletinBoard.closeBatchMessage(message); try {
} catch (CommunicationException e) { init();
System.err.println(e.getMessage()); return bulletinBoard.closeBatch(message);
return null; } catch (CommunicationException e) {
} System.err.println(e.getMessage());
} return null;
}
}
@Override
public void readBatch(BatchSpecificationMessage message, MessageOutputStream<BatchData> out) {
try { @Override
init(); public void readBatch(BatchQuery batchQuery, MessageOutputStream<BatchChunk> out) throws CommunicationException, IllegalArgumentException {
bulletinBoard.readBatch(message, out); try {
} catch (CommunicationException | IllegalArgumentException e) { init();
System.err.println(e.getMessage()); bulletinBoard.readBatch(batchQuery, out);
} } catch (CommunicationException | IllegalArgumentException e) {
} System.err.println(e.getMessage());
}
@Path(GENERATE_SYNC_QUERY_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(GENERATE_SYNC_QUERY_PATH)
@Produces(MEDIATYPE_PROTOBUF) @POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException { @Produces(MEDIATYPE_PROTOBUF)
try { @Override
init(); public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
return bulletinBoard.generateSyncQuery(generateSyncQueryParams); try {
} catch (CommunicationException | IllegalArgumentException e) { init();
System.err.println(e.getMessage()); return bulletinBoard.generateSyncQuery(generateSyncQueryParams);
return null; } catch (CommunicationException | IllegalArgumentException e) {
} System.err.println(e.getMessage());
} return null;
}
@Path(READ_BATCH_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(READ_BATCH_PATH)
/** @POST
* Wrapper for the readBatch method which streams the output into the response @Consumes(MEDIATYPE_PROTOBUF)
*/ /**
public StreamingOutput readBatch(final BatchSpecificationMessage message) { * Wrapper for the readBatch method which streams the output into the response
*/
return new StreamingOutput() { public StreamingOutput readBatch(final BatchQuery batchQuery) {
@Override return new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
MessageOutputStream<BatchData> out = new MessageOutputStream<>(output); @Override
public void write(OutputStream output) throws IOException, WebApplicationException {
try { MessageOutputStream<BatchChunk> out = new MessageOutputStream<>(output);
init();
bulletinBoard.readBatch(message, out); try {
} catch (CommunicationException e) { init();
//TODO: Log bulletinBoard.readBatch(batchQuery, out);
out.writeMessage(null); } catch (CommunicationException e) {
} //TODO: Log
} out.writeMessage(null);
}
}; }
} };
@Path(SYNC_QUERY_PATH) }
@POST
@Consumes(MEDIATYPE_PROTOBUF) @Path(SYNC_QUERY_PATH)
@Produces(MEDIATYPE_PROTOBUF) @POST
@Override @Consumes(MEDIATYPE_PROTOBUF)
public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException { @Produces(MEDIATYPE_PROTOBUF)
try{ @Override
init(); public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException {
return bulletinBoard.querySync(syncQuery); try{
} catch (CommunicationException | IllegalArgumentException e) { init();
System.err.println(e.getMessage()); return bulletinBoard.querySync(syncQuery);
return null; } catch (CommunicationException | IllegalArgumentException e) {
} System.err.println(e.getMessage());
} return null;
}
@Override }
public void close(){
try { @Override
bulletinBoard.close(); public void close(){
} catch (CommunicationException e) { try {
System.err.println(e.getMessage()); bulletinBoard.close();
} } catch (CommunicationException e) {
} System.err.println(e.getMessage());
}
@GET }
@Produces(MediaType.TEXT_PLAIN)
public String test() { @GET
return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries."; @Produces(MediaType.TEXT_PLAIN)
} public String test() {
return "This BulletinBoard is up and running!\n Please consult the API documents to perform queries.";
@Override }
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ServletContext servletContext = servletContextEvent.getServletContext(); @Override
bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME); public void contextDestroyed(ServletContextEvent servletContextEvent) {
close(); ServletContext servletContext = servletContextEvent.getServletContext();
} bulletinBoard = (BulletinBoardServer) servletContext.getAttribute(BULLETIN_BOARD_ATTRIBUTE_NAME);
close();
} }
}

View File

@ -1,50 +1,50 @@
package meerkat.bulletinboard.webapp; package meerkat.bulletinboard.webapp;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.Message; import com.google.protobuf.Message;
import meerkat.bulletinboard.service.HelloProtoBuf; import meerkat.bulletinboard.service.HelloProtoBuf;
import meerkat.protobuf.Crypto.*; import meerkat.protobuf.Crypto.*;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
@Path("/proto") @Path("/proto")
public class HelloProtoWebApp { public class HelloProtoWebApp {
private HelloProtoBuf helloProtoBuf; private HelloProtoBuf helloProtoBuf;
@PostConstruct @PostConstruct
public void init() { public void init() {
helloProtoBuf = new HelloProtoBuf(); helloProtoBuf = new HelloProtoBuf();
} }
@GET @GET
@Produces(Constants.MEDIATYPE_PROTOBUF) @Produces(Constants.MEDIATYPE_PROTOBUF)
public Message hello() { public Message hello() {
byte[] b1 = { (byte) 1, (byte) 2, (byte) 3, (byte) 4 }; byte[] b1 = { (byte) 1, (byte) 2, (byte) 3, (byte) 4 };
byte[] b2 = { (byte) 11, (byte) 12, (byte) 13, (byte) 14 }; byte[] b2 = { (byte) 11, (byte) 12, (byte) 13, (byte) 14 };
byte[] b3 = {(byte) 21, (byte)22, (byte) 23, (byte) 24}; byte[] b3 = {(byte) 21, (byte)22, (byte) 23, (byte) 24};
Message msg; Message msg;
if (helloProtoBuf != null) { if (helloProtoBuf != null) {
msg = helloProtoBuf.sayHello(); msg = helloProtoBuf.sayHello();
} else { } else {
msg = BulletinBoardMessage.newBuilder() msg = BulletinBoardMessage.newBuilder()
.setMsg(UnsignedBulletinBoardMessage.newBuilder() .setMsg(UnsignedBulletinBoardMessage.newBuilder()
.addTag("Signature") .addTag("Signature")
.addTag("Trustee") .addTag("Trustee")
.setData(ByteString.copyFrom(b1)).build()) .setData(ByteString.copyFrom(b1)).build())
.addSig(Signature.newBuilder() .addSig(Signature.newBuilder()
.setType(SignatureType.DSA) .setType(SignatureType.DSA)
.setData(ByteString.copyFrom(b2)) .setData(ByteString.copyFrom(b2))
.setSignerId(ByteString.copyFrom(b3)).build()) .setSignerId(ByteString.copyFrom(b3)).build())
.build(); .build();
} }
return msg; return msg;
} }
} }

View File

@ -1,9 +0,0 @@
syntax = "proto3";
package meerkat;
option java_package = "meerkat.protobuf";
message Boolean {
bool value = 1;
}

View File

@ -1,12 +1,12 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Configure class="org.eclipse.jetty.webapp.WebAppContext">
<Call name="setAttribute"> <Call name="setAttribute">
<Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg> <Arg>org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern</Arg>
<Arg>none</Arg> <Arg>none</Arg>
</Call> </Call>
<Call name="setAttribute"> <Call name="setAttribute">
<Arg>org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern</Arg> <Arg>org.eclipse.jetty.server.webapp.WebInfIncludeJarPattern</Arg>
<Arg>none</Arg> <Arg>none</Arg>
</Call> </Call>
</Configure> </Configure>

View File

@ -1,38 +1,38 @@
<web-app> <web-app>
<servlet> <servlet>
<servlet-name>Jersey Hello World</servlet-name> <servlet-name>Jersey Hello World</servlet-name>
<servlet-class> <servlet-class>
org.glassfish.jersey.servlet.ServletContainer org.glassfish.jersey.servlet.ServletContainer
</servlet-class> </servlet-class>
<init-param> <init-param>
<param-name>jersey.config.server.provider.packages</param-name> <param-name>jersey.config.server.provider.packages</param-name>
<param-value>meerkat</param-value> <param-value>meerkat</param-value>
</init-param> </init-param>
<load-on-startup>1</load-on-startup> <load-on-startup>1</load-on-startup>
</servlet> </servlet>
<servlet-mapping> <servlet-mapping>
<servlet-name>Jersey Hello World</servlet-name> <servlet-name>Jersey Hello World</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>
</servlet-mapping> </servlet-mapping>
<context-param> <context-param>
<param-name>dbAddress</param-name> <param-name>dbAddress</param-name>
<param-value>localhost</param-value></context-param> <param-value>localhost</param-value></context-param>
<context-param> <context-param>
<param-name>dbPort</param-name> <param-name>dbPort</param-name>
<param-value>3306</param-value></context-param> <param-value>3306</param-value></context-param>
<context-param> <context-param>
<param-name>dbName</param-name> <param-name>dbName</param-name>
<param-value>meerkat</param-value></context-param> <param-value>meerkat</param-value></context-param>
<context-param> <context-param>
<param-name>username</param-name> <param-name>username</param-name>
<param-value>arbel</param-value></context-param> <param-value>arbel</param-value></context-param>
<context-param> <context-param>
<param-name>password</param-name> <param-name>password</param-name>
<param-value>mypass</param-value></context-param> <param-value>mypass</param-value></context-param>
<context-param> <context-param>
<param-name>dbType</param-name> <param-name>dbType</param-name>
<param-value>H2</param-value></context-param> <param-value>H2</param-value></context-param>
<listener> <listener>
<listener-class>meerkat.bulletinboard.webapp.BulletinBoardWebApp</listener-class> <listener-class>meerkat.bulletinboard.webapp.BulletinBoardWebApp</listener-class>
</listener> </listener>
</web-app> </web-app>

View File

@ -1,150 +1,150 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.protobuf.BoolValue; import com.google.protobuf.BoolValue;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.TextFormat; import com.google.protobuf.TextFormat;
import com.google.protobuf.Timestamp; import com.google.protobuf.Timestamp;
import meerkat.comm.MessageInputStream; import meerkat.comm.MessageInputStream;
import meerkat.protobuf.Crypto.*; import meerkat.protobuf.Crypto.*;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Comm.*; import meerkat.protobuf.Comm.*;
import static meerkat.bulletinboard.BulletinBoardConstants.*; import static meerkat.bulletinboard.BulletinBoardConstants.*;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import meerkat.rest.ProtobufMessageBodyReader; import meerkat.rest.ProtobufMessageBodyReader;
import meerkat.rest.ProtobufMessageBodyWriter; import meerkat.rest.ProtobufMessageBodyWriter;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
public class BulletinBoardSQLServerIntegrationTest { public class BulletinBoardSQLServerIntegrationTest {
private static String PROP_GETTY_URL = "gretty.httpBaseURI"; private static String PROP_GETTY_URL = "gretty.httpBaseURI";
private static String DEFAULT_BASE_URL = "http://localhost:8081"; private static String DEFAULT_BASE_URL = "http://localhost:8081";
private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
Client client; Client client;
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
System.err.println("Registering client"); System.err.println("Registering client");
client = ClientBuilder.newClient(); client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class); client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class); client.register(ProtobufMessageBodyWriter.class);
} }
@Test @Test
public void testPost() throws Exception { public void testPost() throws Exception {
byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4}; byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14}; byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24}; byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0}; byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0};
Timestamp t1 = Timestamp.newBuilder() Timestamp t1 = Timestamp.newBuilder()
.setSeconds(8276482) .setSeconds(8276482)
.setNanos(4314) .setNanos(4314)
.build(); .build();
Timestamp t2 = Timestamp.newBuilder() Timestamp t2 = Timestamp.newBuilder()
.setSeconds(987591) .setSeconds(987591)
.setNanos(1513) .setNanos(1513)
.build(); .build();
WebTarget webTarget; WebTarget webTarget;
Response response; Response response;
BoolValue bool; BoolValue bool;
BulletinBoardMessage msg; BulletinBoardMessage msg;
MessageFilterList filterList; MessageFilterList filterList;
List<BulletinBoardMessage> msgList; List<BulletinBoardMessage> msgList;
// Test writing mechanism // Test writing mechanism
System.err.println("******** Testing: " + POST_MESSAGE_PATH); System.err.println("******** Testing: " + POST_MESSAGE_PATH);
webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH); webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH);
System.err.println(webTarget.getUri()); System.err.println(webTarget.getUri());
msg = BulletinBoardMessage.newBuilder() msg = BulletinBoardMessage.newBuilder()
.setMsg(UnsignedBulletinBoardMessage.newBuilder() .setMsg(UnsignedBulletinBoardMessage.newBuilder()
.addTag("Signature") .addTag("Signature")
.addTag("Trustee") .addTag("Trustee")
.setData(ByteString.copyFrom(b1)) .setData(ByteString.copyFrom(b1))
.setTimestamp(t1) .setTimestamp(t1)
.build()) .build())
.addSig(Signature.newBuilder() .addSig(Signature.newBuilder()
.setType(SignatureType.DSA) .setType(SignatureType.DSA)
.setData(ByteString.copyFrom(b2)) .setData(ByteString.copyFrom(b2))
.setSignerId(ByteString.copyFrom(b3)) .setSignerId(ByteString.copyFrom(b3))
.build()) .build())
.addSig(Signature.newBuilder() .addSig(Signature.newBuilder()
.setType(SignatureType.ECDSA) .setType(SignatureType.ECDSA)
.setData(ByteString.copyFrom(b3)) .setData(ByteString.copyFrom(b3))
.setSignerId(ByteString.copyFrom(b2)) .setSignerId(ByteString.copyFrom(b2))
.build()) .build())
.build(); .build();
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
System.err.println(response); System.err.println(response);
bool = response.readEntity(BoolValue.class); bool = response.readEntity(BoolValue.class);
assert bool.getValue(); assert bool.getValue();
msg = BulletinBoardMessage.newBuilder() msg = BulletinBoardMessage.newBuilder()
.setMsg(UnsignedBulletinBoardMessage.newBuilder() .setMsg(UnsignedBulletinBoardMessage.newBuilder()
.addTag("Vote") .addTag("Vote")
.addTag("Trustee") .addTag("Trustee")
.setData(ByteString.copyFrom(b4)) .setData(ByteString.copyFrom(b4))
.setTimestamp(t2) .setTimestamp(t2)
.build()) .build())
.addSig(Signature.newBuilder() .addSig(Signature.newBuilder()
.setType(SignatureType.ECDSA) .setType(SignatureType.ECDSA)
.setData(ByteString.copyFrom(b4)) .setData(ByteString.copyFrom(b4))
.setSignerId(ByteString.copyFrom(b2)) .setSignerId(ByteString.copyFrom(b2))
.build()) .build())
.build(); .build();
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
System.err.println(response); System.err.println(response);
bool = response.readEntity(BoolValue.class); bool = response.readEntity(BoolValue.class);
assert bool.getValue(); assert bool.getValue();
// Test reading mechanism // Test reading mechanism
System.err.println("******** Testing: " + READ_MESSAGES_PATH); System.err.println("******** Testing: " + READ_MESSAGES_PATH);
webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); webTarget = client.target(BASE_URL).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
filterList = MessageFilterList.newBuilder() filterList = MessageFilterList.newBuilder()
.addFilter( .addFilter(
MessageFilter.newBuilder() MessageFilter.newBuilder()
.setType(FilterType.TAG) .setType(FilterType.TAG)
.setTag("Vote") .setTag("Vote")
.build() .build()
) )
.build(); .build();
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class); InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
MessageInputStream<BulletinBoardMessage> inputStream = MessageInputStream<BulletinBoardMessage> inputStream =
MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class); MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class);
msgList = inputStream.asList(); msgList = inputStream.asList();
System.err.println("List size: " + msgList.size()); System.err.println("List size: " + msgList.size());
System.err.println("This is the list:"); System.err.println("This is the list:");
for (BulletinBoardMessage message : msgList) { for (BulletinBoardMessage message : msgList) {
System.err.println(TextFormat.printToString(message)); System.err.println(TextFormat.printToString(message));
} }
assert msgList.size() == 1; assert msgList.size() == 1;
} }
} }

View File

@ -1,165 +1,154 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider;
import meerkat.bulletinboard.sqlserver.H2QueryProvider; import meerkat.bulletinboard.sqlserver.H2QueryProvider;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.Result;
import java.lang.management.ManagementFactory;
import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean;
import java.lang.management.ThreadMXBean; import java.sql.*;
import java.sql.*; import java.util.List;
import java.util.List;
import static org.junit.Assert.fail;
import static org.junit.Assert.fail;
/**
/** * Created by Arbel Deutsch Peled on 07-Dec-15.
* Created by Arbel Deutsch Peled on 07-Dec-15. */
*/ public class H2BulletinBoardServerTest {
public class H2BulletinBoardServerTest {
private final String dbName = "meerkatTest";
private final String dbName = "meerkatTest";
private GenericBulletinBoardServerTest serverTest;
private GenericBulletinBoardServerTest serverTest;
private SQLQueryProvider queryProvider;
private SQLQueryProvider queryProvider;
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
@Before
@Before public void init(){
public void init(){
System.err.println("Starting to initialize H2BulletinBoardServerTest");
System.err.println("Starting to initialize H2BulletinBoardServerTest"); long start = threadBean.getCurrentThreadCpuTime();
long start = threadBean.getCurrentThreadCpuTime();
queryProvider = new H2QueryProvider(dbName);
queryProvider = new H2QueryProvider(dbName);
try {
try {
Connection conn = queryProvider.getDataSource().getConnection();
Connection conn = queryProvider.getDataSource().getConnection(); Statement stmt = conn.createStatement();
Statement stmt = conn.createStatement();
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands();
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands();
for (String deletionQuery : deletionQueries) {
for (String deletionQuery : deletionQueries) { stmt.execute(deletionQuery);
stmt.execute(deletionQuery); }
}
} catch (SQLException e) {
} catch (SQLException e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); }
}
BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider);
BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); try {
try { bulletinBoardServer.init();
bulletinBoardServer.init("");
} catch (CommunicationException e) {
} catch (CommunicationException e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); return;
return; }
}
serverTest = new GenericBulletinBoardServerTest();
serverTest = new GenericBulletinBoardServerTest(); try {
try { serverTest.init(bulletinBoardServer);
serverTest.init(bulletinBoardServer); } catch (Exception e) {
} catch (Exception e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); }
}
long end = threadBean.getCurrentThreadCpuTime();
long end = threadBean.getCurrentThreadCpuTime(); System.err.println("Finished initializing H2BulletinBoardServerTest");
System.err.println("Finished initializing H2BulletinBoardServerTest"); System.err.println("Time of operation: " + (end - start));
System.err.println("Time of operation: " + (end - start)); }
}
@Test
@Test public void bulkTest() {
public void bulkTest() { System.err.println("Starting bulkTest of H2BulletinBoardServerTest");
System.err.println("Starting bulkTest of H2BulletinBoardServerTest"); long start = threadBean.getCurrentThreadCpuTime();
long start = threadBean.getCurrentThreadCpuTime();
try {
try { serverTest.testInsert();
serverTest.testInsert(); } catch (Exception e) {
} catch (Exception e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); }
}
try{
try{ serverTest.testSimpleTagAndSignature();
serverTest.testSimpleTagAndSignature(); } catch (Exception e) {
} catch (Exception e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); }
}
try{
try{ serverTest.testEnhancedTagsAndSignatures();
serverTest.testEnhancedTagsAndSignatures(); } catch (Exception e) {
} catch (Exception e) { System.err.println(e.getMessage());
System.err.println(e.getMessage()); fail(e.getMessage());
fail(e.getMessage()); }
}
long end = threadBean.getCurrentThreadCpuTime();
long end = threadBean.getCurrentThreadCpuTime(); System.err.println("Finished bulkTest of H2BulletinBoardServerTest");
System.err.println("Finished bulkTest of H2BulletinBoardServerTest"); System.err.println("Time of operation: " + (end - start));
System.err.println("Time of operation: " + (end - start)); }
}
@Test
@Test public void testBatch() {
public void testBatchPostAfterClose() {
try{ final int BATCH_NUM = 20;
serverTest.testBatchPostAfterClose();
} catch (Exception e) { try{
System.err.println(e.getMessage()); for (int i = 0 ; i < BATCH_NUM ; i++) {
fail(e.getMessage()); serverTest.testPostBatch();
} }
} } catch (Exception e) {
System.err.println(e.getMessage());
@Test fail(e.getMessage());
public void testBatch() { }
final int BATCH_NUM = 20; try{
serverTest.testReadBatch();
try{ } catch (Exception e) {
for (int i = 0 ; i < BATCH_NUM ; i++) { System.err.println(e.getMessage());
serverTest.testPostBatch(); fail(e.getMessage());
} }
} catch (Exception e) {
System.err.println(e.getMessage()); }
fail(e.getMessage());
} @Test
public void testSyncQuery() {
try{ try {
serverTest.testReadBatch(); serverTest.testSyncQuery();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
} }
}
}
@After
@Test public void close() {
public void testSyncQuery() { System.err.println("Starting to close H2BulletinBoardServerTest");
try { long start = threadBean.getCurrentThreadCpuTime();
serverTest.testSyncQuery();
} catch (Exception e) { serverTest.close();
System.err.println(e.getMessage());
fail(e.getMessage()); long end = threadBean.getCurrentThreadCpuTime();
} System.err.println("Finished closing H2BulletinBoardServerTest");
} System.err.println("Time of operation: " + (end - start));
}
@After
public void close() { }
System.err.println("Starting to close H2BulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime();
serverTest.close();
long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished closing H2BulletinBoardServerTest");
System.err.println("Time of operation: " + (end - start));
}
}

View File

@ -1,42 +1,42 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import meerkat.rest.ProtobufMessageBodyReader; import meerkat.rest.ProtobufMessageBodyReader;
import meerkat.rest.ProtobufMessageBodyWriter; import meerkat.rest.ProtobufMessageBodyWriter;
import org.junit.Test; import org.junit.Test;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget; import javax.ws.rs.client.WebTarget;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
/** /**
* Created by talm on 10/11/15. * Created by talm on 10/11/15.
*/ */
public class HelloProtoIntegrationTest { public class HelloProtoIntegrationTest {
private static String PROP_GETTY_URL = "gretty.httpBaseURI"; private static String PROP_GETTY_URL = "gretty.httpBaseURI";
private static String DEFAULT_BASE_URL = "http://localhost:8081/"; private static String DEFAULT_BASE_URL = "http://localhost:8081/";
private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
private static String HELLO_URL = "proto"; private static String HELLO_URL = "proto";
@Test @Test
public void testHello() throws Exception { public void testHello() throws Exception {
Client client = ClientBuilder.newClient(); Client client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class); client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class); client.register(ProtobufMessageBodyWriter.class);
WebTarget webTarget = client.target(BASE_URL).path(HELLO_URL); WebTarget webTarget = client.target(BASE_URL).path(HELLO_URL);
BulletinBoardMessage response = webTarget.request(Constants.MEDIATYPE_PROTOBUF) BulletinBoardMessage response = webTarget.request(Constants.MEDIATYPE_PROTOBUF)
.get(BulletinBoardMessage.class); .get(BulletinBoardMessage.class);
System.out.println(response.getMsg().getData()); System.out.println(response.getMsg().getData());
assertThat(response.getMsg().getData().toStringUtf8(), is("Hello World!")); assertThat(response.getMsg().getData().toStringUtf8(), is("Hello World!"));
assertThat(response.getMsg().getTagCount(), is(2)); assertThat(response.getMsg().getTagCount(), is(2));
assertThat(response.getMsg().getTag(0), is("Greetings")); assertThat(response.getMsg().getTag(0), is("Greetings"));
assertThat(response.getMsg().getTag(1), is("FirstPrograms")); assertThat(response.getMsg().getTag(1), is("FirstPrograms"));
} }
} }

View File

@ -1,172 +1,158 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer.SQLQueryProvider;
import meerkat.bulletinboard.sqlserver.MySQLQueryProvider; import meerkat.bulletinboard.sqlserver.MySQLQueryProvider;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.IOException; import java.lang.management.ManagementFactory;
import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean;
import java.lang.management.ThreadMXBean; import java.sql.Connection;
import java.lang.reflect.InvocationTargetException; import java.sql.SQLException;
import java.security.SignatureException; import java.sql.Statement;
import java.sql.Connection; import java.util.List;
import java.sql.DriverManager;
import java.sql.SQLException; import static org.junit.Assert.fail;
import java.sql.Statement;
import java.util.List; /**
* Created by Arbel Deutsch Peled on 07-Dec-15.
import static org.junit.Assert.fail; */
public class MySQLBulletinBoardServerTest {
/**
* Created by Arbel Deutsch Peled on 07-Dec-15. private final String dbAddress = "localhost";
*/ private final int dbPort = 3306;
public class MySQLBulletinBoardServerTest { private final String dbName = "meerkat";
private final String username = "arbel";
private final String dbAddress = "localhost"; private final String password = "mypass";
private final int dbPort = 3306;
private final String dbName = "meerkat"; private GenericBulletinBoardServerTest serverTest;
private final String username = "arbel";
private final String password = "mypass"; private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
private GenericBulletinBoardServerTest serverTest; @Before
public void init(){
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
System.err.println("Starting to initialize MySQLBulletinBoardServerTest");
@Before long start = threadBean.getCurrentThreadCpuTime();
public void init(){
SQLQueryProvider queryProvider = new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password);
System.err.println("Starting to initialize MySQLBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime(); try {
SQLQueryProvider queryProvider = new MySQLQueryProvider(dbAddress,dbPort,dbName,username,password); Connection conn = queryProvider.getDataSource().getConnection();
Statement stmt = conn.createStatement();
try {
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands();
Connection conn = queryProvider.getDataSource().getConnection();
Statement stmt = conn.createStatement(); for (String deletionQuery : deletionQueries) {
stmt.execute(deletionQuery);
List<String> deletionQueries = queryProvider.getSchemaDeletionCommands(); }
for (String deletionQuery : deletionQueries) { } catch (SQLException e) {
stmt.execute(deletionQuery); System.err.println(e.getMessage());
} fail(e.getMessage());
}
} catch (SQLException e) {
System.err.println(e.getMessage()); BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider);
fail(e.getMessage()); try {
} bulletinBoardServer.init();
BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(queryProvider); } catch (CommunicationException e) {
try { System.err.println(e.getMessage());
bulletinBoardServer.init(""); fail(e.getMessage());
return;
} catch (CommunicationException e) { }
System.err.println(e.getMessage());
fail(e.getMessage()); serverTest = new GenericBulletinBoardServerTest();
return; try {
} serverTest.init(bulletinBoardServer);
} catch (Exception e) {
serverTest = new GenericBulletinBoardServerTest(); System.err.println(e.getMessage());
try { fail(e.getMessage());
serverTest.init(bulletinBoardServer); }
} catch (Exception e) {
System.err.println(e.getMessage()); long end = threadBean.getCurrentThreadCpuTime();
fail(e.getMessage()); System.err.println("Finished initializing MySQLBulletinBoardServerTest");
} System.err.println("Time of operation: " + (end - start));
}
long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished initializing MySQLBulletinBoardServerTest"); @Test
System.err.println("Time of operation: " + (end - start)); public void bulkTest() {
} System.err.println("Starting bulkTest of MySQLBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime();
@Test
public void bulkTest() { try {
System.err.println("Starting bulkTest of MySQLBulletinBoardServerTest"); serverTest.testInsert();
long start = threadBean.getCurrentThreadCpuTime(); } catch (Exception e) {
System.err.println(e.getMessage());
try { fail(e.getMessage());
serverTest.testInsert(); }
} catch (Exception e) {
System.err.println(e.getMessage()); try{
fail(e.getMessage()); serverTest.testSimpleTagAndSignature();
} } catch (Exception e) {
System.err.println(e.getMessage());
try{ fail(e.getMessage());
serverTest.testSimpleTagAndSignature(); }
} catch (Exception e) {
System.err.println(e.getMessage()); try{
fail(e.getMessage()); serverTest.testEnhancedTagsAndSignatures();
} } catch (Exception e) {
System.err.println(e.getMessage());
try{ fail(e.getMessage());
serverTest.testEnhancedTagsAndSignatures(); }
} catch (Exception e) {
System.err.println(e.getMessage()); long end = threadBean.getCurrentThreadCpuTime();
fail(e.getMessage()); System.err.println("Finished bulkTest of MySQLBulletinBoardServerTest");
} System.err.println("Time of operation: " + (end - start));
}
long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished bulkTest of MySQLBulletinBoardServerTest"); @Test
System.err.println("Time of operation: " + (end - start)); public void testBatch() {
}
final int BATCH_NUM = 20;
@Test
public void testBatchPostAfterClose() { try{
try{ for (int i = 0 ; i < BATCH_NUM ; i++) {
serverTest.testBatchPostAfterClose(); serverTest.testPostBatch();
} catch (Exception e) { }
System.err.println(e.getMessage()); } catch (Exception e) {
fail(e.getMessage()); System.err.println(e.getMessage());
} fail(e.getMessage());
} }
@Test try{
public void testBatch() { serverTest.testReadBatch();
} catch (Exception e) {
final int BATCH_NUM = 20; System.err.println(e.getMessage());
fail(e.getMessage());
try{ }
for (int i = 0 ; i < BATCH_NUM ; i++) {
serverTest.testPostBatch(); }
}
} catch (Exception e) { @Test
System.err.println(e.getMessage()); public void testSyncQuery() {
fail(e.getMessage()); try {
} serverTest.testSyncQuery();
} catch (Exception e) {
try{ System.err.println(e.getMessage());
serverTest.testReadBatch(); fail(e.getMessage());
} catch (Exception e) { }
System.err.println(e.getMessage()); }
fail(e.getMessage());
} @After
public void close() {
} System.err.println("Starting to close MySQLBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime();
@Test
public void testSyncQuery() { serverTest.close();
try {
serverTest.testSyncQuery(); long end = threadBean.getCurrentThreadCpuTime();
} catch (Exception e) { System.err.println("Finished closing MySQLBulletinBoardServerTest");
System.err.println(e.getMessage()); System.err.println("Time of operation: " + (end - start));
fail(e.getMessage()); }
}
} }
@After
public void close() {
System.err.println("Starting to close MySQLBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime();
serverTest.close();
long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished closing MySQLBulletinBoardServerTest");
System.err.println("Time of operation: " + (end - start));
}
}

View File

@ -1,106 +1,129 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider; import meerkat.bulletinboard.sqlserver.SQLiteQueryProvider;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.*; import meerkat.protobuf.*;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean; import java.lang.management.ThreadMXBean;
import java.security.*; import java.security.*;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
/** /**
* Created by Arbel Deutsch Peled on 07-Dec-15. * Created by Arbel Deutsch Peled on 07-Dec-15.
*/ */
public class SQLiteBulletinBoardServerTest{ public class SQLiteBulletinBoardServerTest{
private String testFilename = "SQLiteDBTest.db"; private String testFilename = "SQLiteDBTest.db";
private GenericBulletinBoardServerTest serverTest; private GenericBulletinBoardServerTest serverTest;
private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests private final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); // Used to time the tests
@Before @Before
public void init(){ public void init(){
System.err.println("Starting to initialize SQLiteBulletinBoardServerTest"); System.err.println("Starting to initialize SQLiteBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime(); long start = threadBean.getCurrentThreadCpuTime();
File old = new File(testFilename); File old = new File(testFilename);
old.delete(); old.delete();
BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(new SQLiteQueryProvider(testFilename)); BulletinBoardServer bulletinBoardServer = new BulletinBoardSQLServer(new SQLiteQueryProvider(testFilename));
try { try {
bulletinBoardServer.init(""); bulletinBoardServer.init();
} catch (CommunicationException e) { } catch (CommunicationException e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
return; return;
} }
serverTest = new GenericBulletinBoardServerTest(); serverTest = new GenericBulletinBoardServerTest();
try { try {
serverTest.init(bulletinBoardServer); serverTest.init(bulletinBoardServer);
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
} }
long end = threadBean.getCurrentThreadCpuTime(); long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished initializing SQLiteBulletinBoardServerTest"); System.err.println("Finished initializing SQLiteBulletinBoardServerTest");
System.err.println("Time of operation: " + (end - start)); System.err.println("Time of operation: " + (end - start));
} }
@Test // @Test
public void bulkTest() { public void bulkTest() {
System.err.println("Starting bulkTest of SQLiteBulletinBoardServerTest"); System.err.println("Starting bulkTest of SQLiteBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime(); long start = threadBean.getCurrentThreadCpuTime();
try { try {
serverTest.testInsert(); serverTest.testInsert();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
} }
try{ try{
serverTest.testSimpleTagAndSignature(); serverTest.testSimpleTagAndSignature();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
} }
try{ try{
serverTest.testEnhancedTagsAndSignatures(); serverTest.testEnhancedTagsAndSignatures();
} catch (Exception e) { } catch (Exception e) {
System.err.println(e.getMessage()); System.err.println(e.getMessage());
fail(e.getMessage()); fail(e.getMessage());
} }
long end = threadBean.getCurrentThreadCpuTime(); long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished bulkTest of SQLiteBulletinBoardServerTest"); System.err.println("Finished bulkTest of SQLiteBulletinBoardServerTest");
System.err.println("Time of operation: " + (end - start)); System.err.println("Time of operation: " + (end - start));
} }
@After // @Test
public void close() { public void testBatch() {
System.err.println("Starting to close SQLiteBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime(); final int BATCH_NUM = 20;
serverTest.close(); try{
for (int i = 0 ; i < BATCH_NUM ; i++) {
long end = threadBean.getCurrentThreadCpuTime(); serverTest.testPostBatch();
System.err.println("Finished closing SQLiteBulletinBoardServerTest"); }
System.err.println("Time of operation: " + (end - start)); } catch (Exception e) {
} System.err.println(e.getMessage());
fail(e.getMessage());
} }
try{
serverTest.testReadBatch();
} catch (Exception e) {
System.err.println(e.getMessage());
fail(e.getMessage());
}
}
@After
public void close() {
System.err.println("Starting to close SQLiteBulletinBoardServerTest");
long start = threadBean.getCurrentThreadCpuTime();
serverTest.close();
long end = threadBean.getCurrentThreadCpuTime();
System.err.println("Finished closing SQLiteBulletinBoardServerTest");
System.err.println("Time of operation: " + (end - start));
}
}

View File

@ -1,223 +1,223 @@
plugins { plugins {
id "us.kirchmeier.capsule" version "1.0.1" id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.7.0' id 'com.google.protobuf' version '0.7.0'
} }
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
// Uncomment the lines below to define an application // Uncomment the lines below to define an application
// (this will also allow you to build a "fatCapsule" which includes // (this will also allow you to build a "fatCapsule" which includes
// the entire application, including all dependencies in a single jar) // the entire application, including all dependencies in a single jar)
//apply plugin: 'application' //apply plugin: 'application'
//mainClassName='your.main.ApplicationClass' //mainClassName='your.main.ApplicationClass'
// Is this a snapshot version? // Is this a snapshot version?
ext { isSnapshot = false } ext { isSnapshot = false }
ext { ext {
groupId = 'org.factcenter.meerkat' groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties // Should be set in ${HOME}/.gradle/gradle.properties
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
} }
description = "TODO: Add a description" description = "TODO: Add a description"
// Your project version // Your project version
version = "0.0" version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}" version += "${isSnapshot ? '-SNAPSHOT' : ''}"
dependencies { dependencies {
// Meerkat common // Meerkat common
compile project(':meerkat-common') compile project(':meerkat-common')
// Logging // Logging
compile 'org.slf4j:slf4j-api:1.7.7' compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2' runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2' runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs // Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+' compile 'com.google.protobuf:protobuf-java:3.+'
// Depend on test resources from meerkat-common // Depend on test resources from meerkat-common
testCompile project(path: ':meerkat-common', configuration: 'testOutput') testCompile project(path: ':meerkat-common', configuration: 'testOutput')
testCompile 'junit:junit:4.+' testCompile 'junit:junit:4.+'
runtime 'org.codehaus.groovy:groovy:2.4.+' runtime 'org.codehaus.groovy:groovy:2.4.+'
} }
/*==== You probably don't have to edit below this line =======*/ /*==== You probably don't have to edit below this line =======*/
// Setup test configuration that can appear as a dependency in // Setup test configuration that can appear as a dependency in
// other subprojects // other subprojects
configurations { configurations {
testOutput.extendsFrom (testCompile) testOutput.extendsFrom (testCompile)
} }
task testJar(type: Jar, dependsOn: testClasses) { task testJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests' classifier = 'tests'
from sourceSets.test.output from sourceSets.test.output
} }
artifacts { artifacts {
testOutput testJar testOutput testJar
} }
// The run task added by the application plugin // The run task added by the application plugin
// is also of type JavaExec. // is also of type JavaExec.
tasks.withType(JavaExec) { tasks.withType(JavaExec) {
// Assign all Java system properties from // Assign all Java system properties from
// the command line to the JavaExec task. // the command line to the JavaExec task.
systemProperties System.properties systemProperties System.properties
} }
protobuf { protobuf {
// Configure the protoc executable // Configure the protoc executable
protoc { protoc {
// Download from repositories // Download from repositories
artifact = 'com.google.protobuf:protoc:3.+' artifact = 'com.google.protobuf:protoc:3.+'
} }
} }
idea { idea {
module { module {
project.sourceSets.each { sourceSet -> project.sourceSets.each { sourceSet ->
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
println "Adding $srcDir" println "Adding $srcDir"
// add protobuf generated sources to generated source dir. // add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) { if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir) testSourceDirs += file(srcDir)
} else { } else {
sourceDirs += file(srcDir) sourceDirs += file(srcDir)
} }
generatedSourceDirs += file(srcDir) generatedSourceDirs += file(srcDir)
} }
// Don't exclude build directory // Don't exclude build directory
excludeDirs -= file(buildDir) excludeDirs -= file(buildDir)
} }
} }
/*=================================== /*===================================
* "Fat" Build targets * "Fat" Build targets
*===================================*/ *===================================*/
if (project.hasProperty('mainClassName') && (mainClassName != null)) { if (project.hasProperty('mainClassName') && (mainClassName != null)) {
task mavenCapsule(type: MavenCapsule) { task mavenCapsule(type: MavenCapsule) {
description = "Generate a capsule jar that automatically downloads and caches dependencies when run." description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
applicationClass mainClassName applicationClass mainClassName
destinationDir = buildDir destinationDir = buildDir
} }
task fatCapsule(type: FatCapsule) { task fatCapsule(type: FatCapsule) {
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
destinationDir = buildDir destinationDir = buildDir
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
applicationClass fatMain applicationClass fatMain
def testJar = hasProperty('test') def testJar = hasProperty('test')
if (hasProperty('fatmain')) { if (hasProperty('fatmain')) {
appendix = "fat-${fatMain}" appendix = "fat-${fatMain}"
} else { } else {
appendix = "fat" appendix = "fat"
} }
if (testJar) { if (testJar) {
from sourceSets.test.output from sourceSets.test.output
} }
} }
} }
/*=================================== /*===================================
* Repositories * Repositories
*===================================*/ *===================================*/
repositories { repositories {
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven { maven {
url nexusRepository url nexusRepository
if (isSnapshot) { if (isSnapshot) {
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
// Use local maven repository // Use local maven repository
mavenLocal() mavenLocal()
// Use 'maven central' for other dependencies. // Use 'maven central' for other dependencies.
mavenCentral() mavenCentral()
} }
task "info" << { task "info" << {
println "Project: ${project.name}" println "Project: ${project.name}"
println "Description: ${project.description}" println "Description: ${project.description}"
println "--------------------------" println "--------------------------"
println "GroupId: $groupId" println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println "" println ""
} }
info.description 'Print some information about project parameters' info.description 'Print some information about project parameters'
/*=================================== /*===================================
* Publishing * Publishing
*===================================*/ *===================================*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
groupId project.groupId groupId project.groupId
pom.withXml { pom.withXml {
asNode().appendNode('description', project.description) asNode().appendNode('description', project.description)
} }
from project.components.java from project.components.java
} }
} }
repositories { repositories {
maven { maven {
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
} }

View File

@ -1,46 +1,46 @@
syntax = "proto3"; syntax = "proto3";
package meerkat; package meerkat;
option java_package = "meerkat.protobuf"; option java_package = "meerkat.protobuf";
message Payload { message Payload {
enum Type { enum Type {
SHARE = 0; SHARE = 0;
COMMITMENT = 1; COMMITMENT = 1;
COMPLAINT = 2; COMPLAINT = 2;
DONE = 3; DONE = 3;
ANSWER = 4; ANSWER = 4;
YCOMMITMENT = 5; YCOMMITMENT = 5;
YCOMPLAINT = 6; YCOMPLAINT = 6;
YANSWER = 7; YANSWER = 7;
ABORT = 8; ABORT = 8;
} }
// Type of message in protocol // Type of message in protocol
Type type = 1; Type type = 1;
oneof payload_data { oneof payload_data {
IDMessage id = 5; IDMessage id = 5;
ShareMessage share = 6; ShareMessage share = 6;
CommitmentMessage commitment = 7; CommitmentMessage commitment = 7;
} }
} }
message IDMessage { message IDMessage {
int32 id = 1; int32 id = 1;
} }
message ShareMessage { message ShareMessage {
int32 i = 1; int32 i = 1;
int32 j = 2; int32 j = 2;
bytes share = 3; bytes share = 3;
// For double shares (used in GJKR protocol) // For double shares (used in GJKR protocol)
bytes share_t = 4; bytes share_t = 4;
} }
message CommitmentMessage { message CommitmentMessage {
int32 k = 1; int32 k = 1;
bytes commitment = 2; bytes commitment = 2;
} }

Binary file not shown.

View File

@ -1,6 +1,7 @@
#Fri Jan 29 21:00:29 IST 2016 #Tue Aug 05 03:26:05 IDT 2014
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
distributionSha256Sum=4647967f8de78d6d6d8093cdac50f368f8c2b8038f41a5afe1c3bce4c69219a9

10
gradlew vendored
View File

@ -42,6 +42,11 @@ case "`uname`" in
;; ;;
esac esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
@ -56,9 +61,9 @@ while [ -h "$PRG" ] ; do
fi fi
done done
SAVED="`pwd`" SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -109,7 +114,6 @@ fi
if $cygwin ; then if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"` APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath # We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`

View File

@ -1 +1 @@
/bin/ /bin/

View File

@ -1,219 +1,219 @@
plugins { plugins {
id "us.kirchmeier.capsule" version "1.0.1" id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.7.0' id 'com.google.protobuf' version '0.7.0'
} }
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'com.google.protobuf' apply plugin: 'com.google.protobuf'
apply plugin: 'eclipse' apply plugin: 'eclipse'
apply plugin: 'idea' apply plugin: 'idea'
apply plugin: 'application' apply plugin: 'application'
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
mainClassName='Demo' mainClassName='Demo'
// Is this a snapshot version? // Is this a snapshot version?
ext { isSnapshot = false } ext { isSnapshot = false }
ext { ext {
groupId = 'org.factcenter.meerkat' groupId = 'org.factcenter.meerkat'
nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/"
// Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
// Should be set in ${HOME}/.gradle/gradle.properties // Should be set in ${HOME}/.gradle/gradle.properties
nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
} }
description = "Meerkat Voting Common Library" description = "Meerkat Voting Common Library"
// Your project version // Your project version
version = "0.0" version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}" version += "${isSnapshot ? '-SNAPSHOT' : ''}"
dependencies { dependencies {
// Logging // Logging
compile 'org.slf4j:slf4j-api:1.7.7' compile 'org.slf4j:slf4j-api:1.7.7'
compile 'javax.ws.rs:javax.ws.rs-api:2.0.+' compile 'javax.ws.rs:javax.ws.rs-api:2.0.+'
runtime 'ch.qos.logback:logback-classic:1.1.2' runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2' runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs // Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+' compile 'com.google.protobuf:protobuf-java:3.+'
// ListeningExecutor // ListeningExecutor
compile 'com.google.guava:guava:15.0' compile 'com.google.guava:guava:15.0'
// Crypto // Crypto
compile 'org.factcenter.qilin:qilin:1.2+' compile 'org.factcenter.qilin:qilin:1.2+'
compile 'org.bouncycastle:bcprov-jdk15on:1.53' compile 'org.bouncycastle:bcprov-jdk15on:1.53'
testCompile 'junit:junit:4.+' testCompile 'junit:junit:4.+'
runtime 'org.codehaus.groovy:groovy:2.4.+' runtime 'org.codehaus.groovy:groovy:2.4.+'
} }
/*==== You probably don't have to edit below this line =======*/ /*==== You probably don't have to edit below this line =======*/
// Setup test configuration that can appear as a dependency in // Setup test configuration that can appear as a dependency in
// other subprojects // other subprojects
configurations { configurations {
testOutput.extendsFrom (testCompile) testOutput.extendsFrom (testCompile)
} }
task testJar(type: Jar, dependsOn: testClasses) { task testJar(type: Jar, dependsOn: testClasses) {
classifier = 'tests' classifier = 'tests'
from sourceSets.test.output from sourceSets.test.output
} }
artifacts { artifacts {
testOutput testJar testOutput testJar
} }
// The run task added by the application plugin // The run task added by the application plugin
// is also of type JavaExec. // is also of type JavaExec.
tasks.withType(JavaExec) { tasks.withType(JavaExec) {
// Assign all Java system properties from // Assign all Java system properties from
// the command line to the JavaExec task. // the command line to the JavaExec task.
systemProperties System.properties systemProperties System.properties
} }
protobuf { protobuf {
// Configure the protoc executable // Configure the protoc executable
protoc { protoc {
// Download from repositories // Download from repositories
artifact = 'com.google.protobuf:protoc:3.+' artifact = 'com.google.protobuf:protoc:3.+'
} }
} }
idea { idea {
module { module {
project.sourceSets.each { sourceSet -> project.sourceSets.each { sourceSet ->
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
// add protobuf generated sources to generated source dir. // add protobuf generated sources to generated source dir.
if ("test".equals(sourceSet.name)) { if ("test".equals(sourceSet.name)) {
testSourceDirs += file(srcDir) testSourceDirs += file(srcDir)
} else { } else {
sourceDirs += file(srcDir) sourceDirs += file(srcDir)
} }
generatedSourceDirs += file(srcDir) generatedSourceDirs += file(srcDir)
} }
// Don't exclude build directory // Don't exclude build directory
excludeDirs -= file(buildDir) excludeDirs -= file(buildDir)
} }
} }
/*=================================== /*===================================
* "Fat" Build targets * "Fat" Build targets
*===================================*/ *===================================*/
task mavenCapsule(type: MavenCapsule){ task mavenCapsule(type: MavenCapsule){
description = "Generate a capsule jar that automatically downloads and caches dependencies when run." description = "Generate a capsule jar that automatically downloads and caches dependencies when run."
applicationClass mainClassName applicationClass mainClassName
destinationDir = buildDir destinationDir = buildDir
} }
task fatCapsule(type: FatCapsule){ task fatCapsule(type: FatCapsule){
description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class"
destinationDir = buildDir destinationDir = buildDir
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
applicationClass fatMain applicationClass fatMain
def testJar = hasProperty('test') def testJar = hasProperty('test')
if (hasProperty('fatmain')) { if (hasProperty('fatmain')) {
appendix = "fat-${fatMain}" appendix = "fat-${fatMain}"
} else { } else {
appendix = "fat" appendix = "fat"
} }
if (testJar) { if (testJar) {
from sourceSets.test.output from sourceSets.test.output
} }
} }
/*=================================== /*===================================
* Repositories * Repositories
*===================================*/ *===================================*/
repositories { repositories {
mavenLocal(); mavenLocal();
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven { maven {
url nexusRepository url nexusRepository
if (isSnapshot) { if (isSnapshot) {
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
// Use 'maven central' for other dependencies. // Use 'maven central' for other dependencies.
mavenCentral() mavenCentral()
} }
task "info" << { task "info" << {
println "Project: ${project.name}" println "Project: ${project.name}"
println "Description: ${project.description}" println "Description: ${project.description}"
println "--------------------------" println "--------------------------"
println "GroupId: $groupId" println "GroupId: $groupId"
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
println "" println ""
} }
info.description 'Print some information about project parameters' info.description 'Print some information about project parameters'
/*=================================== /*===================================
* Publishing * Publishing
*===================================*/ *===================================*/
publishing { publishing {
publications { publications {
mavenJava(MavenPublication) { mavenJava(MavenPublication) {
groupId project.groupId groupId project.groupId
pom.withXml { pom.withXml {
asNode().appendNode('description', project.description) asNode().appendNode('description', project.description)
} }
from project.components.java from project.components.java
} }
} }
repositories { repositories {
maven { maven {
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
credentials { username credentials { username
password password
username nexusUser username nexusUser
password nexusPassword password nexusPassword
} }
} }
} }
} }

View File

@ -1,29 +1,29 @@
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import static meerkat.protobuf.BulletinBoardAPI.*; import static meerkat.protobuf.BulletinBoardAPI.*;
import java.io.IOException; import java.io.IOException;
/** /**
* Created by talm on 10/26/15. * Created by talm on 10/26/15.
*/ */
public class Demo { public class Demo {
public static void main(String args[]) { public static void main(String args[]) {
System.out.println("Nothing to see yet"); System.out.println("Nothing to see yet");
BulletinBoardMessage msg; BulletinBoardMessage msg;
UnsignedBulletinBoardMessage msgContents = UnsignedBulletinBoardMessage.newBuilder() UnsignedBulletinBoardMessage msgContents = UnsignedBulletinBoardMessage.newBuilder()
.addTag("test") .addTag("test")
.setData(ByteString.copyFromUtf8("some data")) .setData(ByteString.copyFromUtf8("some data"))
.build(); .build();
msg = BulletinBoardMessage.newBuilder() msg = BulletinBoardMessage.newBuilder()
.setMsg(msgContents) .setMsg(msgContents)
.build(); .build();
try { try {
msg.writeTo(System.err); msg.writeTo(System.err);
} catch (IOException e) { } catch (IOException e) {
// Ignore // Ignore
} }
} }
} }

View File

@ -1,8 +1,9 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.ByteString; import com.google.protobuf.Timestamp;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto.Signature;
import java.util.List; import java.util.List;
@ -13,6 +14,7 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient {
/** /**
* Post a message to the bulletin board in an asynchronous manner * Post a message to the bulletin board in an asynchronous manner
* The message may be broken up by the client into a batch message, depending on implementation
* @param msg is the message to be posted * @param msg is the message to be posted
* @param callback is a class containing methods to handle the result of the operation * @param callback is a class containing methods to handle the result of the operation
* @return a unique message ID for the message, that can be later used to retrieve the batch * @return a unique message ID for the message, that can be later used to retrieve the batch
@ -20,56 +22,58 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient {
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback); public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback);
/** /**
* Perform an end-to-end post of a signed batch message * Perform an end-to-end post of a message in batch form
* @param completeBatch contains all the data of the batch including the meta-data and the signature * @param completeBatch contains all the data of the batch
* @param chunkSize is the maximum size of each chunk of the message in bytes
* @param callback is a class containing methods to handle the result of the operation * @param callback is a class containing methods to handle the result of the operation
* @return a unique identifier for the batch message * @return a unique identifier for the batch message
*/ */
public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback); public MessageID postAsBatch(BulletinBoardMessage completeBatch, int chunkSize, FutureCallback<Boolean> callback);
/**
* An interface for returning an opaque identifier for a batch message
* This identifier is used to uniquely identify the batch until it is completely posted and signed
* After the batch is fully posted: it is identified by its digest (like any message)
* This can be implementation-specific (and not necessarily interchangeable between different implementations)
*/
public interface BatchIdentifier {}
/** /**
* This message informs the server about the existence of a new batch message and supplies it with the tags associated with it * This message informs the server about the existence of a new batch message and supplies it with the tags associated with it
* @param beginBatchMessage contains the data required to begin the batch * @param tags contains the tags used in the batch
* @param callback is a callback function class for handling results of the operation * @param callback is a callback function class for handling results of the operation
* it receives a BatchIdentifier for use in subsequent batch post operations
*/ */
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback); public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback);
/** /**
* This method posts batch data into an (assumed to be open) batch * This method posts batch data into an (assumed to be open) batch
* It does not close the batch * It does not close the batch
* @param signerId is the canonical form for the ID of the sender of this batch * @param batchIdentifier is the temporary batch identifier
* @param batchId is a unique (per signer) ID for this batch * @param batchChunkList is the (canonically ordered) list of data comprising the portion of the batch to be posted
* @param batchDataList is the (canonically ordered) list of data comprising the portion of the batch to be posted
* @param startPosition is the location (in the batch) of the first entry in batchDataList * @param startPosition is the location (in the batch) of the first entry in batchDataList
* (optionally used to continue interrupted post operations) * (optionally used to continue interrupted post operations)
* The first position in the batch is position 0 * The first position in the batch is position 0
* @param callback is a callback function class for handling results of the operation * @param callback is a callback function class for handling results of the operation
* @throws IllegalArgumentException if the batch identifier given was of an illegal format
*/ */
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
int startPosition, FutureCallback<Boolean> callback); int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException;
/** /**
* Overloading of the postBatchData method which starts at the first position in the batch * Overloading of the postBatchData method which starts at the first position in the batch
*/ */
public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback); public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
throws IllegalArgumentException;
/**
* Overloading of the postBatchData method which uses ByteString
*/
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback);
/**
* Overloading of the postBatchData method which uses ByteString and starts at the first position in the batch
*/
public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback);
/** /**
* Attempts to close a batch message * Attempts to close a batch message
* @param closeBatchMessage contains the data required to close the batch * @param batchIdentifier is the temporary batch identifier
* @param callback is a callback function class for handling results of the operation * @param callback is a callback function class for handling results of the operation
* @throws IllegalArgumentException if the batch identifier given was of an illegal format
*/ */
public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback); public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback)
throws IllegalArgumentException;
/** /**
* Check how "safe" a given message is in an asynchronous manner * Check how "safe" a given message is in an asynchronous manner
@ -83,18 +87,29 @@ public interface AsyncBulletinBoardClient extends BulletinBoardClient {
* Read all messages posted matching the given filter in an asynchronous manner * Read all messages posted matching the given filter in an asynchronous manner
* Note that if messages haven't been "fully posted", this might return a different * Note that if messages haven't been "fully posted", this might return a different
* set of messages in different calls. However, messages that are fully posted * set of messages in different calls. However, messages that are fully posted
* are guaranteed to be included. * are guaranteed to be included
* @param filterList return only messages that match the filters (null means no filtering). * Also: batch messages are returned as stubs.
* @param filterList return only messages that match the filters (null means no filtering)
* @param callback is a callback function class for handling results of the operation * @param callback is a callback function class for handling results of the operation
*/ */
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback); public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback);
/** /**
* Read a given batch message from the bulletin board * Read a given message from the bulletin board
* @param batchSpecificationMessage contains the data required to specify a single batch instance * If the message is a batch: returns a complete message containing the batch data as well as the metadata
* @param msgID is the ID of the message to be read
* @param callback is a callback class for handling the result of the operation * @param callback is a callback class for handling the result of the operation
*/ */
public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback); public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback);
/**
* Read batch data for a specific stub message
* @param stub is a batch message stub
* @param callback is a callback class for handling the result of the operation
* @return a new BulletinBoardMessage containing both metadata from the stub and actual data from the server
* @throws IllegalArgumentException if the received message is not a stub
*/
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException;
/** /**

View File

@ -1,20 +0,0 @@
package meerkat.bulletinboard;
import meerkat.crypto.Digest;
import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 18-Dec-15.
* Extends the Digest interface with a method for digesting Batch messages
*/
public interface BatchDigest extends Digest {
/**
* Update the digest with the batch message data (ignore the signature)
* @param completeBatch is the batch message that needs to be digested
*/
public void update(CompleteBatch completeBatch);
}

View File

@ -1,34 +0,0 @@
package meerkat.bulletinboard;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import meerkat.protobuf.BulletinBoardAPI.BatchData;
import meerkat.protobuf.Crypto.Signature;
import java.security.InvalidKeyException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 20-Dec-15.
* Extends the DigitalSignature interface with methods for signing and authenticating Batch messages
*/
public interface BatchDigitalSignature extends DigitalSignature {
/**
* Appends the batch data to the signed content (ignoring the signature)
* @param completeBatch contains all the data about the batch
* @throws SignatureException
*/
public void updateContent(CompleteBatch completeBatch) throws SignatureException;
/**
* Performs a complete verification process on the given batch message
* @param completeBatch contains the batch data as well as the signature
* @return TRUE if the batch is verified and FALSE otherwise
* @throws SignatureException | SignatureException | InvalidKeyException when underlying methods do so
*/
public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException;
}

View File

@ -1,63 +1,92 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.Voting.*; import meerkat.protobuf.Voting.*;
import static meerkat.protobuf.BulletinBoardAPI.*; import static meerkat.protobuf.BulletinBoardAPI.*;
import java.util.Collection; import java.util.List;
import java.util.List;
/**
/** * Created by talm on 24/10/15.
* Created by talm on 24/10/15. */
*/ public interface BulletinBoardClient {
public interface BulletinBoardClient {
/**
/** * Initialize the client to use some specified servers
* Initialize the client to use some specified servers * @param clientParams contains the parameters required for the client setup
* @param clientParams contains the parameters required for the client setup */
*/ void init(BulletinBoardClientParams clientParams);
void init(BulletinBoardClientParams clientParams);
/**
/** * Post a message to the bulletin board in a synchronous manner
* Post a message to the bulletin board in a synchronous manner * The message may be broken up by the client into a batch message depending on implementation
* @param msg is the message to be posted * @param msg is the message to be posted
* @return a unique message ID for the message, that can be later used to retrieve the batch * @return a unique message ID for the message, that can be later used to retrieve the batch
* @throws CommunicationException * @throws CommunicationException
*/ */
MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException; MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException;
/** /**
* Check how "safe" a given message is in a synchronous manner * Check how "safe" a given message is in a synchronous manner
* @param id is the unique message identifier for retrieval * @param id is the unique message identifier for retrieval
* @return a normalized "redundancy score" from 0 (local only) to 1 (fully published) * @return a normalized "redundancy score" from 0 (local only) to 1 (fully published)
*/ * @throws CommunicationException
float getRedundancy(MessageID id); */
float getRedundancy(MessageID id) throws CommunicationException;
/**
* Read all messages posted matching the given filter in a synchronous manner /**
* Note that if messages haven't been "fully posted", this might return a different * Read all messages posted matching the given filter in a synchronous manner
* set of messages in different calls. However, messages that are fully posted * Note that if messages haven't been "fully posted", this might return a different
* are guaranteed to be included. * set of messages in different calls. However, messages that are fully posted
* @param filterList return only messages that match the filters (null means no filtering) * are guaranteed to be included.
* @return the list of messages * Also: batch messages are returned as stubs.
*/ * @param filterList return only messages that match the filters (null means no filtering)
List<BulletinBoardMessage> readMessages(MessageFilterList filterList); * @return the list of messages
*/
/** List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException;
* Create a SyncQuery to test against that corresponds with the current server state for a specific filter list
* Should only be called on instances for which the actual server contacted is known (i.e. there is only one server) /**
* @param GenerateSyncQueryParams defines the required information needed to generate the query * Breaks up a bulletin board message into chunks and posts it as a batch message
* These are represented as fractions of the total number of relevant messages * @param msg is the message to post
* @return The generated SyncQuery * @param chunkSize is the maximal chunk size in bytes
* @throws CommunicationException when no DB can be contacted * @return the unique message ID
*/ * @throws CommunicationException if operation is unsuccessful
SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException; */
MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException;
/**
* Closes all connections, if any. /**
* This is done in a synchronous (blocking) way. * Read a given message from the bulletin board
*/ * If the message is a batch: returns a complete message containing the batch data as well as the metadata
void close(); * @param msgID is the ID of the message to be read
* @return the complete message
} * @throws CommunicationException if operation is unsuccessful
*/
BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException;
/**
* Read batch data for a specific stub message
* @param stub is a batch message stub
* @return a new BulletinBoardMessage containing both metadata from the stub and actual data from the server
* @throws CommunicationException if operation is unsuccessful
* @throws IllegalArgumentException if the received message is not a stub
*/
BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException;
/**
* Create a SyncQuery to test against that corresponds with the current server state for a specific filter list
* Should only be called on instances for which the actual server contacted is known (i.e. there is only one server)
* @param generateSyncQueryParams defines the required information needed to generate the query
* These are represented as fractions of the total number of relevant messages
* @return The generated SyncQuery
* @throws CommunicationException when no DB can be contacted
*/
SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException;
/**
* Closes all connections, if any.
* This is done in a synchronous (blocking) way.
*/
void close();
}

View File

@ -10,6 +10,7 @@ public interface BulletinBoardConstants {
public static final String BULLETIN_BOARD_SERVER_PATH = "/bbserver"; public static final String BULLETIN_BOARD_SERVER_PATH = "/bbserver";
public static final String GENERATE_SYNC_QUERY_PATH = "/generatesyncquery"; public static final String GENERATE_SYNC_QUERY_PATH = "/generatesyncquery";
public static final String READ_MESSAGES_PATH = "/readmessages"; public static final String READ_MESSAGES_PATH = "/readmessages";
public static final String COUNT_MESSAGES_PATH = "/countmessages";
public static final String READ_BATCH_PATH = "/readbatch"; public static final String READ_BATCH_PATH = "/readbatch";
public static final String POST_MESSAGE_PATH = "/postmessage"; public static final String POST_MESSAGE_PATH = "/postmessage";
public static final String BEGIN_BATCH_PATH = "/beginbatch"; public static final String BEGIN_BATCH_PATH = "/beginbatch";
@ -17,9 +18,4 @@ public interface BulletinBoardConstants {
public static final String CLOSE_BATCH_PATH = "/closebatch"; public static final String CLOSE_BATCH_PATH = "/closebatch";
public static final String SYNC_QUERY_PATH = "/syncquery"; public static final String SYNC_QUERY_PATH = "/syncquery";
// Other Constants
public static final String BATCH_TAG = "@BATCH";
public static final String BATCH_ID_TAG_PREFIX = "BATCHID#";
} }

View File

@ -0,0 +1,30 @@
package meerkat.bulletinboard;
import meerkat.crypto.Digest;
import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 18-Dec-15.
* Extends the Digest interface with methods for digesting Bulletin Board messages
*/
public interface BulletinBoardDigest extends Digest {
/**
* Update the digest with the message data (ignore the signature)
* The digest only uses the part the signatures are computed on for this operation
* If the message is a stub: this should be called before digesting the raw data
* @param msg is the message that needs to be digested
*/
public void update(BulletinBoardMessage msg);
/**
* Update the digest with the message data (ignore the signature)
* The digest only uses the part the signatures are computed on for this operation
* If the message is a stub: this should be called before digesting the raw data
* @param msg is the message that needs to be digested
*/
public void update(UnsignedBulletinBoardMessage msg);
}

View File

@ -0,0 +1,49 @@
package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
/**
* Created by Arbel Deutsch Peled on 13-Apr-16.
* This interface is meant to extend a BulletinBoardClient interface/class
* It provides it with the ability to delete messages from the Server
* This assumes the Server implements the {@link DeletableBulletinBoardServer}
*/
public interface BulletinBoardMessageDeleter {
/**
* Deletes a message from a Bulletin Board Server in a possibly asynchronous manner
* Logs this action
* @param msgID is the ID of the message to delete
* @param callback handles the result of the operation
*/
public void deleteMessage(MessageID msgID, FutureCallback<Boolean> callback);
/**
* Deletes a message from the Bulletin Board in a possibly asynchronous manner
* Logs this action
* @param entryNum is the serial entry number of the message to delete
* @param callback handles the result of the operation
*/
public void deleteMessage(long entryNum, FutureCallback<Boolean> callback);
/**
* Deletes a message from a Bulletin Board Server in a synchronous manner
* Logs this action
* @param msgID is the ID of the message to delete
* @return TRUE if the message was deleted and FALSE if it did not exist on the server
* @throws CommunicationException when an error occurs
*/
public boolean deleteMessage(MessageID msgID) throws CommunicationException;
/**
* Deletes a message from the Bulletin Board in a synchronous manner
* Logs this action
* @param entryNum is the serial entry number of the message to delete
* @return TRUE if the message was deleted and FALSE if it did not exist on the server
* @throws CommunicationException when an error occurs
*/
public boolean deleteMessage(long entryNum) throws CommunicationException;
}

View File

@ -1,104 +1,110 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.protobuf.BoolValue; import com.google.protobuf.BoolValue;
import meerkat.comm.CommunicationException; import com.google.protobuf.Int32Value;
import meerkat.comm.MessageOutputStream; import com.google.protobuf.Int64Value;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.comm.CommunicationException;
import meerkat.comm.MessageOutputStream;
import java.util.Collection; import meerkat.protobuf.BulletinBoardAPI.*;
/** /**
* Created by Arbel on 07/11/15. * Created by Arbel on 07/11/15.
* *
* This interface refers to a single instance of a Bulletin Board * This interface refers to a single instance of a Bulletin Board
* An implementation of this interface may use any DB and be hosted on any machine. * An implementation of this interface may use any DB and be hosted on any machine.
*/ */
public interface BulletinBoardServer{ public interface BulletinBoardServer{
/** /**
* This method initializes the server by reading the signature data and storing it * This method initializes the server by reading the signature data and storing it
* It also establishes the connection to the DB * It also establishes the connection to the DB
* @throws CommunicationException on DB connection error * @throws CommunicationException on DB connection error
*/ */
public void init(String meerkatDB) throws CommunicationException; public void init() throws CommunicationException;
/** /**
* Post a message to bulletin board. * Post a message to bulletin board.
* @param msg is the actual (signed) message * @param msg is the actual (signed) message
* @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form) * @return TRUE if the message has been authenticated and FALSE otherwise (in ProtoBuf form)
* @throws CommunicationException on DB connection error * @throws CommunicationException on DB connection error
*/ */
public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException; public BoolValue postMessage(BulletinBoardMessage msg) throws CommunicationException;
/** /**
* Read all messages posted matching the given filter * Read all posted messages matching the given filters
* @param filterList return only messages that match the filters (empty list or null means no filtering) * @param filterList return only messages that match the filters (empty list or null means no filtering)
* @param out is an output stream into which the matching messages are written * @param out is an output stream into which the matching messages are written
* @throws CommunicationException on DB connection error * @throws CommunicationException on DB connection error
*/ */
public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException; public void readMessages(MessageFilterList filterList, MessageOutputStream<BulletinBoardMessage> out) throws CommunicationException;
/** /**
* Informs server about a new batch message * Return the number of posted messages matching the given filters
* @param message contains the required data about the new batch * @param filterList count only messages that match the filters (empty list or null means no filtering)
* @return TRUE if the batch request is accepted amd FALSE otherwise * @return an IntMsg containing the number of messages that match the filter
* Specifically, if such a batch already exists and is not yet closed: the value returned will be TRUE * @throws CommunicationException on DB connection error
* However, if such a batch exists and is already closed: the value returned will be FALSE */
* @throws CommunicationException on DB connection error public Int32Value getMessageCount(MessageFilterList filterList) throws CommunicationException;
*/
public BoolValue beginBatch(BeginBatchMessage message) throws CommunicationException; /**
* Informs server about a new batch message
/** * @param message contains the required data about the new batch
* Posts a (part of a) batch message to the bulletin board * @return a unique batch identifier for the new batch ; -1 if batch creation was unsuccessful
* Note that the existence and contents of a batch message are not available for reading before the batch is finalized * @throws CommunicationException on DB connection error
* @param batchMessage contains the (partial) data this message carries as well as meta-data required in order to place the data */
* in the correct position inside the correct batch public Int64Value beginBatch(BeginBatchMessage message) throws CommunicationException;
* @return TRUE if the message is accepted and successfully saved and FALSE otherwise
* Specifically, if the batch is already closed: the value returned will be FALSE /**
* However, requiring to open a batch before insertion of messages is implementation-dependent * Posts a chunk of a batch message to the bulletin board
* @throws CommunicationException on DB connection error * Note that the existence and contents of a batch message are not available for reading before the batch is finalized
*/ * @param batchMessage contains the (partial) data this message carries as well as meta-data required in order to place the data
public BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException; * in the correct position inside the correct batch
* @return TRUE if the message is accepted and successfully saved and FALSE otherwise
/** * Specifically, if the batch is already closed: the value returned will be FALSE
* Attempts to stop and finalize a batch message * However, requiring to open a batch before insertion of messages is implementation-dependent
* @param message contains the data necessary to stop the batch; in particular: the signature for the batch * @throws CommunicationException on DB connection error
* @return TRUE if the batch was successfully closed, FALSE otherwise */
* Specifically, if the signature is invalid or if some of the batch parts have not yet been submitted: the value returned will be FALSE public BoolValue postBatchMessage(BatchMessage batchMessage) throws CommunicationException;
* @throws CommunicationException on DB connection error
*/ /**
public BoolValue closeBatchMessage(CloseBatchMessage message) throws CommunicationException; * Attempts to close and finalize a batch message
* @param message contains the data necessary to close the batch; in particular: the signature for the batch
/** * @return TRUE if the batch was successfully closed, FALSE otherwise
* Reads a batch message from the server (starting with the supplied position) * Specifically, if the signature is invalid or if some of the batch parts have not yet been submitted: the value returned will be FALSE
* @param message specifies the signer ID and the batch ID to read as well as an (optional) start position * @throws CommunicationException on DB connection error
* @param out is a stream of the ordered batch messages starting from the specified start position (if given) or from the beginning (if omitted) */
* @throws CommunicationException on DB connection error public BoolValue closeBatch(CloseBatchMessage message) throws CommunicationException;
* @throws IllegalArgumentException if message does not specify a batch
*/ /**
public void readBatch(BatchSpecificationMessage message, MessageOutputStream<BatchData> out) throws CommunicationException, IllegalArgumentException; * Reads a batch message from the server (starting with the supplied position)
* @param batchQuery specifies which batch and what parts of it to retrieve
/** * @param out is a stream of the ordered batch messages starting from the specified start position (if given) or from the beginning (if omitted)
* Create a SyncQuery to test against that corresponds with the current server state for a specific filter list * @throws CommunicationException on DB connection error
* @param generateSyncQueryParams defines the information needed to generate the query * @throws IllegalArgumentException if message ID does not specify a batch
* @return The generated SyncQuery */
* @throws CommunicationException on DB connection error public void readBatch(BatchQuery batchQuery, MessageOutputStream<BatchChunk> out) throws CommunicationException, IllegalArgumentException;
*/
SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException; /**
* Create a SyncQuery to test against that corresponds with the current server state for a specific filter list
/** * @param generateSyncQueryParams defines the information needed to generate the query
* Queries the database for sync status with respect to a given sync query * @return The generated SyncQuery
* @param syncQuery contains a succinct representation of states to compare to * @throws CommunicationException on DB connection error
* @return a SyncQueryResponse object containing the representation of the most recent state the database matches */
* @throws CommunicationException public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException;
*/
public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException; /**
* Queries the database for sync status with respect to a given sync query
/** * @param syncQuery contains a succinct representation of states to compare to
* This method closes the connection to the DB * @return a SyncQueryResponse object containing the representation of the most recent state the database matches
* @throws CommunicationException on DB connection error * @throws CommunicationException
*/ */
public void close() throws CommunicationException; public SyncQueryResponse querySync(SyncQuery syncQuery) throws CommunicationException;
}
/**
* This method closes the connection to the DB
* @throws CommunicationException on DB connection error
*/
public void close() throws CommunicationException;
}

View File

@ -0,0 +1,31 @@
package meerkat.bulletinboard;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
import meerkat.protobuf.Crypto;
import java.security.SignatureException;
/**
* Created by Arbel Deutsch Peled on 18-Dec-15.
* Extends the DigitalSignature interface with methods for signing Bulletin Board messages
*/
public interface BulletinBoardSignature extends DigitalSignature {
/**
* Add msg to the content stream to be verified / signed
* The digest only uses the part the signatures are computed on for this operation
* If the message is a stub: this should be called before updating with the raw data
* @param msg is the message that needs to be digested
*/
public void updateContent(BulletinBoardMessage msg) throws SignatureException;
/**
* Add msg to the content stream to be verified / signed
* If the message is a stub: this should be called before updating with the raw data
* @param msg is the message that needs to be digested
*/
public void updateContent(UnsignedBulletinBoardMessage msg) throws SignatureException;
}

View File

@ -1,22 +1,84 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
import com.google.common.util.concurrent.FutureCallback;
import java.util.List;
/** /**
* Created by Arbel Deutsch Peled on 08-Mar-16. * Created by Arbel Deutsch Peled on 08-Mar-16.
* This interface defines the behaviour of a bulletin board synchronizer * This interface defines the behaviour of a bulletin board synchronizer
* This is used to make sure that data in a specific instance of a bulletin board server is duplicated to a sufficient percentage of the other servers * This is used to make sure that data in a specific instance of a bulletin board server is duplicated to a sufficient percentage of the other servers
*/ */
public interface BulletinBoardSynchronizer extends Runnable{ public interface BulletinBoardSynchronizer extends Runnable {
public enum SyncStatus{
SYNCHRONIZED, // No more messages to upload
PENDING, // Synchronizer is querying for data to upload and uploading it as needed
SERVER_ERROR, // Synchronizer encountered an error while uploading, but will retry
STOPPED // Stopped/Not started by user
}
/** /**
* * Initializes the synchronizer with the required data to function properly
* @param localClient is a client for the local DB instance * @param localClient is a client for the temporary local storage server which contains only data to be uploaded
* @param remoteClient is a client for the remote DBs * @param remoteClient is a client for the remote servers into which the data needs to be uploaded
* @param minRedundancy
*/ */
public void init(BulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient, float minRedundancy); public void init(DeletableSubscriptionBulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient);
/**
* Returns the current server synchronization status
* @return the current synchronization status
*/
public SyncStatus getSyncStatus();
/**
* Creates a subscription to sync status changes
* @param callback is the handler for any status changes
*/
public void subscribeToSyncStatus(FutureCallback<SyncStatus> callback);
/**
* Returns the messages which have not yet been synchronized
* @return the list of messages remaining to be synchronized
*/
public List<BulletinBoardMessage> getRemainingMessages() throws CommunicationException;
/**
* Asynchronously returns the messages which have not yet been synchronized
* @param callback is the handler for the list of messages
*/
public void getRemainingMessages(FutureCallback<List<BulletinBoardMessage>> callback);
/**
* Returns the current number of unsynchronized messages
* @return the current synchronization status
*/
public long getRemainingMessagesCount() throws CommunicationException;
/**
* Creates a subscription to changes in the number of unsynchronized messages
* @param callback is the handler for any status changes
*/
public void subscribeToRemainingMessagesCount(FutureCallback<Integer> callback);
/**
* Starts the synchronization
*/
@Override @Override
public void run(); public void run();
/**
* Lets the Synchronizer know that there is new data to be uploaded
* This is used to reduce the latency between local data-writes and uploads to the remote servers
*/
public void nudge();
/**
* Stops the synchronization
*/
public void stop();
} }

View File

@ -1,150 +0,0 @@
package meerkat.bulletinboard;
import com.google.protobuf.Timestamp;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto.*;
import meerkat.util.BulletinBoardMessageComparator;
import java.util.LinkedList;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 14-Dec-15.
*
* A data structure for holding a complete batch message along with its signature
*/
public class CompleteBatch {
private BeginBatchMessage beginBatchMessage;
private List<BatchData> batchDataList;
private Signature signature;
private Timestamp timestamp;
public CompleteBatch() {
batchDataList = new LinkedList<BatchData>();
}
public CompleteBatch(BeginBatchMessage newBeginBatchMessage) {
this();
beginBatchMessage = newBeginBatchMessage;
}
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList) {
this(newBeginBatchMessage);
appendBatchData(newDataList);
}
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList, Signature newSignature) {
this(newBeginBatchMessage, newDataList);
signature = newSignature;
}
public CompleteBatch(BeginBatchMessage newBeginBatchMessage, List<BatchData> newDataList, Signature newSignature, Timestamp timestamp) {
this(newBeginBatchMessage, newDataList, newSignature);
this.timestamp = timestamp;
}
public CompleteBatch(Timestamp timestamp) {
this();
this.timestamp = timestamp;
}
public BeginBatchMessage getBeginBatchMessage() {
return beginBatchMessage;
}
public List<BatchData> getBatchDataList() {
return batchDataList;
}
public Signature getSignature() {
return signature;
}
public Timestamp getTimestamp() {
return timestamp;
}
public CloseBatchMessage getCloseBatchMessage() {
return CloseBatchMessage.newBuilder()
.setBatchId(getBeginBatchMessage().getBatchId())
.setBatchLength(getBatchDataList().size())
.setSig(getSignature())
.setTimestamp(getTimestamp())
.build();
}
public void setBeginBatchMessage(BeginBatchMessage beginBatchMessage) {
this.beginBatchMessage = beginBatchMessage;
}
public void appendBatchData(BatchData newBatchData) {
batchDataList.add(newBatchData);
}
public void appendBatchData(List<BatchData> newBatchDataList) {
batchDataList.addAll(newBatchDataList);
}
public void setSignature(Signature newSignature) {
signature = newSignature;
}
public void setTimestamp(Timestamp timestamp) {
this.timestamp = timestamp;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof CompleteBatch)) {
return false;
}
CompleteBatch otherBatch = (CompleteBatch) other;
boolean result = true;
if (beginBatchMessage == null) {
if (otherBatch.getBeginBatchMessage() != null)
return false;
} else {
result = result && beginBatchMessage.equals(otherBatch.getBeginBatchMessage());
}
if (batchDataList == null) {
if (otherBatch.getBatchDataList() != null)
return false;
} else {
result = result && batchDataList.equals(otherBatch.getBatchDataList());
}
if (signature == null) {
if (otherBatch.getSignature() != null)
return false;
} else {
result = result && signature.equals(otherBatch.getSignature());
}
if (timestamp == null) {
if (otherBatch.getTimestamp() != null)
return false;
} else {
result = result && timestamp.equals(otherBatch.getTimestamp());
}
return result;
}
@Override
public String toString() {
if (beginBatchMessage == null || beginBatchMessage.getSignerId() == null)
return "Unspecified batch " + super.toString();
return "Batch " + beginBatchMessage.getSignerId().toString() + ":" + beginBatchMessage.getBatchId();
}
}

View File

@ -0,0 +1,32 @@
package meerkat.bulletinboard;
import com.google.protobuf.BoolValue;
import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardAPI.*;
/**
* Created by Arbel Deutsch Peled on 13-Apr-16.
*/
public interface DeletableBulletinBoardServer extends BulletinBoardServer {
/**
* Deletes a message from the Bulletin Board
* If the message is a batch: the batch data is deleted as well
* Logs this action
* @param msgID is the ID of the message to delete
* @return a BoolMsg containing the value TRUE if a message was deleted, FALSE if the message does not exist
* @throws CommunicationException in case of an error
*/
public BoolValue deleteMessage(MessageID msgID) throws CommunicationException;
/**
* Deletes a message from the Bulletin Board
* If the message is a batch: the batch data is deleted as well
* Logs this action
* @param entryNum is the serial entry number of the message to delete
* @return a BoolMsg containing the value TRUE if a message was deleted, FALSE if the message does not exist
* @throws CommunicationException in case of an error
*/
public BoolValue deleteMessage(long entryNum) throws CommunicationException;
}

View File

@ -0,0 +1,7 @@
package meerkat.bulletinboard;
/**
* Created by Arbel Deutsch Peled on 13-Apr-16.
*/
public interface DeletableSubscriptionBulletinBoardClient extends SubscriptionBulletinBoardClient, BulletinBoardMessageDeleter {
}

View File

@ -1,61 +0,0 @@
package meerkat.bulletinboard;
import com.google.protobuf.Message;
import meerkat.crypto.Digest;
import meerkat.protobuf.BulletinBoardAPI.MessageID;
import meerkat.protobuf.BulletinBoardAPI.BatchData;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 19-Dec-15.
* Wrapper class for digesting Batches in a standardized way
*/
public class GenericBatchDigest implements BatchDigest{
private Digest digest;
public GenericBatchDigest(Digest digest) {
this.digest = digest;
}
@Override
public void update(CompleteBatch completeBatch) {
update(completeBatch.getBeginBatchMessage());
for (BatchData batchData : completeBatch.getBatchDataList()) {
update(batchData);
}
update(completeBatch.getTimestamp());
}
@Override
public byte[] digest() {
return digest.digest();
}
@Override
public MessageID digestAsMessageID() {
return digest.digestAsMessageID();
}
@Override
public void update(Message msg) {
digest.update(msg);
}
@Override
public void reset() {
digest.reset();
}
@Override
public GenericBatchDigest clone() throws CloneNotSupportedException{
return new GenericBatchDigest(digest.clone());
}
}

View File

@ -1,104 +0,0 @@
package meerkat.bulletinboard;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.BulletinBoardAPI.BatchData;
import meerkat.protobuf.Crypto;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.SignatureException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
/**
* Created by Arbel Deutsch Peled on 20-Dec-15.
* Wrapper class for signing and verifying Batch signatures in a standardized way
*/
public class GenericBatchDigitalSignature implements BatchDigitalSignature{
private DigitalSignature digitalSignature;
public GenericBatchDigitalSignature(DigitalSignature digitalSignature) {
this.digitalSignature = digitalSignature;
}
@Override
public void updateContent(CompleteBatch completeBatch) throws SignatureException {
digitalSignature.updateContent(completeBatch.getBeginBatchMessage());
for (BatchData batchData : completeBatch.getBatchDataList()) {
digitalSignature.updateContent(batchData);
}
digitalSignature.updateContent(completeBatch.getTimestamp());
}
@Override
public boolean verify(CompleteBatch completeBatch) throws SignatureException, CertificateException, InvalidKeyException {
digitalSignature.initVerify(completeBatch.getSignature());
updateContent(completeBatch);
return digitalSignature.verify();
}
@Override
public void loadVerificationCertificates(InputStream certStream) throws CertificateException {
digitalSignature.loadVerificationCertificates(certStream);
}
@Override
public void clearVerificationCertificates() {
digitalSignature.clearVerificationCertificates();
}
@Override
public void updateContent(Message msg) throws SignatureException {
digitalSignature.updateContent(msg);
}
@Override
public Crypto.Signature sign() throws SignatureException {
return digitalSignature.sign();
}
@Override
public void initVerify(Crypto.Signature sig) throws CertificateException, InvalidKeyException {
digitalSignature.initVerify(sig);
}
@Override
public boolean verify() {
return digitalSignature.verify();
}
@Override
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password)
throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
return digitalSignature.getPKCS12KeyStoreBuilder(keyStream, password);
}
@Override
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder) throws IOException, CertificateException, UnrecoverableKeyException {
digitalSignature.loadSigningCertificate(keyStoreBuilder);
}
@Override
public ByteString getSignerID() {
return digitalSignature.getSignerID();
}
@Override
public void clearSigningKey() {
digitalSignature.clearSigningKey();
}
}

View File

@ -0,0 +1,74 @@
package meerkat.bulletinboard;
import com.google.protobuf.ByteString;
import com.google.protobuf.BytesValue;
import com.google.protobuf.Message;
import meerkat.crypto.Digest;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardAPI.MessageID;
/**
* Created by Arbel Deutsch Peled on 19-Dec-15.
* Wrapper class for digesting Batches in a standardized way
*/
public class GenericBulletinBoardDigest implements BulletinBoardDigest {
private Digest digest;
public GenericBulletinBoardDigest(Digest digest) {
this.digest = digest;
}
@Override
public byte[] digest() {
return digest.digest();
}
@Override
public MessageID digestAsMessageID() {
return digest.digestAsMessageID();
}
@Override
public void update(Message msg) {
digest.update(msg);
}
@Override
public void update(byte[] data) {
digest.update(data);
}
@Override
public void reset() {
digest.reset();
}
@Override
public GenericBulletinBoardDigest clone() throws CloneNotSupportedException{
return new GenericBulletinBoardDigest(digest.clone());
}
@Override
public void update(BulletinBoardMessage msg) {
update(msg.getMsg());
}
@Override
public void update(UnsignedBulletinBoardMessage msg) {
for (ByteString tag : msg.getTagList().asByteStringList()){
update(tag.toByteArray());
}
update(msg.getTimestamp());
if (msg.getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.DATA){
update(msg.getData().toByteArray());
}
}
}

View File

@ -0,0 +1,106 @@
package meerkat.bulletinboard;
import com.google.protobuf.ByteString;
import com.google.protobuf.Message;
import meerkat.crypto.Digest;
import meerkat.crypto.DigitalSignature;
import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.protobuf.BulletinBoardAPI.MessageID;
import meerkat.protobuf.BulletinBoardAPI.UnsignedBulletinBoardMessage;
import meerkat.protobuf.Crypto;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
/**
* Created by Arbel Deutsch Peled on 19-Dec-15.
* Wrapper class for digesting Batches in a standardized way
*/
public class GenericBulletinBoardSignature implements BulletinBoardSignature {
private DigitalSignature signer;
public GenericBulletinBoardSignature(DigitalSignature signer) {
this.signer = signer;
}
@Override
public void updateContent(BulletinBoardMessage msg) throws SignatureException{
signer.updateContent(msg.getMsg());
}
@Override
public void updateContent(UnsignedBulletinBoardMessage msg) throws SignatureException{
for (ByteString tag : msg.getTagList().asByteStringList()){
updateContent(tag.toByteArray());
}
updateContent(msg.getTimestamp());
if (msg.getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.DATA){
updateContent(msg.getData().toByteArray());
}
}
@Override
public void loadVerificationCertificates(InputStream certStream) throws CertificateException {
signer.loadVerificationCertificates(certStream);
}
@Override
public void clearVerificationCertificates() {
signer.clearVerificationCertificates();
}
@Override
public void updateContent(byte[] data) throws SignatureException {
signer.updateContent(data);
}
@Override
public void updateContent(Message msg) throws SignatureException {
signer.updateContent(msg);
}
@Override
public Crypto.Signature sign() throws SignatureException {
return signer.sign();
}
@Override
public void initVerify(Crypto.Signature sig) throws CertificateException, InvalidKeyException {
signer.initVerify(sig);
}
@Override
public boolean verify() {
return signer.verify();
}
@Override
public KeyStore.Builder getPKCS12KeyStoreBuilder(InputStream keyStream, char[] password) throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException {
return signer.getPKCS12KeyStoreBuilder(keyStream, password);
}
@Override
public void loadSigningCertificate(KeyStore.Builder keyStoreBuilder) throws IOException, CertificateException, UnrecoverableKeyException {
signer.loadSigningCertificate(keyStoreBuilder);
}
@Override
public ByteString getSignerID() {
return signer.getSignerID();
}
@Override
public void clearSigningKey() {
signer.clearSigningKey();
}
}

View File

@ -1,7 +0,0 @@
package meerkat.bulletinboard;
/**
* Created by Arbel Deutsch Peled on 03-Mar-16.
*/
public interface SubscriptionAsyncBulletinBoardClient extends AsyncBulletinBoardClient, BulletinBoardSubscriber {
}

View File

@ -0,0 +1,7 @@
package meerkat.bulletinboard;
/**
* Created by Arbel Deutsch Peled on 03-Mar-16.
*/
public interface SubscriptionBulletinBoardClient extends AsyncBulletinBoardClient, BulletinBoardSubscriber {
}

View File

@ -1,36 +1,36 @@
package meerkat.comm; package meerkat.comm;
/** /**
* Created by talm on 24/10/15. * Created by talm on 24/10/15.
*/ */
public class CommunicationException extends Exception { public class CommunicationException extends Exception {
/** /**
* Generated serial. * Generated serial.
*/ */
private static final long serialVersionUID = 2279440129497891293L; private static final long serialVersionUID = 2279440129497891293L;
private String message; private String message;
/** /**
* Default constructor. To be used only if error type is unknown. * Default constructor. To be used only if error type is unknown.
*/ */
public CommunicationException(){ public CommunicationException(){
message = "Unknown communication exception"; message = "Unknown communication exception";
} }
/** /**
* Constructor enabling specifying of an error message. * Constructor enabling specifying of an error message.
* @param errorMessage * @param errorMessage
*/ */
public CommunicationException(String errorMessage){ public CommunicationException(String errorMessage){
message = errorMessage; message = errorMessage;
} }
/** /**
* @return the error message specified. * @return the error message specified.
*/ */
public String getMessage(){ public String getMessage(){
return message; return message;
} }
} }

View File

@ -22,6 +22,12 @@ public interface Digest {
*/ */
public MessageID digestAsMessageID(); public MessageID digestAsMessageID();
/**
* Updates the digest using the given raw data
* @param data contains the raw data
*/
public void update (byte[] data);
/** /**
* Updates the digest using the specified message (in serialized wire form) * Updates the digest using the specified message (in serialized wire form)
* *

View File

@ -39,6 +39,13 @@ public interface DigitalSignature {
*/ */
public void clearVerificationCertificates(); public void clearVerificationCertificates();
/**
* Add raw data to the content stream to be verified / signed.
*
* @param data
* @throws SignatureException
*/
public void updateContent(byte[] data) throws SignatureException;
/** /**
* Add msg to the content stream to be verified / signed. Each message is (automatically) * Add msg to the content stream to be verified / signed. Each message is (automatically)

View File

@ -140,6 +140,11 @@ public class ECDSASignature implements DigitalSignature {
signer.update(msg.toByteString().asReadOnlyByteBuffer()); signer.update(msg.toByteString().asReadOnlyByteBuffer());
} }
@Override
public void updateContent(byte[] data) throws SignatureException {
signer.update(data);
}
public void updateContent(InputStream in) throws IOException, SignatureException { public void updateContent(InputStream in) throws IOException, SignatureException {
ByteString inStr = ByteString.readFrom(in); ByteString inStr = ByteString.readFrom(in);
signer.update(inStr.asReadOnlyByteBuffer()); signer.update(inStr.asReadOnlyByteBuffer());

View File

@ -80,6 +80,7 @@ public class SHA256Digest implements Digest {
hash.update(msg.asReadOnlyByteBuffer()); hash.update(msg.asReadOnlyByteBuffer());
} }
@Override
final public void update(byte[] msg) { final public void update(byte[] msg) {
hash.update(msg); hash.update(msg);
} }

View File

@ -1,7 +1,7 @@
package meerkat.logging; package meerkat.logging;
/** /**
* Created by talm on 25/10/15. * Created by talm on 25/10/15.
*/ */
public class LogVerifier { public class LogVerifier {
} }

View File

@ -1,7 +1,7 @@
package meerkat.logging; package meerkat.logging;
/** /**
* Created by talm on 25/10/15. * Created by talm on 25/10/15.
*/ */
public class Logger { public class Logger {
} }

View File

@ -1,49 +1,83 @@
package meerkat.util; package meerkat.util;
import meerkat.protobuf.BulletinBoardAPI; import meerkat.protobuf.BulletinBoardAPI;
import meerkat.protobuf.BulletinBoardAPI.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto.*; import meerkat.protobuf.Crypto.*;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.Iterator;
import java.util.List;
/**
* Created by Arbel Deutsch Peled on 05-Dec-15. /**
* This class implements a comparison between BulletinBoardMessage instances that disregards: * Created by Arbel Deutsch Peled on 05-Dec-15.
* 1. The entry number (since this can be different between database instances) * This class implements a comparison between BulletinBoardMessage instances that disregards:
* 2. The order of the signatures * 1. The entry number (since this can be different between database instances)
*/ * 2. The order of the signatures
public class BulletinBoardMessageComparator implements Comparator<BulletinBoardMessage> { */
public class BulletinBoardMessageComparator implements Comparator<BulletinBoardMessage> {
/**
* Compare the messages /**
* @param msg1 * Compare the messages
* @param msg2 * @param msg1
* @return 0 if the messages are equivalent (see above) and -1 otherwise. * @param msg2
*/ * @return 0 if the messages are equivalent (see above) and -1 otherwise.
@Override */
public int compare(BulletinBoardMessage msg1, BulletinBoardMessage msg2) { @Override
public int compare(BulletinBoardMessage msg1, BulletinBoardMessage msg2) {
List<Signature> msg1Sigs = msg1.getSigList();
List<Signature> msg2Sigs = msg2.getSigList();
// Compare unsigned message // Compare Timestamps
if (!msg1.getMsg().equals(msg2.getMsg())){
return -1; if (!msg1.getMsg().getTimestamp().equals(msg2.getMsg().getTimestamp())){
} return -1;
}
// Compare signatures
// Compare tags (enforce order)
if (msg1Sigs.size() != msg2Sigs.size()){
return -1; List<String> tags1 = msg1.getMsg().getTagList();
} Iterator<String> tags2 = msg2.getMsg().getTagList().iterator();
for (Signature sig : msg1Sigs){ for (String tag : tags1){
if (!msg2Sigs.contains(sig)) { if (!tags2.hasNext()) {
return -1; return -1;
} }
} if (!tags2.next().equals(tag)){
return -1;
return 0; }
} }
}
// Compare data
if (msg1.getMsg().getDataTypeCase() != msg2.getMsg().getDataTypeCase()){
return -1;
}
if (msg1.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.DATA){
if (!msg1.getMsg().getData().equals(msg2.getMsg().getData())){
return -1;
}
} else if (msg1.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
if (!msg1.getMsg().getMsgId().equals(msg2.getMsg().getMsgId())){
return -1;
}
}
// Compare signatures (do not enforce order)
List<Signature> sigs1 = msg1.getSigList();
List<Signature> sigs2 = msg2.getSigList();
if (sigs1.size() != sigs2.size()){
return -1;
}
for (Signature sig : sigs1){
if (!sigs2.contains(sig)) {
return -1;
}
}
return 0;
}
}

Some files were not shown because too many files have changed in this diff Show More