Compare commits

..

No commits in common. "master" and "PollingStation-ScannerWebApp" have entirely different histories.

501 changed files with 8750 additions and 31278 deletions

54
.gitignore vendored
View File

@ -1,38 +1,16 @@
.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
*.db bulletin-board-server/local-instances/meerkat.db
*.sql
.arcconfig
/meerkat_election_params_tempfile.dat
/meerkat_booth_system_messages.dat
local.properties
# Angular junk
*/tmp
*/dist
*/out-tsc
*/node_modules
*/e2e/*.js
*/e2e/*.map
*/.sass-cache
*/connect.lock
*/coverage
*/libpeerconnection.log
npm-debug.log
testem.log
*/typings
bundle.js
bundle.d.ts

View File

@ -1,10 +0,0 @@
This file describes Wombat Code & Documentation Conventions:
Code Conventions:
* Code- The good old classic java code conventions
camelCase convention, constants should be capital letters with underscore etc...
Documentation Conventions:
* Comments- The good old classic java code documentation, Block Comments (to describe
method/class/interface etc...) // comments too describe complex code
(only if the code block complicated)

View File

@ -1,82 +0,0 @@
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "com.meerkat.laura.fakescannerapp"
minSdkVersion 14
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
// Enabling multidex support.
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
debuggable true
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
configurations.all {
resolutionStrategy.dependencySubstitution {
// Avoid Android compilation error caused by two different javax.inject dependencies.
substitute module('org.glassfish.hk2.external:javax.inject:2.4.0-b34') with module('javax.inject:javax.inject:1')
}
// Exclude non-android logback modules
exclude group:"ch.qos.logback", module:"logback-core"
exclude group:"ch.qos.logback", module:"logback-classic"
}
dependencies {
// compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':meerkat-common')
compile project(':scanner-api-common')
compile 'com.android.support:appcompat-v7:26.0.+'
compile 'com.google.protobuf:protobuf-java:3.+'
compile 'com.android.support:support-v4:26.0.+'
compile 'com.android.support:support-vector-drawable:26.0.+'
testCompile 'junit:junit:4.12'
// Android logging
compile 'com.github.tony19:logback-android-core:1.1.1-6'
compile('com.github.tony19:logback-android-classic:1.1.1-6') {
// workaround issue #73
exclude group: 'com.google.android', module: 'android'
}
}
repositories {
mavenLocal()
jcenter()
}

View File

@ -1 +0,0 @@
../gradlew

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage">
<!-- Ignore javax.mail from logback https://github.com/tony19/logback-android/issues/140-->
<ignore regexp="javax.mail"/>
</issue>
</lint>

View File

@ -1,13 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.meerkat.laura.fakescannerapp">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name=".SettingsActivity"
android:label="@string/title_activity_settings">
</activity>
</application>
</manifest>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{12}</pattern>
</tagEncoder>
<encoder>
<pattern>[%-20thread] %msg</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="logcat" />
</root>
</configuration>

View File

@ -1,508 +0,0 @@
package com.google.zxing.integration.android;
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
/**
* <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple
* way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the
* project's source code.</p>
*
* <h2>Initiating a barcode scan</h2>
*
* <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait
* for the result in your app.</p>
*
* <p>It does require that the Barcode Scanner (or work-alike) application is installed. The
* {@link #initiateScan()} method will prompt the user to download the application, if needed.</p>
*
* <p>There are a few steps to using this integration. First, your {@link Activity} must implement
* the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p>
*
* <pre>{@code
* public void onActivityResult(int requestCode, int resultCode, Intent intent) {
* IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
* if (scanResult != null) {
* // handle scan result
* }
* // else continue with any other code you need in the method
* ...
* }
* }</pre>
*
* <p>This is where you will handle a scan result.</p>
*
* <p>Second, just call this in response to a user action somewhere to begin the scan process:</p>
*
* <pre>{@code
* IntentIntegrator integrator = new IntentIntegrator(yourActivity);
* integrator.initiateScan();
* }</pre>
*
* <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the
* user was prompted to download the application. This lets the calling app potentially manage the dialog.
* In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()}
* method.</p>
*
* <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use
* {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and
* yes/no button labels can be changed.</p>
*
* <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used
* to invoke the scanner. This can be used to set additional options not directly exposed by this
* simplified API.</p>
*
* <p>By default, this will only allow applications that are known to respond to this intent correctly
* do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}.
* For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p>
*
* <h2>Sharing text via barcode</h2>
*
* <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p>
*
* <p>Some code, particularly download integration, was contributed from the Anobiit application.</p>
*
* <h2>Enabling experimental barcode formats</h2>
*
* <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as
* PDF417. Use {@link #initiateScan(Collection)} with
* a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such
* formats.</p>
*
* @author Sean Owen
* @author Fred Lin
* @author Isaac Potoczny-Jones
* @author Brad Drehmer
* @author gcstang
*/
public class IntentIntegrator {
public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
private static final String TAG = IntentIntegrator.class.getSimpleName();
public static final String DEFAULT_TITLE = "Install Barcode Scanner?";
public static final String DEFAULT_MESSAGE =
"This application requires Barcode Scanner. Would you like to install it?";
public static final String DEFAULT_YES = "Yes";
public static final String DEFAULT_NO = "No";
private static final String BS_PACKAGE = "com.google.zxing.client.android";
private static final String BSPLUS_PACKAGE = "com.srowen.bs.android";
// supported barcode formats
public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14");
public static final Collection<String> ONE_D_CODE_TYPES =
list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128",
"ITF", "RSS_14", "RSS_EXPANDED");
public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE");
public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX");
public static final Collection<String> ALL_CODE_TYPES = null;
public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE);
public static final List<String> TARGET_ALL_KNOWN = list(
BSPLUS_PACKAGE, // Barcode Scanner+
BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
BS_PACKAGE // Barcode Scanner
// What else supports this intent?
);
private final Activity activity;
private final Fragment fragment;
private String title;
private String message;
private String buttonYes;
private String buttonNo;
private List<String> targetApplications;
private final Map<String,Object> moreExtras = new HashMap<String,Object>(3);
/**
* @param activity {@link Activity} invoking the integration
*/
public IntentIntegrator(Activity activity) {
this.activity = activity;
this.fragment = null;
initializeConfiguration();
}
/**
* @param fragment {@link Fragment} invoking the integration.
* {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead
* of an {@link Activity}
*/
public IntentIntegrator(Fragment fragment) {
this.activity = fragment.getActivity();
this.fragment = fragment;
initializeConfiguration();
}
private void initializeConfiguration() {
title = DEFAULT_TITLE;
message = DEFAULT_MESSAGE;
buttonYes = DEFAULT_YES;
buttonNo = DEFAULT_NO;
targetApplications = TARGET_ALL_KNOWN;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setTitleByID(int titleID) {
title = activity.getString(titleID);
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setMessageByID(int messageID) {
message = activity.getString(messageID);
}
public String getButtonYes() {
return buttonYes;
}
public void setButtonYes(String buttonYes) {
this.buttonYes = buttonYes;
}
public void setButtonYesByID(int buttonYesID) {
buttonYes = activity.getString(buttonYesID);
}
public String getButtonNo() {
return buttonNo;
}
public void setButtonNo(String buttonNo) {
this.buttonNo = buttonNo;
}
public void setButtonNoByID(int buttonNoID) {
buttonNo = activity.getString(buttonNoID);
}
public Collection<String> getTargetApplications() {
return targetApplications;
}
public final void setTargetApplications(List<String> targetApplications) {
if (targetApplications.isEmpty()) {
throw new IllegalArgumentException("No target applications");
}
this.targetApplications = targetApplications;
}
public void setSingleTargetApplication(String targetApplication) {
this.targetApplications = Collections.singletonList(targetApplication);
}
public Map<String,?> getMoreExtras() {
return moreExtras;
}
public final void addExtra(String key, Object value) {
moreExtras.put(key, value);
}
/**
* Initiates a scan for all known barcode types with the default camera.
*
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan() {
return initiateScan(ALL_CODE_TYPES, -1);
}
/**
* Initiates a scan for all known barcode types with the specified camera.
*
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(int cameraId) {
return initiateScan(ALL_CODE_TYPES, cameraId);
}
/**
* Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise.
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) {
return initiateScan(desiredBarcodeFormats, -1);
}
/**
* Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding
* to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants
* like {@link #PRODUCT_CODE_TYPES} for example.
*
* @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for
* @param cameraId camera ID of the camera to use. A negative value means "no preference".
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) {
Intent intentScan = new Intent(BS_PACKAGE + ".SCAN");
intentScan.addCategory(Intent.CATEGORY_DEFAULT);
// check which types of codes to scan for
if (desiredBarcodeFormats != null) {
// set the desired barcode types
StringBuilder joinedByComma = new StringBuilder();
for (String format : desiredBarcodeFormats) {
if (joinedByComma.length() > 0) {
joinedByComma.append(',');
}
joinedByComma.append(format);
}
intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString());
}
// check requested camera ID
if (cameraId >= 0) {
intentScan.putExtra("SCAN_CAMERA_ID", cameraId);
}
String targetAppPackage = findTargetAppPackage(intentScan);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intentScan.setPackage(targetAppPackage);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intentScan);
startActivityForResult(intentScan, REQUEST_CODE);
return null;
}
/**
* Start an activity. This method is defined to allow different methods of activity starting for
* newer versions of Android and for compatibility library.
*
* @param intent Intent to start.
* @param code Request code for the activity
* @see Activity#startActivityForResult(Intent, int)
* @see Fragment#startActivityForResult(Intent, int)
*/
protected void startActivityForResult(Intent intent, int code) {
if (fragment == null) {
activity.startActivityForResult(intent, code);
} else {
fragment.startActivityForResult(intent, code);
}
}
private String findTargetAppPackage(Intent intent) {
PackageManager pm = activity.getPackageManager();
List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
if (availableApps != null) {
for (String targetApp : targetApplications) {
if (contains(availableApps, targetApp)) {
return targetApp;
}
}
}
return null;
}
private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) {
for (ResolveInfo availableApp : availableApps) {
String packageName = availableApp.activityInfo.packageName;
if (targetApp.equals(packageName)) {
return true;
}
}
return false;
}
private AlertDialog showDownloadDialog() {
AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity);
downloadDialog.setTitle(title);
downloadDialog.setMessage(message);
downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
String packageName;
if (targetApplications.contains(BS_PACKAGE)) {
// Prefer to suggest download of BS if it's anywhere in the list
packageName = BS_PACKAGE;
} else {
// Otherwise, first option:
packageName = targetApplications.get(0);
}
Uri uri = Uri.parse("market://details?id=" + packageName);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
try {
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
} catch (ActivityNotFoundException anfe) {
// Hmm, market is not installed
Log.w(TAG, "Google Play is not installed; cannot install " + packageName);
}
}
});
downloadDialog.setNegativeButton(buttonNo, null);
downloadDialog.setCancelable(true);
return downloadDialog.show();
}
/**
* <p>Call this from your {@link Activity}'s
* {@link Activity#onActivityResult(int, int, Intent)} method.</p>
*
* @param requestCode request code from {@code onActivityResult()}
* @param resultCode result code from {@code onActivityResult()}
* @param intent {@link Intent} from {@code onActivityResult()}
* @return null if the event handled here was not related to this class, or
* else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning,
* the fields will be null.
*/
public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
String contents = intent.getStringExtra("SCAN_RESULT");
String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT");
byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES");
int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE);
Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation;
String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL");
return new IntentResult(contents,
formatName,
rawBytes,
orientation,
errorCorrectionLevel);
}
return new IntentResult();
}
return null;
}
/**
* Defaults to type "TEXT_TYPE".
*
* @param text the text string to encode as a barcode
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
* @see #shareText(CharSequence, CharSequence)
*/
public final AlertDialog shareText(CharSequence text) {
return shareText(text, "TEXT_TYPE");
}
/**
* Shares the given text by encoding it as a barcode, such that another user can
* scan the text off the screen of the device.
*
* @param text the text string to encode as a barcode
* @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants.
* @return the {@link AlertDialog} that was shown to the user prompting them to download the app
* if a prompt was needed, or null otherwise
*/
public final AlertDialog shareText(CharSequence text, CharSequence type) {
Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setAction(BS_PACKAGE + ".ENCODE");
intent.putExtra("ENCODE_TYPE", type);
intent.putExtra("ENCODE_DATA", text);
String targetAppPackage = findTargetAppPackage(intent);
if (targetAppPackage == null) {
return showDownloadDialog();
}
intent.setPackage(targetAppPackage);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
attachMoreExtras(intent);
if (fragment == null) {
activity.startActivity(intent);
} else {
fragment.startActivity(intent);
}
return null;
}
private static List<String> list(String... values) {
return Collections.unmodifiableList(Arrays.asList(values));
}
private void attachMoreExtras(Intent intent) {
for (Map.Entry<String,Object> entry : moreExtras.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// Kind of hacky
if (value instanceof Integer) {
intent.putExtra(key, (Integer) value);
} else if (value instanceof Long) {
intent.putExtra(key, (Long) value);
} else if (value instanceof Boolean) {
intent.putExtra(key, (Boolean) value);
} else if (value instanceof Double) {
intent.putExtra(key, (Double) value);
} else if (value instanceof Float) {
intent.putExtra(key, (Float) value);
} else if (value instanceof Bundle) {
intent.putExtra(key, (Bundle) value);
} else {
intent.putExtra(key, value.toString());
}
}
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright 2009 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.integration.android;
/**
* <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p>
*
* @author Sean Owen
*/
public final class IntentResult {
private final String contents;
private final String formatName;
private final byte[] rawBytes;
private final Integer orientation;
private final String errorCorrectionLevel;
IntentResult() {
this(null, null, null, null, null);
}
IntentResult(String contents,
String formatName,
byte[] rawBytes,
Integer orientation,
String errorCorrectionLevel) {
this.contents = contents;
this.formatName = formatName;
this.rawBytes = rawBytes;
this.orientation = orientation;
this.errorCorrectionLevel = errorCorrectionLevel;
}
/**
* @return raw content of barcode
*/
public String getContents() {
return contents;
}
/**
* @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names.
*/
public String getFormatName() {
return formatName;
}
/**
* @return raw bytes of the barcode content, if applicable, or null otherwise
*/
public byte[] getRawBytes() {
return rawBytes;
}
/**
* @return rotation of the image, in degrees, which resulted in a successful scan. May be null.
*/
public Integer getOrientation() {
return orientation;
}
/**
* @return name of the error correction level used in the barcode, if applicable
*/
public String getErrorCorrectionLevel() {
return errorCorrectionLevel;
}
@Override
public String toString() {
int rawBytesLength = rawBytes == null ? 0 : rawBytes.length;
return "Format: " + formatName + '\n' +
"Contents: " + contents + '\n' +
"Raw bytes: (" + rawBytesLength + " bytes)\n" +
"Orientation: " + orientation + '\n' +
"EC level: " + errorCorrectionLevel + '\n';
}
}

View File

@ -1,109 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onStop() {
super.onStop();
getDelegate().onStop();
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
}

View File

@ -1,252 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import ch.qos.logback.classic.android.BasicLogcatConfigurator;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import meerkat.crypto.DigitalSignatureGenerator;
import meerkat.crypto.concrete.ECDSADeterministicSignature;
import meerkat.pollingstation.PollingStationScanner;
import meerkat.pollingstation.ScannerClientAPI;
import meerkat.protobuf.PollingStation;
import java.math.BigInteger;
import java.util.Date;
import static com.meerkat.laura.fakescannerapp.R.xml.preferences;
public class MainActivity extends AppCompatActivity implements View.OnClickListener, SharedPreferences.OnSharedPreferenceChangeListener {
static {
BasicLogcatConfigurator.configureDefaultContext();
}
final public static String SCANNER_NAME = "AndroidScanner";
SharedPreferences sharedPref;
Button scanBtn;
TextView formatTxt, contentTxt, responseTxt, serverStatus;
PollingStationScanner.ScannerClient scannerClient;
DigitalSignatureGenerator signer;
class AsyncScanConnect extends AsyncTask<Void, Void, Boolean> {
PollingStation.ConnectionServerData serverData;
public AsyncScanConnect(PollingStation.ConnectionServerData connectionServerData) {
this.serverData = connectionServerData;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
serverStatus.setTextColor(Color.BLUE);
serverStatus.setText("Connecting to " + serverData.getServerUrl());
}
@Override
protected Boolean doInBackground(Void... foo) {
try {
return scannerClient.connect(serverData);
} catch (RuntimeException e) {
Log.e("MainActivity", "Exception during connect: " + e.toString());
return false;
}
}
@Override
protected void onPostExecute(Boolean res) {
super.onPostExecute(res);
if (res) {
serverStatus.setTextColor(Color.GREEN);
serverStatus.setText("Connected to " + serverData.getServerUrl());
} else {
serverStatus.setTextColor(Color.RED);
serverStatus.setText("Connection failed: " + serverData.getServerUrl());
}
}
}
class AsyncScanSend extends AsyncTask<Void, Void, Boolean> {
Message data;
public AsyncScanSend(Message data) {
this.data = data;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
serverStatus.setTextColor(Color.BLUE);
serverStatus.setText("Sending to server...");
}
@Override
protected Boolean doInBackground(Void... foo) {
try {
if (data instanceof PollingStation.ScannedBallot) {
return scannerClient.newScan((PollingStation.ScannedBallot) data);
} else if (data instanceof PollingStation.ScanError) {
return scannerClient.reportError((PollingStation.ScanError) data);
} else {
Log.e("MainActivity", "Trying to send invalid message to scanner");
return false;
}
} catch (RuntimeException e) {
Log.e("MainActivity", "Exception during send: " + e.toString());
return false;
}
}
@Override
protected void onPostExecute(Boolean res) {
super.onPostExecute(res);
if (res) {
serverStatus.setTextColor(Color.GREEN);
serverStatus.setText("Sent successfully");
Log.i("MainActivity", "post submitted to API.");
} else {
serverStatus.setTextColor(Color.RED);
serverStatus.setText("Send failed");
Log.e("MainActivity", "Unable to submit post to API.");
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.action_settings:
Intent i = new Intent(this, SettingsActivity.class);
startActivity(i);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
void setNewScannerClientAPI(SharedPreferences sharedPref) {
String pscUrlString = sharedPref.getString(SettingsFragment.PSC_URL, "");
long nonce = 0;
try {
nonce = Long.parseLong(sharedPref.getString(SettingsFragment.PSC_NONCE, "0"));
} catch (NumberFormatException e) {
// Ignore
}
if (pscUrlString.isEmpty())
return;
PollingStation.ConnectionServerData serverData = PollingStation.ConnectionServerData.newBuilder()
.setServerUrl(pscUrlString)
.setNonce(nonce)
.build();
new AsyncScanConnect(serverData).execute();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
signer = new ECDSADeterministicSignature();
signer.generateSigningCertificate(BigInteger.ONE, new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365), SCANNER_NAME);
scannerClient = new ScannerClientAPI(signer);
PreferenceManager.setDefaultValues(this, preferences, false);
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
sharedPref.registerOnSharedPreferenceChangeListener(this);
scanBtn = (Button) findViewById(R.id.scan_button);
formatTxt = (TextView) findViewById(R.id.scan_format);
contentTxt = (TextView) findViewById(R.id.scan_content);
responseTxt = (TextView) findViewById(R.id.server_response);
serverStatus = (TextView) findViewById(R.id.serverStatus);
setNewScannerClientAPI(sharedPref);
scanBtn.setOnClickListener(this);
}
public void onClick(View v) {
if (v.getId() == R.id.scan_button) {
IntentIntegrator scanIntegrator = new IntentIntegrator(this);
formatTxt.setText("Initiating scan...");
scanIntegrator.initiateScan();
}
}
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
IntentResult scanningResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (scanningResult != null) {
String scanContent = scanningResult.getContents();
byte[] rawBytes = scanningResult.getRawBytes();
String scanFormat = scanningResult.getFormatName();
formatTxt.setText("FORMAT: " + scanFormat);
contentTxt.setText("CONTENT: " + scanContent + "sending data to server...");
// sendPost(rawBytes);
sendPost(scanContent);
} else {
Toast toast = Toast.makeText(getApplicationContext(),
"No scan data received!", Toast.LENGTH_SHORT);
toast.show();
}
}
// public void sendPost(byte[] body) {
public void sendPost(String scanContent) {
try {
PollingStation.ScannedBallot scannedBallot = PollingStation.ScannedBallot.parseFrom(Base64.decode(scanContent.getBytes(), Base64.DEFAULT));
//
// PollingStation.ScannedData scannedData = PollingStation.ScannedData.newBuilder()
// .setChannel(ByteString.copyFrom(body))
// .build();
new AsyncScanSend(scannedBallot).execute();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
public void showResponse(String response) {
responseTxt.setText(response);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){
setNewScannerClientAPI(sharedPreferences);
}
}

View File

@ -1,26 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
/**
* Created by talm on 25/06/17.
*/
public class SettingsActivity extends Activity {
SettingsFragment settings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
settings = new SettingsFragment();
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(settings);
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, settings)
.commit();
}
}

View File

@ -1,34 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public final static String PSC_URL = "psc_url";
public final static String PSC_NONCE = "psc_nonce";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);
if (pref == null)
return;
switch(key) {
case SettingsFragment.PSC_URL:
pref.setSummary(sharedPreferences.getString(key, ""));
break;
case SettingsFragment.PSC_NONCE:
pref.setSummary(sharedPreferences.getString(key, ""));
break;
}
}
}

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/>
</vector>

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<TextView android:text="@string/instructions" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_txt"
android:id="@+id/scan_button"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:id="@+id/scan_format"
android:layout_below="@+id/scan_button"
android:layout_centerHorizontal="true"
android:layout_marginTop="44dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:id="@+id/scan_content"
android:layout_below="@+id/scan_format"
android:layout_centerHorizontal="true"
android:layout_marginTop="32dp" />
<TextView
android:id="@+id/server_response"
android:layout_below="@+id/scan_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textIsSelectable="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="44dp" />
<TextView
android:text="Not Connected"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/serverStatus"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true" android:layout_alignParentEnd="true"
android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/>
</RelativeLayout>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Settings, should always be in the overflow -->
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
app:showAsAction="never"/>
</menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,6 +0,0 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>

View File

@ -1,5 +0,0 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -1,80 +0,0 @@
<resources>
<string name="app_name">FakeScannerApp</string>
<string name="button_txt">SCAN</string>
<string name="instructions">Please, press the button below to scan the vote receipt.</string>
<string name="title_activity_settings">Settings</string>
<!-- Strings related to Settings -->
<string name="action_settings">Settings</string>
<!-- Example General settings -->
<string name="pref_header_general">General</string>
<string name="pref_title_social_recommendations">Enable social recommendations</string>
<string name="pref_description_social_recommendations">Recommendations for people to contact based on your message
history
</string>
<string name="pref_title_psc_url">Scanner Service URL</string>
<string name="pref_default_psc_url">http://192.168.70.10:8880/scan</string>
<string name="pref_title_add_friends_to_messages">Add friends to messages</string>
<string-array name="pref_example_list_titles">
<item>Always</item>
<item>When possible</item>
<item>Never</item>
</string-array>
<string-array name="pref_example_list_values">
<item>1</item>
<item>0</item>
<item>-1</item>
</string-array>
<!-- Example settings for Data & Sync -->
<string name="pref_header_data_sync">Data &amp; sync</string>
<string name="pref_title_sync_frequency">Sync frequency</string>
<string-array name="pref_sync_frequency_titles">
<item>15 minutes</item>
<item>30 minutes</item>
<item>1 hour</item>
<item>3 hours</item>
<item>6 hours</item>
<item>Never</item>
</string-array>
<string-array name="pref_sync_frequency_values">
<item>15</item>
<item>30</item>
<item>60</item>
<item>180</item>
<item>360</item>
<item>-1</item>
</string-array>
<string-array name="list_preference_entries">
<item>Entry 1</item>
<item>Entry 2</item>
<item>Entry 3</item>
</string-array>
<string-array name="list_preference_entry_values">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="multi_select_list_preference_default_value"/>
<string name="pref_title_system_sync_settings">System sync settings</string>
<!-- Example settings for Notifications -->
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_new_message_notifications">New message notifications</string>
<string name="pref_title_ringtone">Ringtone</string>
<string name="pref_ringtone_silent">Silent</string>
<string name="pref_title_vibrate">Vibrate</string>
</resources>

View File

@ -1,11 +0,0 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@ -1,28 +0,0 @@
<PreferenceScreen xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NOTE: EditTextPreference accepts EditText attributes. -->
<!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
<EditTextPreference
android:key="psc_url"
android:title="@string/pref_title_psc_url"
android:defaultValue="@string/pref_default_psc_url"
android:selectAllOnFocus="true"
android:singleLine="true"
android:maxLines="1"
android:inputType="textUri" />
<EditTextPreference
android:key="psc_nonce"
android:title="Nonce"
android:defaultValue="0"
android:selectAllOnFocus="true"
android:singleLine="true"
android:maxLines="1"
android:inputType="number"/>
<!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
dismiss it. -->
<!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
</PreferenceScreen>

View File

@ -1,15 +0,0 @@
package com.meerkat.laura.fakescannerapp;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* To work on unit tests, switch the Test Artifact in the Build Variants view.
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}

View File

@ -1,56 +1,10 @@
apply plugin: com.google.gradle.osdetector.OsDetectorPlugin
subprojects { proj ->
proj.afterEvaluate {
subprojects { proj -> // Used to generate initial maven-dir layout
proj.afterEvaluate { task "create-dirs" { description = "Create default maven directory structure" } << {
// Used to generate initial maven-dir layout sourceSets*.java.srcDirs*.each { it.mkdirs() }
task "create-dirs" { sourceSets*.resources.srcDirs*.each { it.mkdirs() }
description = "Create default maven directory structure" }
doLast { }
sourceSets*.java.srcDirs*.each { it.mkdirs() } }
sourceSets*.resources.srcDirs*.each { it.mkdirs() }
}
}
}
}
// Script to create a copy of the protobuf compiler jar in the top-level build directory
// (will make it easier to configure the intellij protobuf plugin)
repositories {
mavenLocal();
mavenCentral();
}
project.ext.protocLocation = 'com.google.protobuf:protoc:3.+'
// Copied from Google's protobuf plugin code
File getProtocDep(protocLocation) {
// create a project configuration dependency for the artifact
Configuration config = project.configurations.create("protobufToolsLocator") {
visible = false
transitive = false
extendsFrom = []
}
def groupId, artifact, version
(groupId, artifact, version) = protocLocation.split(":")
def notation = [group: groupId,
name: artifact,
version: version,
classifier: project.osdetector.classifier,
ext: 'exe']
Dependency dep = project.dependencies.add(config.name, notation)
File file = config.fileCollection(dep).singleFile
return file
}
task getprotoc(type: Copy) {
from getProtocDep(project.ext.protocLocation)
rename { file -> return "protoc" }
into "${buildDir}/"
}

View File

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

View File

@ -1,25 +0,0 @@
apply plugin: 'groovy'
description = 'A Gradle plugin that detects the OS name and architecture, providing a uniform\
classifier to be used in the names of native artifacts.'
group = 'com.google.gradle'
// The major and minor versions are aligned with the Maven plugin's.
version = '1.5.0-SNAPSHOT'
dependencies {
compile gradleApi(),
localGroovy(),
'kr.motd.maven:os-maven-plugin:1.5.0.Final'
// Newer versions of gradle conflict with the default guava 10. dependency of the maven
// plugin.
compile("com.google.guava:guava:19.0") {
force = true
}
testCompile 'junit:junit:4.12'
}
repositories {
mavenLocal()
mavenCentral()
}

1
buildSrc/gradlew vendored
View File

@ -1 +0,0 @@
../gradlew

View File

@ -1,25 +0,0 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gradle.osdetector
import org.gradle.api.Plugin
import org.gradle.api.Project
class OsDetectorPlugin implements Plugin<Project> {
void apply(final Project project) {
project.extensions.create('osdetector', OsDetector)
}
}

View File

@ -1,120 +0,0 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gradle.osdetector;
import kr.motd.maven.os.Detector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class OsDetector {
private static final Logger logger = LoggerFactory.getLogger(OsDetector.class.getName());
private final List<String> classifierWithLikes = new ArrayList<String>();
private Impl impl;
public String getOs() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_NAME);
}
public String getArch() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_ARCH);
}
public String getClassifier() {
return (String) getImpl().detectedProperties.get(Detector.DETECTED_CLASSIFIER);
}
public Release getRelease() {
Impl impl = getImpl();
Object releaseId = impl.detectedProperties.get(Detector.DETECTED_RELEASE);
if (releaseId == null) {
return null;
}
return new Release(impl);
}
public synchronized void setClassifierWithLikes(List<String> classifierWithLikes) {
if (impl != null) {
throw new IllegalStateException("classifierWithLikes must be set before osdetector is read.");
}
this.classifierWithLikes.clear();
this.classifierWithLikes.addAll(classifierWithLikes);
}
private synchronized Impl getImpl() {
if (impl == null) {
impl = new Impl(classifierWithLikes);
}
return impl;
}
/**
* Accessor to information about the current OS release.
*/
public static class Release {
private final Impl impl;
private Release(Impl impl) {
this.impl = impl;
}
/**
* Returns the release ID.
*/
public String getId() {
return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE);
}
/**
* Returns the version ID.
*/
public String getVersion() {
return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE_VERSION);
}
/**
* Returns {@code true} if this release is a variant of the given base release (for example,
* ubuntu is "like" debian).
*/
public boolean isLike(String baseRelease) {
return impl.detectedProperties.containsKey(
Detector.DETECTED_RELEASE_LIKE_PREFIX + baseRelease);
}
}
private static class Impl extends Detector {
final Properties detectedProperties = System.getProperties();
@Override
protected void log(String message) {
logger.info(message);
}
@Override
protected void logProperty(String name, String value) {
logger.info(name + "=" + value);
}
Impl(List<String> classifierWithLikes) {
detect(detectedProperties, classifierWithLikes);
}
}
}

View File

@ -1 +0,0 @@
implementation-class=com.google.gradle.osdetector.OsDetectorPlugin

View File

@ -1,62 +0,0 @@
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gradle.osdetector
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.api.Project
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertNotNull
import static org.junit.Assert.fail
class OsDetectorPluginTest {
@Test
public void pluginAddsExtensionToProject() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'com.google.osdetector'
assertNotNull(project.osdetector)
assertNotNull(project.osdetector.os)
assertNotNull(project.osdetector.arch)
assertEquals(project.osdetector.os + '-' + project.osdetector.arch,
project.osdetector.classifier)
System.err.println('classifier=' + project.osdetector.classifier)
if (project.osdetector.os == 'linux') {
assertNotNull(project.osdetector.release.id)
assertNotNull(project.osdetector.release.version)
System.err.println('release.id=' + project.osdetector.release.id)
System.err.println('release.version=' + project.osdetector.release.version)
System.err.println('release.isLike(debian)=' + project.osdetector.release.isLike('debian'))
System.err.println('release.isLike(redhat)=' + project.osdetector.release.isLike('redhat'))
} else if (project.osdetector.release) {
fail("Should be null")
}
}
@Test
public void setClassifierWithLikes() {
Project project = ProjectBuilder.builder().build()
project.apply plugin: 'com.google.osdetector'
project.osdetector.classifierWithLikes = ['debian', 'fedora']
assertNotNull(project.osdetector.os)
assertNotNull(project.osdetector.arch)
System.err.println('classifier=' + project.osdetector.classifier)
try {
project.osdetector.classifierWithLikes = ['debian']
fail("Should throw IllegalStateException")
} catch (IllegalStateException expected) {
}
}
}

View File

@ -1,3 +0,0 @@
Manifest-Version: 1.0
Main-Class: meerkat.voting.gui.configuration.Converter

View File

@ -1,7 +1,7 @@
plugins { plugins {
id "us.kirchmeier.capsule" version '1.0.2' id "us.kirchmeier.capsule" version "1.0.1"
id 'com.google.protobuf' version '0.8.1' id 'com.google.protobuf' version '0.7.0'
} }
apply plugin: 'java' apply plugin: 'java'
@ -16,17 +16,18 @@ 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'}/"
// Credentials for publishing repositories // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing)
publishRepository = "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" // Should be set in ${HOME}/.gradle/gradle.properties
publishUser = project.hasProperty('publishUser') ? project.property('publishUser') : "" nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : ""
publishPassword = project.hasProperty('publishPassword') ? project.property('publishPassword') : "" 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.1" version = "0.0"
version += "${isSnapshot ? '-SNAPSHOT' : ''}" version += "${isSnapshot ? '-SNAPSHOT' : ''}"
@ -37,10 +38,23 @@ dependencies {
compile project(':meerkat-common') compile project(':meerkat-common')
compile project(':restful-api-common') compile project(':restful-api-common')
// Jersey for RESTful API
// Databases compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+'
compile 'org.xerial:sqlite-jdbc:3.7.+' compile 'org.xerial:sqlite-jdbc:3.7.+'
// Logging
compile 'org.slf4j:slf4j-api:1.7.7'
runtime 'ch.qos.logback:logback-classic:1.1.2'
runtime 'ch.qos.logback:logback-core:1.1.2'
// Google protobufs
compile 'com.google.protobuf:protobuf-java:3.+'
// Crypto
compile 'org.factcenter.qilin:qilin:1.1+'
compile 'org.bouncycastle:bcprov-jdk15on:1.53'
compile 'org.bouncycastle:bcpkix-jdk15on:1.53'
// 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')
@ -49,11 +63,13 @@ dependencies {
testCompile 'junit:junit:4.+' testCompile 'junit:junit:4.+'
testCompile 'org.hamcrest:hamcrest-all:1.3' testCompile 'org.hamcrest:hamcrest-all:1.3'
runtime 'org.codehaus.groovy:groovy:2.4.+'
} }
test { test {
exclude '**/*IntegrationTest*' exclude '**/*IntegrationTest*'
// outputs.upToDateWhen { false } outputs.upToDateWhen { false }
} }
task integrationTest(type: Test) { task integrationTest(type: Test) {
@ -162,22 +178,34 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) {
*===================================*/ *===================================*/
repositories { repositories {
// Use local repo if possible
mavenLocal(); mavenLocal();
// Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral)
maven {
url nexusRepository
if (isSnapshot) {
credentials { username
password
username nexusUser
password nexusPassword
}
}
}
// Use 'maven central' for other dependencies. // Use 'maven central' for other dependencies.
mavenCentral() mavenCentral()
} }
task "info" { task "info" << {
doLast { 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'
@ -199,12 +227,12 @@ publishing {
} }
repositories { repositories {
maven { maven {
url publishRepository url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
credentials { username credentials { username
password password
username publishUser username nexusUser
password publishPassword password nexusPassword
} }
} }
} }

View File

@ -1 +0,0 @@
../gradlew

View File

@ -1,7 +1,7 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import meerkat.protobuf.BulletinBoardApi.BatchChunk; import com.google.protobuf.ByteString;
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; import meerkat.protobuf.BulletinBoardAPI.BatchData;
import java.util.List; import java.util.List;
@ -11,13 +11,15 @@ import java.util.List;
*/ */
public class BatchDataContainer { public class BatchDataContainer {
public final MultiServerBatchIdentifier batchId; public final byte[] signerId;
public final List<BatchChunk> batchChunkList; public final int batchId;
public final List<BatchData> batchDataList;
public final int startPosition; public final int startPosition;
public BatchDataContainer(MultiServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) { public BatchDataContainer(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition) {
this.signerId = signerId;
this.batchId = batchId; this.batchId = batchId;
this.batchChunkList = batchChunkList; this.batchDataList = batchDataList;
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,490 +1,168 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp; import com.google.common.util.concurrent.ListeningScheduledExecutorService;
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.Crypto.Signature; import meerkat.protobuf.BulletinBoardAPI.*;
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 operations are performed on the local server * Read/write 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 SubscriptionBulletinBoardClient { public class CachedBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient {
private final AsyncBulletinBoardClient localClient; private final BulletinBoardClient localClient;
private final AsyncBulletinBoardClient remoteClient; private AsyncBulletinBoardClient remoteClient;
private final AsyncBulletinBoardClient queueClient; private BulletinBoardSubscriber subscriber;
private final BulletinBoardSubscriber subscriber;
private final BulletinBoardSynchronizer synchronizer;
private Thread syncThread; private final int threadPoolSize;
private final long failDelayInMilliseconds;
private final long subscriptionIntervalInMilliseconds;
private final static int DEFAULT_WAIT_CAP = 3000; public CachedBulletinBoardClient(BulletinBoardClient localClient,
private final static int DEFAULT_SLEEP_INTERVAL = 3000; int threadPoolSize,
long failDelayInMilliseconds,
private class SubscriptionStoreCallback implements FutureCallback<List<BulletinBoardMessage>> { long subscriptionIntervalInMilliseconds)
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.remoteClient = remoteClient; this.threadPoolSize = threadPoolSize;
this.subscriber = subscriber; this.failDelayInMilliseconds = failDelayInMilliseconds;
this.queueClient = queueClient; this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds;
this.synchronizer = new SimpleBulletinBoardSynchronizer(sleepInterval,waitCap); remoteClient = new ThreadedBulletinBoardClient();
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 postAsBatch(final BulletinBoardMessage msg, final int chunkSize, final FutureCallback<Boolean> callback) { public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
return null;
}
return localClient.postAsBatch(msg, chunkSize, new FutureCallback<Boolean>() { @Override
@Override public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
public void onSuccess(Boolean result) { return null;
remoteClient.postAsBatch(msg, chunkSize, callback); }
}
@Override @Override
public void onFailure(Throwable t) { public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
if (callback != null)
callback.onFailure(t);
}
});
} }
@Override @Override
public void beginBatch(final Iterable<String> tags, final FutureCallback<BatchIdentifier> callback) { public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> 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(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList, public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
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(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList, final FutureCallback<Boolean> callback) public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition, 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 closeBatch(final BatchIdentifier batchIdentifier, final Timestamp timestamp, final Iterable<Signature> signatures, public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
final FutureCallback<Boolean> callback) {
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){ }
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier; @Override
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 readMessages(MessageFilterList filterList, final FutureCallback<List<BulletinBoardMessage>> callback) { public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> 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
* All resources are assumed to be initialized remoteClient.init(clientParams);
*/
public void init(BulletinBoardClientParams clientParams) {} ListeningScheduledExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize));
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 localClient.postMessage(msg); return null;
} }
@Override @Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException { public float getRedundancy(MessageID id) {
MessageID result = localClient.postAsBatch(msg, chunkSize); return 0;
remoteClient.postAsBatch(msg, chunkSize);
return result;
} }
@Override @Override
public float getRedundancy(MessageID id) throws CommunicationException { public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
return remoteClient.getRedundancy(id); return null;
} }
@Override @Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException { public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) throws CommunicationException {
subscriber.subscribe(filterList, new SubscriptionStoreCallback()); return null;
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

@ -1,29 +0,0 @@
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,21 +1,20 @@
package meerkat.bulletinboard; package meerkat.bulletinboard;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.google.protobuf.Int64Value; import com.google.protobuf.ByteString;
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;
@ -23,27 +22,28 @@ 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 wraps a BulletinBoardServer in an asynchronous client. * This client is to be used mainly for testing.
* It is meant to be used as a local cache handler and for testing purposes. * It wraps a BulletinBoardServer in an asynchronous client.
* 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 DeletableSubscriptionBulletinBoardClient { public class LocalBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient{
private final DeletableBulletinBoardServer server; private final BulletinBoardServer server;
private final ListeningScheduledExecutorService executorService; private final ListeningScheduledExecutorService executorService;
private final BulletinBoardDigest digest; private final BatchDigest digest;
private final long subsrciptionDelay; private final int 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(DeletableBulletinBoardServer server, int threadNum, int subscriptionDelay) { public LocalBulletinBoardClient(BulletinBoardServer 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 GenericBulletinBoardDigest(new SHA256Digest()); this.digest = new GenericBatchDigest(new SHA256Digest());
this.subsrciptionDelay = subscriptionDelay; this.subsrciptionDelay = subscriptionDelay;
} }
@ -57,7 +57,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
@Override @Override
public Boolean call() throws CommunicationException { public Boolean call() throws Exception {
return server.postMessage(msg).getValue(); return server.postMessage(msg).getValue();
} }
@ -75,57 +75,51 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
private class CompleteBatchPoster implements Callable<Boolean> { private class CompleteBatchPoster implements Callable<Boolean> {
private final BulletinBoardMessage msg; private final CompleteBatch completeBatch;
private final int chunkSize;
public CompleteBatchPoster(BulletinBoardMessage msg, int chunkSize) { public CompleteBatchPoster(CompleteBatch completeBatch) {
this.msg = msg; this.completeBatch = completeBatch;
this.chunkSize = chunkSize;
} }
@Override @Override
public Boolean call() throws CommunicationException { public Boolean call() throws Exception {
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder() if (!server.beginBatch(completeBatch.getBeginBatchMessage()).getValue())
.addAllTag(msg.getMsg().getTagList()) return false;
.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 (BatchChunk chunk : batchChunkList){ for (BatchData data : completeBatch.getBatchDataList()){
server.postBatchMessage(builder.setSerialNum(i).setData(chunk).build()); BatchMessage message = BatchMessage.newBuilder()
.setSignerId(completeBatch.getSignature().getSignerId())
.setBatchId(completeBatch.getBeginBatchMessage().getBatchId())
.setSerialNum(i)
.setData(data)
.build();
if (!server.postBatchMessage(message).getValue())
return false;
i++; i++;
} }
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, batchChunkList.size(), msg); return server.closeBatchMessage(completeBatch.getCloseBatchMessage()).getValue();
return server.closeBatch(closeBatchMessage).getValue();
} }
} }
@Override @Override
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) { public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
Futures.addCallback(executorService.submit(new CompleteBatchPoster(msg, chunkSize)), callback); Futures.addCallback(executorService.schedule(new CompleteBatchPoster(completeBatch), subsrciptionDelay, TimeUnit.MILLISECONDS), callback);
digest.reset(); digest.update(completeBatch);
digest.update(msg);
return digest.digestAsMessageID(); return digest.digestAsMessageID();
} }
private class BatchBeginner implements Callable<SingleServerBatchIdentifier> { private class BatchBeginner implements Callable<Boolean> {
private final BeginBatchMessage msg; private final BeginBatchMessage msg;
@ -135,31 +129,28 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
@Override @Override
public SingleServerBatchIdentifier call() throws Exception { public Boolean call() throws Exception {
return new SingleServerBatchIdentifier(server.beginBatch(msg)); return server.beginBatch(msg).getValue();
} }
} }
@Override @Override
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) { public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> 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 SingleServerBatchIdentifier batchId; private final ByteString signerId;
private final List<BatchChunk> batchChunkList; private final int batchId;
private final List<BatchData> batchDataList;
private final int startPosition; private final int startPosition;
public BatchDataPoster(SingleServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) { public BatchDataPoster(ByteString signerId, int batchId, List<BatchData> batchDataList, int startPosition) {
this.signerId = signerId;
this.batchId = batchId; this.batchId = batchId;
this.batchChunkList = batchChunkList; this.batchDataList = batchDataList;
this.startPosition = startPosition; this.startPosition = startPosition;
} }
@ -168,10 +159,11 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
public Boolean call() throws Exception { public Boolean call() throws Exception {
BatchMessage.Builder msgBuilder = BatchMessage.newBuilder() BatchMessage.Builder msgBuilder = BatchMessage.newBuilder()
.setBatchId(batchId.getBatchId().getValue()); .setSignerId(signerId)
.setBatchId(batchId);
int i = startPosition; int i = startPosition;
for (BatchChunk data : batchChunkList){ for (BatchData data : batchDataList){
msgBuilder.setSerialNum(i) msgBuilder.setSerialNum(i)
.setData(data); .setData(data);
@ -183,8 +175,6 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
} }
batchId.setLength(i);
return true; return true;
} }
@ -192,28 +182,24 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
} }
@Override @Override
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition, FutureCallback<Boolean> callback) public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition, FutureCallback<Boolean> callback) {
throws IllegalArgumentException{ postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, 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
Futures.addCallback(executorService.submit(new BatchDataPoster(identifier, batchChunkList, startPosition)), callback);
} }
@Override @Override
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) throws IllegalArgumentException{ public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
postBatchData(batchId, batchChunkList, 0, callback); postBatchData(signerId, batchId, batchDataList, 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> {
@ -226,33 +212,14 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
@Override @Override
public Boolean call() throws Exception { public Boolean call() throws Exception {
return server.closeBatch(msg).getValue(); return server.closeBatchMessage(msg).getValue();
} }
} }
@Override @Override
public void closeBatch(BatchIdentifier batchId, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback) { public void closeBatch(CloseBatchMessage closeBatchMessage, 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> {
@ -343,8 +310,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user // Report new messages to user
if (callback != null) callback.onSuccess(result);
callback.onSuccess(result);
MessageFilterList.Builder filterBuilder = filterList.toBuilder(); MessageFilterList.Builder filterBuilder = filterList.toBuilder();
@ -365,7 +331,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
filterList = filterBuilder.build(); filterList = filterBuilder.build();
// Reschedule job // Reschedule job
Futures.addCallback(executorService.schedule(new MessageReader(filterList), subsrciptionDelay, TimeUnit.MILLISECONDS), this); Futures.addCallback(executorService.submit(new MessageReader(filterList)), this);
} }
@ -373,8 +339,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
// Notify caller about failure and terminate subscription // Notify caller about failure and terminate subscription
if (callback != null) callback.onFailure(t);
callback.onFailure(t);
} }
} }
@ -399,122 +364,83 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
subscribe(filterList, 0, callback); subscribe(filterList, 0, callback);
} }
private class BatchDataReader implements Callable<List<BatchChunk>> { private class CompleteBatchReader implements Callable<CompleteBatch> {
private final MessageID msgID; private final BatchSpecificationMessage batchSpecificationMessage;
public BatchDataReader(MessageID msgID) { public CompleteBatchReader(BatchSpecificationMessage batchSpecificationMessage) {
this.msgID = msgID; this.batchSpecificationMessage = batchSpecificationMessage;
} }
@Override
public List<BatchChunk> call() throws Exception {
BatchQuery batchQuery = BatchQuery.newBuilder() @Override
.setMsgID(msgID) public CompleteBatch call() throws Exception {
.setStartPosition(0)
.build(); final String[] TAGS_TO_REMOVE = {BulletinBoardConstants.BATCH_TAG, BulletinBoardConstants.BATCH_ID_TAG_PREFIX};
CompleteBatch completeBatch = new CompleteBatch(BeginBatchMessage.newBuilder()
.setSignerId(batchSpecificationMessage.getSignerId())
.setBatchId(batchSpecificationMessage.getBatchId())
.build());
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
MessageOutputStream<BatchChunk> batchOutputStream = new MessageOutputStream<>(byteOutputStream); MessageOutputStream<BatchData> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readBatch(batchQuery,batchOutputStream); server.readBatch(batchSpecificationMessage,batchOutputStream);
MessageInputStream<BatchChunk> inputStream = MessageInputStream<BatchData> batchInputStream =
MessageInputStreamFactory.createMessageInputStream( MessageInputStreamFactory.createMessageInputStream(
new ByteArrayInputStream(byteOutputStream.toByteArray()), new ByteArrayInputStream(byteOutputStream.toByteArray()),
BatchChunk.class); BatchData.class);
return inputStream.asList(); completeBatch.appendBatchData(batchInputStream.asList());
}
}
private class CompleteBatchReader implements Callable<BulletinBoardMessage> {
private final MessageID msgID;
public CompleteBatchReader(MessageID msgID) {
this.msgID = msgID;
}
@Override
public BulletinBoardMessage call() throws Exception {
// Read message (mat be a stub)
MessageFilterList filterList = MessageFilterList.newBuilder() MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder() .addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID) .setType(FilterType.TAG)
.setId(msgID.getID()) .setTag(BulletinBoardConstants.BATCH_TAG)
.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();
MessageReader messageReader = new MessageReader(filterList); byteOutputStream = new ByteArrayOutputStream();
List<BulletinBoardMessage> bulletinBoardMessages = messageReader.call(); MessageOutputStream<BulletinBoardMessage> messageOutputStream = new MessageOutputStream<>(byteOutputStream);
server.readMessages(filterList,messageOutputStream);
if (bulletinBoardMessages.size() <= 0) { MessageInputStream<BulletinBoardMessage> messageInputStream =
throw new NotFoundException("Message does not exist"); MessageInputStreamFactory.createMessageInputStream(
} new ByteArrayInputStream(byteOutputStream.toByteArray()),
BulletinBoardMessage.class);
BulletinBoardMessage msg = bulletinBoardMessages.get(0); if (!messageInputStream.isAvailable())
throw new NotFoundException("Batch does not exist");
if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) { BulletinBoardMessage message = messageInputStream.readMessage();
// Read data completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder()
.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());
BatchDataReader batchDataReader = new BatchDataReader(msgID); completeBatch.setSignature(message.getSig(0));
List<BatchChunk> batchChunkList = batchDataReader.call(); completeBatch.setTimestamp(message.getMsg().getTimestamp());
// Combine and return return completeBatch;
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 readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) { public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
Futures.addCallback(executorService.submit(new CompleteBatchReader(msgID)), callback); Futures.addCallback(executorService.submit(new CompleteBatchReader(batchSpecificationMessage)), 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> {
@ -548,26 +474,17 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
@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.getMsg()); digest.update(msg);
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();
} }
@ -586,7 +503,7 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
} }
@Override @Override
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{ public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
try { try {
@ -594,89 +511,14 @@ public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBo
return reader.call(); return reader.call();
} catch (Exception e){ } catch (Exception e){
throw new CommunicationException("Error reading from server"); return null;
} }
} }
@Override @Override
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException { public SyncQuery generateSyncQuery(GenerateSyncQueryParams GenerateSyncQueryParams) 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

@ -1,27 +0,0 @@
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>{
protected final List<SingleServerBulletinBoardClient> clients; private 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,8 +74,7 @@ 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)) {
if (futureCallback != null) futureCallback.onSuccess(result);
futureCallback.onSuccess(result);
} }
} }
@ -86,11 +85,18 @@ 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)) {
if (futureCallback != null) futureCallback.onFailure(t);
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,329 +1,192 @@
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.Int64Value; import meerkat.comm.CommunicationException;
import meerkat.bulletinboard.workers.singleserver.*; import meerkat.crypto.Digest;
import meerkat.comm.CommunicationException; import meerkat.crypto.concrete.SHA256Digest;
import meerkat.crypto.concrete.SHA256Digest; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.BulletinBoardApi; import meerkat.protobuf.Comm.*;
import meerkat.protobuf.BulletinBoardApi.*; import meerkat.protobuf.Voting.*;
import meerkat.protobuf.Voting.*; import meerkat.rest.*;
import meerkat.rest.*;
import meerkat.util.BulletinBoardUtils; import java.util.List;
import java.util.List; import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client; import javax.ws.rs.client.Entity;
import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget;
import javax.ws.rs.client.Entity; import javax.ws.rs.core.Response;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response; import static meerkat.bulletinboard.BulletinBoardConstants.*;
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. */
* Implements BulletinBoardClient interface in a simple, straightforward manner public class SimpleBulletinBoardClient implements BulletinBoardClient{
*/
public class SimpleBulletinBoardClient implements BulletinBoardClient{ protected List<String> meerkatDBs;
protected List<String> meerkatDBs; protected Client client;
protected Client client; protected Digest digest;
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 */
* @param clientParams contains the data needed to access the DBs @Override
*/ public void init(BulletinBoardClientParams clientParams) {
@Override
public void init(BulletinBoardClientParams clientParams) { this.meerkatDBs = clientParams.getBulletinBoardAddressList();
this.meerkatDBs = clientParams.getBulletinBoardAddressList(); client = ClientBuilder.newClient();
client.register(ProtobufMessageBodyReader.class);
client = ClientBuilder.newClient(); client.register(ProtobufMessageBodyWriter.class);
client.register(ProtobufMessageBodyReader.class);
client.register(ProtobufMessageBodyWriter.class); digest = new SHA256Digest();
// 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,
* Post message to all DBs * @return the message ID for later retrieval
* Make only one try per DB. * @throws CommunicationException
* @param msg is the message, */
* @return the message ID for later retrieval @Override
* @throws CommunicationException public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
*/
@Override WebTarget webTarget;
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { Response response;
WebTarget webTarget; // Post message to all databases
Response response = null; try {
for (String db : meerkatDBs) {
// Post message to all databases webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH);
try { response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
for (String db : meerkatDBs) {
// Only consider valid responses
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(db, msg, 0); if (response.getStatusInfo() == Response.Status.OK
|| response.getStatusInfo() == Response.Status.CREATED) {
worker.call(); response.readEntity(BoolValue.class).getValue();
}
} }
} 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) throws CommunicationException{ public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
// Replace null filter list with blank one. WebTarget webTarget;
if (filterList == null){ Response response;
filterList = MessageFilterList.getDefaultInstance(); BulletinBoardMessageList messageList;
}
// Replace null filter list with blank one.
String exceptionString = ""; if (filterList == null){
filterList = MessageFilterList.newBuilder().build();
for (String db : meerkatDBs) { }
try { for (String db : meerkatDBs) {
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0); try {
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) {
//TODO: log if (messageList != null){
exceptionString += e.getMessage() + "\n"; return messageList.getMessageList();
} }
}
} 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 { }
List<BatchChunk> chunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize); @Override
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
BeginBatchMessage beginBatchMessage = BulletinBoardUtils.generateBeginBatchMessage(msg);
WebTarget webTarget;
boolean posted = false; Response response;
// Post message to all databases for (String db : meerkatDBs) {
for (String db : meerkatDBs) { try {
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 =
new SingleServerPostBatchWorker( public void close() {
db, client.close();
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

@ -1,241 +0,0 @@
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

@ -1,42 +0,0 @@
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,19 +4,16 @@ 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.Int64Value; import com.google.protobuf.ByteString;
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 javax.ws.rs.client.Client; import java.util.Arrays;
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;
@ -31,23 +28,19 @@ 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 implements SubscriptionBulletinBoardClient { public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient implements SubscriptionAsyncBulletinBoardClient {
protected Client client;
protected BulletinBoardDigest digest;
private String dbAddress;
private final int MAX_RETRIES = 11; private final int MAX_RETRIES = 11;
private final ListeningScheduledExecutorService executorService; private ListeningScheduledExecutorService executorService;
protected BatchDigest batchDigest;
private long lastServerErrorTime; private long lastServerErrorTime;
private final long FAIL_DELAY_IN_MILLISECONDS; private final long failDelayInMilliseconds;
private final long SUBSCRIPTION_INTERVAL_IN_MILLISECONDS; private final long subscriptionIntervalInMilliseconds;
/** /**
* Notify the client that a job has failed * Notify the client that a job has failed
@ -60,43 +53,6 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
} }
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
@ -108,7 +64,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime; long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime;
if (timeSinceLastServerError >= FAIL_DELAY_IN_MILLISECONDS) { if (timeSinceLastServerError >= failDelayInMilliseconds) {
// Schedule for immediate processing // Schedule for immediate processing
Futures.addCallback(executorService.submit(worker), callback); Futures.addCallback(executorService.submit(worker), callback);
@ -118,7 +74,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
// Schedule for processing immediately following delay expiry // Schedule for processing immediately following delay expiry
Futures.addCallback(executorService.schedule( Futures.addCallback(executorService.schedule(
worker, worker,
FAIL_DELAY_IN_MILLISECONDS - timeSinceLastServerError, failDelayInMilliseconds - timeSinceLastServerError,
TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS),
callback); callback);
@ -141,8 +97,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
@Override @Override
public void onSuccess(T result) { public void onSuccess(T result) {
if (futureCallback != null) futureCallback.onSuccess(result);
futureCallback.onSuccess(result);
} }
@Override @Override
@ -160,8 +115,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
scheduleWorker(worker, this); scheduleWorker(worker, this);
} else { } else {
// No more retries: notify caller about failure // No more retries: notify caller about failure
if (futureCallback != null) futureCallback.onFailure(t);
futureCallback.onFailure(t);
} }
} }
@ -173,14 +127,14 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
* 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 PostBatchChunkListCallback implements FutureCallback<Boolean> { class PostBatchDataListCallback 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 PostBatchChunkListCallback(int batchDataLength, FutureCallback<Boolean> callback) { public PostBatchDataListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
this.callback = callback; this.callback = callback;
this.batchDataRemaining = new AtomicInteger(batchDataLength); this.batchDataRemaining = new AtomicInteger(batchDataLength);
@ -196,8 +150,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
} }
if (batchDataRemaining.decrementAndGet() == 0){ if (batchDataRemaining.decrementAndGet() == 0){
if (callback != null) callback.onSuccess(this.aggregatedResult.get());
callback.onSuccess(this.aggregatedResult.get());
} }
} }
@ -205,80 +158,110 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
// Notify caller about failure // Notify caller about failure
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); callback.onFailure(t);
}
}
} }
/** /**
* This callback receives a message which may be a stub * This callback ties together the different parts of a CompleteBatch as they arrive from the server
* If the message is not a stub: it returns it as is to a callback function * It assembles a CompleteBatch from the parts and sends it to the user if all parts arrived
* If it is a stub: it schedules a read of the batch data which will return a complete message to the callback function * If any part fails to arrive: it invokes the onFailure method
*/ */
class CompleteMessageReadCallback implements FutureCallback<List<BulletinBoardMessage>>{ class CompleteBatchReadCallback {
private final FutureCallback<BulletinBoardMessage> callback; private final FutureCallback<CompleteBatch> callback;
public CompleteMessageReadCallback(FutureCallback<BulletinBoardMessage> callback) { private List<BatchData> batchDataList;
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);
} }
@Override protected void combineAndReturn() {
public void onSuccess(List<BulletinBoardMessage> result) {
if (result.size() <= 0) {
onFailure(new CommunicationException("Could not find required message on the server."));
} else {
BulletinBoardMessage msg = result.get(0); final String[] prefixes = {
BulletinBoardConstants.BATCH_ID_TAG_PREFIX,
BulletinBoardConstants.BATCH_TAG};
if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) { if (remainingQueries.decrementAndGet() == 0){
callback.onSuccess(msg);
} else {
// Create job with MAX retries for retrieval of the Batch Data List String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX);
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 /**
public void onFailure(Throwable t) { * @return a FutureCallback for the Batch Data List that ties to this object
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);
}
};
} }
} }
@ -306,31 +289,22 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
public void onSuccess(List<BulletinBoardMessage> result) { public void onSuccess(List<BulletinBoardMessage> result) {
// Report new messages to user // Report new messages to user
if (callback != null) callback.onSuccess(result);
callback.onSuccess(result);
// Update filter if needed // Remove last filter from list (MIN_ENTRY one)
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
if (result.size() > 0) { // Add updated MIN_ENTRY filter (entry number is successor of last received entry's number)
filterBuilder.addFilter(MessageFilter.newBuilder()
// Remove last filter from list (MIN_ENTRY one) .setType(FilterType.MIN_ENTRY)
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1); .setEntry(result.get(result.size() - 1).getEntryNum() + 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(), MAX_RETRIES); worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), 1);
RetryCallback<List<BulletinBoardMessage>> retryCallback = new RetryCallback<>(worker, this); // Schedule the worker
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);
} }
@ -341,8 +315,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
fail(); fail();
// Notify caller about failure and terminate subscription // Notify caller about failure and terminate subscription
if (callback != null) callback.onFailure(t);
callback.onFailure(t);
} }
} }
@ -352,8 +325,8 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
this.executorService = executorService; this.executorService = executorService;
this.FAIL_DELAY_IN_MILLISECONDS = failDelayInMilliseconds; this.failDelayInMilliseconds = failDelayInMilliseconds;
this.SUBSCRIPTION_INTERVAL_IN_MILLISECONDS = subscriptionIntervalInMilliseconds; this.subscriptionIntervalInMilliseconds = 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;
@ -375,289 +348,79 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
@Override @Override
public void init(BulletinBoardClientParams clientParams) { public void init(BulletinBoardClientParams clientParams) {
this.digest = new GenericBulletinBoardDigest(new SHA256Digest()); // Perform usual setup
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
this.dbAddress = clientParams.getBulletinBoardAddress(0); String dbAddress = meerkatDBs.get(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(dbAddress, msg, MAX_RETRIES); SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(meerkatDBs.get(0), 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
digest.reset(); batchDigest.reset();
digest.update(msg.getMsg()); batchDigest.update(msg.getMsg());
return digest.digestAsMessageID(); return batchDigest.digestAsMessageID();
} }
private class PostBatchDataCallback implements FutureCallback<Boolean> { private class PostBatchDataCallback implements FutureCallback<Boolean> {
private final BulletinBoardMessage msg; private final CompleteBatch completeBatch;
private final BatchIdentifier identifier;
private final FutureCallback<Boolean> callback; private final FutureCallback<Boolean> callback;
public PostBatchDataCallback(BulletinBoardMessage msg, BatchIdentifier identifier, FutureCallback<Boolean> callback) { public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
this.msg = msg; this.completeBatch = completeBatch;
this.identifier = identifier;
this.callback = callback; this.callback = callback;
} }
@Override @Override
public void onSuccess(Boolean result) { public void onSuccess(Boolean msg) {
closeBatch( closeBatch(
identifier, completeBatch.getCloseBatchMessage(),
msg.getMsg().getTimestamp(),
msg.getSigList(),
callback callback
); );
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
if (callback != null) callback.onFailure(t);
callback.onFailure(t);
} }
} }
private class ContinueBatchCallback implements FutureCallback<BatchIdentifier> { private class BeginBatchCallback implements FutureCallback<Boolean> {
private final BulletinBoardMessage msg; private final CompleteBatch completeBatch;
private final int chunkSize;
private final FutureCallback<Boolean> callback; private final FutureCallback<Boolean> callback;
public ContinueBatchCallback(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) { public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
this.msg = msg; this.completeBatch = completeBatch;
this.chunkSize = chunkSize;
this.callback = callback; this.callback = callback;
} }
@Override @Override
public void onSuccess(BatchIdentifier identifier) { public void onSuccess(Boolean msg) {
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
postBatchData( postBatchData(
identifier, completeBatch.getBeginBatchMessage().getSignerId(),
batchChunkList, completeBatch.getBeginBatchMessage().getBatchId(),
completeBatch.getBatchDataList(),
0, 0,
new PostBatchDataCallback(msg, identifier, callback)); new PostBatchDataCallback(completeBatch,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
@ -667,53 +430,51 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
} }
@Override @Override
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) { public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) {
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder() beginBatch(
.addAllTag(tags) completeBatch.getBeginBatchMessage(),
.build(); new BeginBatchCallback(completeBatch, callback)
);
// Create worker with redundancy 1 and MAX_RETRIES retries batchDigest.update(completeBatch);
SingleServerBeginBatchWorker worker =
new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
// Submit worker and create callback return batchDigest.digestAsMessageID();
scheduleWorker(worker, new RetryCallback<>(worker, new BeginBatchCallback(callback)));
} }
@Override
public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) {
// Create worker with redundancy 1 and MAX_RETRIES retries
SingleServerBeginBatchWorker worker =
new SingleServerBeginBatchWorker(meerkatDBs.get(0), beginBatchMessage, MAX_RETRIES);
// Submit worker and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback));
}
@Override @Override
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList,
int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException{ int startPosition, FutureCallback<Boolean> callback) {
// Cast identifier to usable form BatchMessage.Builder builder = BatchMessage.newBuilder()
.setSignerId(signerId)
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){ .setBatchId(batchId);
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
PostBatchChunkListCallback listCallback = new PostBatchChunkListCallback(batchChunkList.size(), callback); PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback);
// Iterate through data list // Iterate through data list
BatchMessage.Builder builder = BatchMessage.newBuilder() for (BatchData data : batchDataList) {
.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(dbAddress, builder.build(), MAX_RETRIES); new SingleServerPostBatchWorker(meerkatDBs.get(0), 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));
@ -725,33 +486,33 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
} }
@Override @Override
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
throws IllegalArgumentException {
postBatchData(batchIdentifier, batchChunkList, 0, callback); postBatchData(signerId, batchId, batchDataList, 0, callback);
} }
@Override @Override
public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Crypto.Signature> signatures, FutureCallback<Boolean> callback) public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList,
throws IllegalArgumentException { int startPosition, FutureCallback<Boolean> callback) {
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){ postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback);
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
}
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier; }
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder() @Override
.setBatchId(identifier.getBatchId().getValue()) public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) {
.setBatchLength(identifier.getLength())
.setTimestamp(timestamp) postBatchData(signerId, batchId, batchDataList, 0, callback);
.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(dbAddress, closeBatchMessage, MAX_RETRIES); new SingleServerCloseBatchWorker(meerkatDBs.get(0), 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));
@ -762,7 +523,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
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(dbAddress, id, 1); SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(meerkatDBs.get(0), id, 1);
// Submit job and create callback // Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -773,7 +534,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
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(dbAddress, filterList, 1); SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
// Submit job and create callback // Submit job and create callback
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -781,55 +542,43 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
} }
@Override @Override
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) { public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) {
// Create job with MAX retries for retrieval of the Bulletin Board Message (which may be a stub) // Create job with no retries for retrieval of the Bulletin Board Message that defines the batch
MessageFilterList filterList = MessageFilterList.newBuilder() MessageFilterList filterList = MessageFilterList.newBuilder()
.addFilter(MessageFilter.newBuilder() .addFilter(MessageFilter.newBuilder()
.setType(FilterType.MSG_ID) .setType(FilterType.TAG)
.setId(msgID.getID()) .setTag(BulletinBoardConstants.BATCH_TAG)
.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();
BatchQuery batchQuery = BatchQuery.newBuilder() SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1);
.setMsgID(msgID)
.setStartPosition(0)
.build();
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES); // Create job with no retries for retrieval of the Batch Data List
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, new CompleteMessageReadCallback(callback))); scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, completeBatchReadCallback.asBulletinBoardMessageListFutureCallback()));
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(dbAddress, syncQuery, MAX_RETRIES); SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(meerkatDBs.get(0), syncQuery, MAX_RETRIES);
scheduleWorker(worker, new RetryCallback<>(worker, callback)); scheduleWorker(worker, new RetryCallback<>(worker, callback));
@ -855,7 +604,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
.build()); .build());
// Create job with no retries // Create job with no retries
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterListBuilder.build(), MAX_RETRIES); SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), 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)));
@ -869,6 +618,8 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar
@Override @Override
public void close() { public void close() {
super.close();
executorService.shutdown(); executorService.shutdown();
} }

View File

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

@ -3,11 +3,12 @@ package meerkat.bulletinboard;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.protobuf.Timestamp; import com.google.protobuf.Timestamp;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.util.BulletinBoardUtils; 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;
@ -18,22 +19,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber { public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber {
protected final Collection<SubscriptionBulletinBoardClient> clients; protected final Collection<SubscriptionAsyncBulletinBoardClient> clients;
protected final BulletinBoardClient localClient; protected final BulletinBoardClient localClient;
protected Iterator<SubscriptionBulletinBoardClient> clientIterator; protected Iterator<SubscriptionAsyncBulletinBoardClient> clientIterator;
protected SubscriptionBulletinBoardClient currentClient; protected SubscriptionAsyncBulletinBoardClient 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<SubscriptionBulletinBoardClient> clients, BulletinBoardClient localClient) { public ThreadedBulletinBoardSubscriber(Collection<SubscriptionAsyncBulletinBoardClient> clients, BulletinBoardClient localClient) {
this.clients = clients; this.clients = clients;
this.localClient = localClient; this.localClient = localClient;
@ -46,8 +45,6 @@ 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);
} }
/** /**
@ -135,8 +132,7 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
//TODO: log //TODO: log
if (callback != null) callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
} }
@ -222,8 +218,7 @@ 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
if (callback != null) callback.onSuccess(result);
callback.onSuccess(result);
// Renew subscription // Renew subscription
@ -250,12 +245,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
if (callback != null) callback.onSuccess(result);
callback.onSuccess(result);
} }
@ -273,4 +268,5 @@ public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber
subscribe(filterList, 0, callback); subscribe(filterList, 0, callback);
} }
} }

View File

@ -1,96 +1,28 @@
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.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
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 MultiServerWorker<Iterable<String>, BatchIdentifier> { public class MultiServerBeginBatchWorker extends MultiServerGenericPostWorker<BeginBatchMessage> {
private BatchIdentifier[] identifiers;
private AtomicInteger remainingServers;
public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, Iterable<String> payload, int maxRetry, int minServers, BeginBatchMessage payload, int maxRetry,
FutureCallback<BatchIdentifier> futureCallback) { FutureCallback<Boolean> 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
public void onSuccess(BatchIdentifier result) { protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) {
succeed(result); client.beginBatch(payload, this);
} }
@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,83 +1,27 @@
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.crypto.DigitalSignature; import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
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 MultiServerWorker<MultiServerBatchIdentifier, Boolean> { public class MultiServerCloseBatchWorker extends MultiServerGenericPostWorker<CloseBatchMessage> {
private final Timestamp timestamp;
private final Iterable<Crypto.Signature> signatures;
public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, MultiServerBatchIdentifier payload, Timestamp timestamp, Iterable<Crypto.Signature> signatures, int minServers, CloseBatchMessage payload, int maxRetry,
int maxRetry, FutureCallback<Boolean> futureCallback) { FutureCallback<Boolean> futureCallback) {
super(clients, minServers, payload, maxRetry, futureCallback); super(clients, minServers, payload, maxRetry, futureCallback);
this.timestamp = timestamp;
this.signatures = signatures;
} }
@Override @Override
public void run() { protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) {
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,9 +35,14 @@ 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,9 +14,7 @@ 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 Iterator<SingleServerBulletinBoardClient> clientIterator; private final 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,
@ -24,8 +22,7 @@ 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 = clients.iterator(); clientIterator = getClientIterator();
errorString = "";
} }
@ -49,7 +46,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. Errors follow:\n" + errorString)); fail(new CommunicationException("Could not contact any server"));
} }
} }
@ -61,8 +58,6 @@ 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

@ -4,7 +4,7 @@ import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.MultiServerWorker; import meerkat.bulletinboard.MultiServerWorker;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.*; import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -36,8 +36,13 @@ 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,18 +1,15 @@
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 MultiServerWorker<BatchDataContainer, Boolean> { public class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker<BatchDataContainer> {
public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BatchDataContainer payload, int maxRetry, int minServers, BatchDataContainer payload, int maxRetry,
@ -23,50 +20,9 @@ public class MultiServerPostBatchDataWorker extends MultiServerWorker<BatchDataC
} }
@Override @Override
public void run() { protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) {
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,33 +1,27 @@
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<BulletinBoardMessage> { public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<CompleteBatch> {
private final int chunkSize;
public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients, public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients,
int minServers, BulletinBoardMessage payload, int chunkSize, int maxRetry, int minServers, CompleteBatch payload, 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, BulletinBoardMessage payload) { protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) {
client.postBatch(payload, this);
client.postAsBatch(payload, chunkSize, this);
} }

View File

@ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardApi.*; import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.List; import java.util.List;

View File

@ -1,29 +0,0 @@
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

@ -0,0 +1,30 @@
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

@ -1,30 +0,0 @@
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

@ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import meerkat.bulletinboard.SingleServerBulletinBoardClient; import meerkat.bulletinboard.SingleServerBulletinBoardClient;
import meerkat.protobuf.BulletinBoardApi.*; import meerkat.protobuf.BulletinBoardAPI.*;
import java.util.List; import java.util.List;

View File

@ -1,53 +1,17 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import com.google.protobuf.Int64Value; import meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage;
import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException;
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 SingleServerWorker<BeginBatchMessage,Int64Value> { public class SingleServerBeginBatchWorker extends SingleServerGenericPostWorker<BeginBatchMessage> {
public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) { public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) {
super(serverAddress, payload, maxRetry); super(serverAddress, BEGIN_BATCH_PATH, 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

@ -1,6 +1,6 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardApi.CloseBatchMessage; import meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH;

View File

@ -1,55 +0,0 @@
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

@ -3,9 +3,10 @@ package meerkat.bulletinboard.workers.singleserver;
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;
@ -13,6 +14,7 @@ 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,6 +1,6 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardApi.BatchMessage; import meerkat.protobuf.BulletinBoardAPI.BatchMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH;

View File

@ -1,6 +1,6 @@
package meerkat.bulletinboard.workers.singleserver; package meerkat.bulletinboard.workers.singleserver;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH; import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH;

View File

@ -2,8 +2,8 @@ package meerkat.bulletinboard.workers.singleserver;
import meerkat.bulletinboard.SingleServerWorker; import meerkat.bulletinboard.SingleServerWorker;
import meerkat.comm.CommunicationException; import meerkat.comm.CommunicationException;
import meerkat.protobuf.BulletinBoardApi.SyncQuery; import meerkat.protobuf.BulletinBoardAPI.SyncQuery;
import meerkat.protobuf.BulletinBoardApi.SyncQueryResponse; import meerkat.protobuf.BulletinBoardAPI.SyncQueryResponse;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException; import javax.ws.rs.ProcessingException;

View File

@ -1,28 +1,35 @@
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<BatchQuery, List<BatchChunk>> { public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecificationMessage, List<BatchData>> {
public SingleServerReadBatchWorker(String serverAddress, BatchQuery payload, int maxRetry) { public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) {
super(serverAddress, payload, maxRetry); super(serverAddress, payload, maxRetry);
} }
@ -32,7 +39,7 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery,
* @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<BatchChunk> call() throws CommunicationException{ public List<BatchData> call() throws CommunicationException{
Client client = clientLocal.get(); Client client = clientLocal.get();
@ -43,11 +50,11 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery,
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<BatchChunk> inputStream = null; MessageInputStream<BatchData> inputStream = null;
try { try {
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class); inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class);
return inputStream.asList(); return inputStream.asList();

View File

@ -3,10 +3,10 @@ package meerkat.bulletinboard.workers.singleserver;
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.protobuf.BulletinBoardApi.BulletinBoardMessageList; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessageList;
import meerkat.protobuf.BulletinBoardApi.MessageFilterList; import meerkat.protobuf.BulletinBoardAPI.MessageFilterList;
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage; import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage;
import meerkat.rest.Constants; import meerkat.rest.Constants;
import javax.ws.rs.ProcessingException; import javax.ws.rs.ProcessingException;

View File

@ -1,318 +0,0 @@
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

@ -1,111 +0,0 @@
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,13 +5,10 @@ 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;
@ -31,7 +28,7 @@ public class GenericBulletinBoardClientTester {
// Signature resources // Signature resources
private BulletinBoardSignature signers[]; private GenericBatchDigitalSignature 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";
@ -48,29 +45,28 @@ 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, int seed){ public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient){
this.bulletinBoardClient = bulletinBoardClient; this.bulletinBoardClient = bulletinBoardClient;
signers = new GenericBulletinBoardSignature[2]; signers = new GenericBatchDigitalSignature[2];
signerIDs = new ByteString[signers.length]; signerIDs = new ByteString[signers.length];
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature()); signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray(); char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -112,10 +108,6 @@ 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
@ -146,21 +138,16 @@ 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 && !msg) { if (assertValue) {
genericHandleFailure(new AssertionError("Post operation failed")); assertThat("Post operation failed", msg, is(Boolean.TRUE));
} else if (!assertValue && msg){
genericHandleFailure(new AssertionError("Post operation succeeded unexpectedly"));
} else { } else {
jobSemaphore.release(); assertThat("Post operation succeeded unexpectedly", msg, is(Boolean.FALSE));
} }
} else {
jobSemaphore.release();
} }
} }
@Override @Override
@ -223,24 +210,21 @@ public class GenericBulletinBoardClientTester {
} }
} }
private class ReadBatchCallback implements FutureCallback<BulletinBoardMessage>{ private class ReadBatchCallback implements FutureCallback<CompleteBatch> {
private BulletinBoardMessage expectedMsg; private CompleteBatch expectedBatch;
public ReadBatchCallback(BulletinBoardMessage expectedMsg) { public ReadBatchCallback(CompleteBatch expectedBatch) {
this.expectedMsg = expectedMsg; this.expectedBatch = expectedBatch;
} }
@Override @Override
public void onSuccess(BulletinBoardMessage msg) { public void onSuccess(CompleteBatch batch) {
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator(); System.err.println(batch);
jobSemaphore.release();
if (msgComparator.compare(msg, expectedMsg) != 0) { assertThat("Batch returned is incorrect", batch, is(equalTo(expectedBatch)));
genericHandleFailure(new AssertionError("Batch read returned different message.\nExpected:" + expectedMsg + "\nRecieved:" + msg + "\n"));
} else {
jobSemaphore.release();
}
} }
@ -250,6 +234,59 @@ 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
/** /**
@ -274,13 +311,7 @@ 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;
} }
} }
@ -288,7 +319,7 @@ public class GenericBulletinBoardClientTester {
/** /**
* Tests the standard post, redundancy and read methods * Tests the standard post, redundancy and read methods
*/ */
public void testPost() { public void postTest() {
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};
@ -364,69 +395,59 @@ 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;
final Timestamp timestamp = Timestamp.newBuilder() CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
.setSeconds(141515)
.setNanos(859018)
.build();
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
// Begin batch // Begin batch
bulletinBoardClient.beginBatch(msg.getMsg().getTagList(), new FutureCallback<BatchIdentifier>() { bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), postCallback);
@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();
digest.reset(); // Post data
digest.update(msg);
bulletinBoardClient.readMessage(digest.digestAsMessageID(), new ReadBatchCallback(msg)); bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), postCallback);
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();
@ -434,61 +455,62 @@ public class GenericBulletinBoardClientTester {
/** /**
* Posts a complete batch message * Posts a complete batch message
* Checks reading of the message in two parts * Checks reading of the message
* @throws CommunicationException, SignatureException, InterruptedException * @throws CommunicationException, SignatureException, InterruptedException
*/ */
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
final int BATCH_LENGTH = 100; final int SIGNER = 0;
final int CHUNK_SIZE = 99; final int BATCH_ID = 101;
final int TAG_NUM = 8; final int BATCH_LENGTH = 50;
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
MessageID msgID = bulletinBoardClient.postAsBatch(msg, CHUNK_SIZE, postCallback); CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH);
bulletinBoardClient.postBatch(completeBatch,postCallback);
jobSemaphore.acquire(); jobSemaphore.acquire();
// Read batch // Read batch
MessageFilterList filterList = MessageFilterList.newBuilder() BatchSpecificationMessage batchSpecificationMessage =
.addFilter(MessageFilter.newBuilder() BatchSpecificationMessage.newBuilder()
.setType(FilterType.MSG_ID) .setSignerId(signerIDs[SIGNER])
.setId(msgID.getID()) .setBatchId(BATCH_ID)
.build()) .setStartPosition(0)
.build(); .build();
bulletinBoardClient.readMessages(filterList, new FutureCallback<List<BulletinBoardMessage>>() { readBatchCallback = new ReadBatchCallback(completeBatch);
@Override bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback);
public void onSuccess(List<BulletinBoardMessage> msgList) {
if (msgList.size() != 1) { jobSemaphore.acquire();
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 {
BulletinBoardMessage retrievedMsg = msgList.get(0); final int NON_EXISTENT_BATCH_ID = 999;
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;
@Override bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback);
public void onFailure(Throwable t) {
genericHandleFailure(t);
}
});
jobSemaphore.acquire(); jobSemaphore.acquire();

View File

@ -5,7 +5,7 @@ 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.protobuf.BulletinBoardApi.*; import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.util.BulletinBoardMessageComparator; import meerkat.util.BulletinBoardMessageComparator;
import meerkat.util.BulletinBoardMessageGenerator; import meerkat.util.BulletinBoardMessageGenerator;
@ -16,6 +16,9 @@ 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;
/** /**
@ -23,7 +26,7 @@ import static org.junit.Assert.fail;
*/ */
public class GenericSubscriptionClientTester { public class GenericSubscriptionClientTester {
private BulletinBoardSignature signers[]; private GenericBatchDigitalSignature 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";
@ -35,7 +38,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 SubscriptionBulletinBoardClient bulletinBoardClient; private SubscriptionAsyncBulletinBoardClient bulletinBoardClient;
private Random random; private Random random;
private BulletinBoardMessageGenerator generator; private BulletinBoardMessageGenerator generator;
@ -43,14 +46,14 @@ public class GenericSubscriptionClientTester {
private Semaphore jobSemaphore; private Semaphore jobSemaphore;
private Vector<Throwable> thrown; private Vector<Throwable> thrown;
public GenericSubscriptionClientTester(SubscriptionBulletinBoardClient bulletinBoardClient){ public GenericSubscriptionClientTester(SubscriptionAsyncBulletinBoardClient bulletinBoardClient){
this.bulletinBoardClient = bulletinBoardClient; this.bulletinBoardClient = bulletinBoardClient;
signers = new BulletinBoardSignature[2]; signers = new GenericBatchDigitalSignature[2];
signerIDs = new ByteString[signers.length]; signerIDs = new ByteString[signers.length];
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); signers[0] = new GenericBatchDigitalSignature(new ECDSASignature());
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature()); signers[1] = new GenericBatchDigitalSignature(new ECDSASignature());
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
char[] password = KEYFILE_PASSWORD1.toCharArray(); char[] password = KEYFILE_PASSWORD1.toCharArray();
@ -178,15 +181,17 @@ 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(); jobSemaphore.release(expectedMessages.size());
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);
@ -202,9 +207,9 @@ public class GenericSubscriptionClientTester {
.build(); .build();
List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3); List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3);
expectedMessages.add(new LinkedList<>()); expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.add(new LinkedList<>()); expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.add(new LinkedList<>()); expectedMessages.add(new LinkedList<BulletinBoardMessage>());
expectedMessages.get(0).add(msg1); expectedMessages.get(0).add(msg1);
expectedMessages.get(2).add(msg3); expectedMessages.get(2).add(msg3);

View File

@ -7,6 +7,12 @@ 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.
@ -24,14 +30,30 @@ public class LocalBulletinBoardClientTest {
public LocalBulletinBoardClientTest() throws CommunicationException { public LocalBulletinBoardClientTest() throws CommunicationException {
H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME); H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME) ;
DeletableBulletinBoardServer server = new BulletinBoardSQLServer(queryProvider); try {
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, 98354); clientTest = new GenericBulletinBoardClientTester(client);
} }
@ -59,9 +81,9 @@ public class LocalBulletinBoardClientTest {
} }
@Test @Test
public void testPost() { public void postTest() {
clientTest.testPost(); clientTest.postTest();
} }
@ -79,8 +101,14 @@ public class LocalBulletinBoardClientTest {
} }
@Test @Test
public void testSubscription() throws SignatureException, CommunicationException { public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
@Test
public void testSubscription() throws SignatureException, CommunicationException {
subscriptionTester.init(); subscriptionTester.init();
subscriptionTester.subscriptionTest(); subscriptionTester.subscriptionTest();
subscriptionTester.close(); subscriptionTester.close();

View File

@ -1,104 +0,0 @@
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(3,0,500); ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient();
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, 52351); clientTest = new GenericBulletinBoardClientTester(client);
} }
@ -66,9 +66,9 @@ public class ThreadedBulletinBoardClientIntegrationTest {
} }
@Test @Test
public void testPost() { public void postTest() {
clientTest.testPost(); clientTest.postTest();
} }
@ -76,7 +76,6 @@ public class ThreadedBulletinBoardClientIntegrationTest {
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
clientTest.testBatchPost(); clientTest.testBatchPost();
} }
@Test @Test
@ -86,4 +85,11 @@ public class ThreadedBulletinBoardClientIntegrationTest {
} }
@Test
public void testInvalidBatchClose() throws CommunicationException, InterruptedException {
clientTest.testInvalidBatchClose();
}
} }

View File

@ -1,57 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "bulletin-board-server-frontend"
},
"apps": [
{
"root": "src",
"outDir": "dist",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
"test": "test.ts",
"tsconfig": "tsconfig.app.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
"e2e": {
"protractor": {
"config": "./protractor.conf.js"
}
},
"lint": [
{
"project": "src/tsconfig.app.json"
},
{
"project": "src/tsconfig.spec.json"
},
{
"project": "e2e/tsconfig.e2e.json"
}
],
"test": {
"karma": {
"config": "./karma.conf.js"
}
},
"defaults": {
"styleExt": "css",
"component": {}
}
}

View File

@ -1,13 +0,0 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -1,42 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# dependencies
/node_modules
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
# System Files
.DS_Store
Thumbs.db

View File

@ -1,28 +0,0 @@
# BulletinBoardServerFrontend
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.0.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -1,60 +0,0 @@
plugins {
id "com.moowork.node" version "1.2.0"
}
node {
download = true;
}
ext {
bundlejs = "src/app/bundle.js"
bundlets = "src/app/bundle.d.ts"
}
//task installAngularCli(type: NpmTask) {
// args = ['install', '@angular/cli']
//}
//npm_install.dependsOn(installAngularCli);
task printProtos {
doLast {
println getProtoFiles().join(" ")
}
}
printProtos.description = "List all the .proto files we can find."
def getProtoFiles() {
def protoFiles = []
rootProject.subprojects { proj ->
if (proj.hasProperty('sourceSets') && proj.sourceSets.hasProperty('main') &&
proj.sourceSets.main.hasProperty('proto')) {
protoFiles = protoFiles + proj.sourceSets.main.proto.getFiles();
}
}
return protoFiles;
}
task generateProtobufBundle(type: NpmTask) {
doFirst {
// We need do this in doFirst since getProtoFiles won't find all subprojects
// until after configuration is complete
def protoFiles = getProtoFiles()
generateProtobufBundle.args = ['run', 'protoc', '--' ] + protoFiles;
}
dependsOn npm_install
}
generateProtobufBundle.description = "Generate the bundle.js/bundle.d.ts files containing compiled protobufs"
npm_run_build.dependsOn(npm_install)
npm_run_build.dependsOn(generateProtobufBundle)
npm_start.dependsOn(generateProtobufBundle)
// It would be nice if this worked, but it seems to get stuck:
//npm_start.dependsOn(project(':bulletin-board-server').jettyStart);

View File

@ -1,14 +0,0 @@
import { BulletinBoardServerFrontendPage } from './app.po';
describe('bulletin-board-server-frontend App', () => {
let page: BulletinBoardServerFrontendPage;
beforeEach(() => {
page = new BulletinBoardServerFrontendPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to app!!');
});
});

View File

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class BulletinBoardServerFrontendPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}

View File

@ -1,13 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -1,32 +0,0 @@
#!/usr/bin/env node
const { execFileSync } = require('child_process');
const path = require('path');
const isWin = (process.platform === 'win32');
const bundlejs = 'src/app/bundle.js'
const bundlets = 'src/app/bundle.d.ts'
const npm = isWin ? 'npm.cmd' : 'npm';
var args = process.argv.slice(2).map(function(x) {
return path.normalize(x);
});
var pbjs_args = [
'-t', 'static-module', '-w', 'commonjs', '-o', bundlejs
].concat(args);
console.log("Running pbjs " + pbjs_args.join(" "));
var out = execFileSync(npm, ['run', 'pbjs', '--'].concat(pbjs_args));
console.log("" + out);
var pbts_args = [
'-o', bundlets, bundlejs
];
console.log("Running pbts " + pbts_args.join(" "));
out = execFileSync(npm, ['run', 'pbts', '--'].concat(pbts_args));
console.log("" + out);

View File

@ -1 +0,0 @@
../gradlew

View File

@ -1,33 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular/cli'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular/cli/plugins/karma')
],
client:{
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
reports: [ 'html', 'lcovonly' ],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};

View File

@ -1,53 +0,0 @@
{
"name": "bulletin-board-server-frontend",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"lint": "ng e2e",
"pbts": "pbts",
"pbjs": "pbjs",
"protoc" : "node ./generate-protobuf.js"
},
"private": true,
"dependencies": {
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/router": "^4.0.0",
"@types/long": "^3.0.31",
"core-js": "^2.4.1",
"protobufjs": "^6.8.0",
"rxjs": "^5.1.0",
"zone.js": "^0.8.4"
},
"devDependencies": {
"@angular/cli": "^1.2.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/language-service": "^4.0.0",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "~3.0.1",
"jasmine-core": "~2.6.2",
"jasmine-spec-reporter": "~4.1.0",
"karma": "~1.7.0",
"karma-chrome-launcher": "~2.1.1",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.1.2",
"ts-node": "~3.0.4",
"tslint": "~5.3.2",
"typescript": "~2.3.3"
}
}

View File

@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -1,6 +0,0 @@
{
"/bbserver" : {
"target" : "http://localhost:8081/",
"secure" : false
}
}

View File

@ -1,12 +0,0 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{title}}!!
</h1>
<h3>Current status: {{status}}</h3>
<img width="300" src="assets/meerkat-logo.svg">
<hr>
{{errorMessage}}
</div>

View File

@ -1,32 +0,0 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!');
}));
});

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