Compare commits
305 Commits
Author | SHA1 | Date |
---|---|---|
Tal Moran | cf891549bb | |
Tal Moran | 21ad93f4e1 | |
Tal Moran | 73619b0c9e | |
Tal Moran | 252a761005 | |
Tal Moran | 931bb56378 | |
Tal Moran | fa4656f0a9 | |
Tal Moran | 08d9bd6217 | |
Tal Moran | 173b952e09 | |
Tal Moran | c0b21f82ee | |
Tal Moran | 05c7a25bb7 | |
Tal Moran | 68e1737f22 | |
Tal Moran | 3c52eb2e8d | |
Tal Moran | 0dfd2480d2 | |
Tal Moran | 291f7d93c0 | |
Tal Moran | 6b47387920 | |
Tal Moran | 324a079a90 | |
Tal Moran | 6f35f105c5 | |
Tal Moran | cb1d2343c4 | |
Tal Moran | 7e4260b8d5 | |
Tal Moran | ceba09e65c | |
Tal Moran | c7dd5bd663 | |
Tal Moran | 24e3556320 | |
Tal Moran | 62349f9fe7 | |
Tal Moran | 3b3a6a09b2 | |
Tal Moran | 1387de97e2 | |
Tal Moran | ef2e09ba6b | |
Tal Moran | c921fef055 | |
Tal Moran | ea8813bade | |
Tal Moran | 355ea1758b | |
Tal Moran | 9ad0828eb2 | |
Tal Moran | 33a5c493f0 | |
Laura Radaelli | 24b111a20b | |
Laura Radaelli | f813cf49ee | |
Laura Radaelli | 646824de44 | |
Laura Radaelli | 4e1b66f890 | |
Laura Radaelli | cc3e151a26 | |
Laura Radaelli | 29f6b244d7 | |
Laura Radaelli | ed7b50de23 | |
Laura Radaelli | 09ee63eca4 | |
Laura Radaelli | 11b86dd2aa | |
Laura Radaelli | db4cf6a734 | |
Laura Radaelli | 6350da1fc2 | |
Tal Moran | 181f49145f | |
Laura Radaelli | 97fbfcecaf | |
Laura Radaelli | 17d7d5eca4 | |
Laura Radaelli | f393825b26 | |
Tal Moran | b360ae81bb | |
Laura Radaelli | 480f4e52bb | |
Laura Radaelli | 0cf9390fa5 | |
Laura Radaelli | e60e76f104 | |
Tal Moran | 06526a16db | |
Tal Moran | 53cc13b51b | |
Tal Moran | ce0e0e0d62 | |
Tal Moran | 5b7fbffb09 | |
Tal Moran | c8f82223bc | |
Tal Moran | f930a65c48 | |
Tal Moran | 2744005263 | |
Tal Moran | 43d4fb75b2 | |
Tal Moran | 273338010d | |
Tal Moran | b9abd847c7 | |
Tal Moran | deeed4f20f | |
Tal Moran | fc2c26d7e9 | |
Tal Moran | 78f823f31e | |
Tal Moran | 37d1857f9c | |
Tal Moran | abf4cc5e54 | |
Tal Moran | 0d416f0018 | |
Tal Moran | c2da5aa464 | |
Laura Radaelli | 5386acfd1b | |
Laura Radaelli | 72d96c4d78 | |
Laura Radaelli | b8418156b1 | |
Tal Moran | a5cceaa6c0 | |
Tal Moran | 723d348443 | |
Tal Moran | b7ef2c10e1 | |
Laura Radaelli | 29b8ef9734 | |
Laura Radaelli | c8044fc93d | |
Laura Radaelli | 5113c2235b | |
Laura Radaelli | c97519c28e | |
Laura Radaelli | 0b1d334d3c | |
Laura Radaelli | 971db5d89c | |
Laura Radaelli | 71076f5c7e | |
Tal Moran | 9db8efd75b | |
VladimirEliTokarev | 0509bf6dda | |
VladimirEliTokarev | 08cc50c22a | |
VladimirEliTokarev | 6161898805 | |
VladimirEliTokarev | 7b937cc754 | |
Tal Moran | 97f52bfd82 | |
Tal Moran | e611d0c721 | |
Tal Moran | f5e71dec2f | |
VladimirEliTokarev | d6ea9412e3 | |
Tal Moran | aac7a50a94 | |
Tal Moran | 5b268cd779 | |
Tal Moran | 1baa567d8e | |
Tal Moran | c4e0e5f56d | |
Tal Moran | ac4d668984 | |
VladimirEliTokarev | b601619d39 | |
VladimirEliTokarev | 555783d30c | |
VladimirEliTokarev | c5dcb9f06e | |
VladimirEliTokarev | fbc1707b37 | |
VladimirEliTokarev | e440bb3e76 | |
Tal Moran | ca89514c97 | |
VladimirEliTokarev | d575eaf1d2 | |
VladimirEliTokarev | 2bf78f7c73 | |
VladimirEliTokarev | 0af9d94a73 | |
VladimirEliTokarev | 37f8893224 | |
VladimirEliTokarev | c474bc5ffb | |
VladimirEliTokarev | 5a78f723e5 | |
VladimirEliTokarev | 94adef22f3 | |
VladimirEliTokarev | dcaf36f8fa | |
VladimirEliTokarev | ef5cd70cc9 | |
VladimirEliTokarev | 8e2fdb7828 | |
VladimirEliTokarev | 6655f2dc8e | |
VladimirEliTokarev | af2622b05c | |
VladimirEliTokarev | f535847ee5 | |
VladimirEliTokarev | 55279e2e3d | |
VladimirEliTokarev | 80a0a2fc2c | |
VladimirEliTokarev | 80a032d373 | |
VladimirEliTokarev | 6e47a4df98 | |
VladimirEliTokarev | 21867fb5da | |
VladimirEliTokarev | 91b0d947f2 | |
VladimirEliTokarev | 352fb7a548 | |
VladimirEliTokarev | 4e071c83ce | |
VladimirEliTokarev | fe5a4d7be1 | |
VladimirEliTokarev | ef1ff8dea1 | |
VladimirEliTokarev | 272e40647e | |
VladimirEliTokarev | c410fc6693 | |
VladimirEliTokarev | 5267c7026f | |
Vladimir Eliezer Tokarev | 68291d34f3 | |
Vladimir Eliezer Tokarev | 5c2de6d009 | |
Vladimir Eliezer Tokarev | c0ed5ea3fc | |
Vladimir Eliezer Tokarev | 51ea368c01 | |
Vladimir Eliezer Tokarev | 7cda19247e | |
Vladimir Eliezer Tokarev | 9211c70c2e | |
Vladimir Eliezer Tokarev | 16e0633b75 | |
Vladimir Eliezer Tokarev | 37be8f3a52 | |
Vladimir Eliezer Tokarev | f1704ce16e | |
Tal Moran | a9fbf555d6 | |
Tal Moran | 1ff490a354 | |
Tal Moran | 63e26fdc16 | |
Tal Moran | 267164e996 | |
Vladimir Eliezer Tokarev | 0007df38b8 | |
Vladimir Eliezer Tokarev | d7fca43817 | |
Vladimir Eliezer Tokarev | 210994ca75 | |
Vladimir Eliezer Tokarev | d8adf7b49e | |
Vladimir Eliezer Tokarev | 9444072f40 | |
Vladimir Eliezer Tokarev | c6f2ffff9b | |
Vladimir Eliezer Tokarev | dda2d3e55b | |
Vladimir Eliezer Tokarev | 38f1624071 | |
Vladimir Eliezer Tokarev | b687782f5c | |
Vladimir Eliezer Tokarev | 4591c4e2e8 | |
Vladimir Eliezer Tokarev | 36d9276ad8 | |
Vladimir Eliezer Tokarev | 01cda996f9 | |
Vladimir Eliezer Tokarev | e200bc0f8b | |
Vladimir Eliezer Tokarev | 9b636f2d2e | |
Vladimir Eliezer Tokarev | e605089666 | |
Vladimir Eliezer Tokarev | c39d0774f9 | |
Vladimir Eliezer Tokarev | 19b11736e0 | |
Vladimir Eliezer Tokarev | 3a86e6f01a | |
Vladimir Eliezer Tokarev | c26acbc3cf | |
Vladimir Eliezer Tokarev | 86de5b07a4 | |
Vladimir Eliezer Tokarev | 8a73f21f48 | |
Hai Brenner | f2ea4b5d15 | |
Hai Brenner | 6b512e078b | |
Hai Brenner | ce40a04ac7 | |
Vladimir Eliezer Tokarev | bc790c3eb4 | |
Vladimir Eliezer Tokarev | 99edbcf1b1 | |
Hai Brenner | 4ddd5f852a | |
Hai Brenner | c78b78aa3c | |
Hai Brenner | afed4fb510 | |
Hai Brenner | aea84d0f54 | |
Hai Brenner | cb65103fca | |
Vladimir Eliezer Tokarev | 4f3fc40dc6 | |
Hai Brenner | ae357541e8 | |
Hai Brenner | da7a05ecd8 | |
Hai Brenner | 1cf16d8386 | |
Hai Brenner | 88991ea9ff | |
Hai Brenner | 0956fa98d3 | |
Hai Brenner | 42d68b7ce8 | |
Hai Brenner | 5404bb9ed2 | |
Hai Brenner | f2836d277a | |
Hai Brenner | b667de95aa | |
Hai Brenner | 49551dc36b | |
Hai Brenner | 88c4e0e644 | |
Hai Brenner | d804f0dbac | |
Hai Brenner | b1a033da5e | |
Hai Brenner | 853a6b5684 | |
Hai Brenner | 2336d44ffc | |
Hai Brenner | d8b766725b | |
Hai Brenner | 218677fd96 | |
Hai Brenner | 2b56928e9a | |
Hai Brenner | 7db6218735 | |
Hai Brenner | 66e5db9f22 | |
arbel.peled | 48bf8dbe6b | |
arbel.peled | 8aada21119 | |
Hai Brenner | e298ab1e76 | |
Hai Brenner | 14fac728b3 | |
Hai Brenner | 3a5908ac49 | |
Hai Brenner | 438df78e36 | |
Hai Brenner | 4379921445 | |
arbel.peled | 53d609bfee | |
Hai Brenner | d12ad408c4 | |
Hai Brenner | 06d69554d9 | |
Arbel Deutsch Peled | cc2888483d | |
Arbel Deutsch Peled | d1f7413cde | |
Hai Brenner | 42ae18df00 | |
Hai Brenner | e9732561f4 | |
Hai Brenner | 19deec00bb | |
Hai Brenner | 559c714aac | |
Hai Brenner | 2bdf92b075 | |
Arbel Deutsch Peled | 1951db546d | |
Arbel Deutsch Peled | b501992643 | |
Hai Brenner | 13f8948cfb | |
Arbel Deutsch Peled | 337a135151 | |
Hai Brenner | 76d3fdeac2 | |
arbel.peled | ffac7c1e34 | |
arbel.peled | 229cbfd48f | |
arbel.peled | fe209f6b5a | |
arbel.peled | e2f3dbe6b2 | |
Arbel Deutsch Peled | e91a48b5e1 | |
arbel.peled | 7c60e487cc | |
arbel.peled | 347e826f73 | |
Hai Brenner | 94f3920e6d | |
arbel.peled | 061dc69fbc | |
arbel.peled | b934894bc5 | |
Hai Brenner | c04ed42dca | |
Hai Brenner | e042779b15 | |
tzlil.gon | e677355040 | |
tzlil.gon | b4e5040814 | |
arbel.peled | 4c33e923b2 | |
arbel.peled | 9ed728fca7 | |
Tal Moran | 1f8df95895 | |
arbel.peled | 48b2b9efa2 | |
Arbel Deutsch Peled | c806e7b32a | |
Tal Moran | c798e827dc | |
Tal Moran | 78207532ec | |
Tal Moran | d2373c09f2 | |
Tal Moran | 1ec02173e7 | |
Arbel Deutsch Peled | 67b01032d0 | |
Arbel Deutsch Peled | e904caa74f | |
Arbel Deutsch Peled | edfd47a98d | |
Arbel Deutsch Peled | 07aecd5237 | |
Arbel Deutsch Peled | 857821c0e4 | |
tzlil.gon | d0951f8644 | |
tzlil.gon | 3e1f59ec2b | |
tzlil.gon | 5d564c834c | |
tzlil.gon | 0ae9719bc5 | |
Tal Moran | 4f608e813d | |
Tal Moran | f5410bedf4 | |
Tal Moran | 19e52344f5 | |
Tal Moran | d99bf4123e | |
Tal Moran | 6d6e4748b7 | |
tzlil.gon | 5670739e49 | |
Arbel Deutsch Peled | 49c1e2c178 | |
Arbel Deutsch Peled | e56312d38b | |
tzlil.gon | 5f45c1f6d6 | |
Arbel Deutsch Peled | a7699086d8 | |
tzlil.gon | e27fddcf0c | |
tzlil.gon | b7e543e5e8 | |
tzlil.gon | e4a33af4d4 | |
Hai Brenner | d1bc0d7c84 | |
Hai Brenner | 0fa5d4094a | |
Hai Brenner | 15453a772d | |
tzlil.gon | cc7e138a43 | |
Arbel Deutsch Peled | 50bcca8da3 | |
Arbel Deutsch Peled | 1cf14a60a8 | |
Arbel Deutsch Peled | 71191e05b9 | |
Hai Brenner | 77f47fe9e1 | |
Arbel Deutsch Peled | ca31d5a177 | |
tzlil.gon | 0f7dbe3d50 | |
Arbel Deutsch Peled | aeb7c13436 | |
tzlil.gon | 210cc327ac | |
Arbel Deutsch Peled | 9a78330e29 | |
tzlil.gon | a233f2f713 | |
tzlil.gon | 8288b07d80 | |
tzlil.gon | 91dd19ead2 | |
tzlil.gon | 0a8d4abe72 | |
tzlil.gon | 635165ef8e | |
tzlil.gon | f8d31d16a3 | |
tzlil.gon | 8ba55bacd2 | |
tzlil.gon | 93240c10f4 | |
tzlil.gon | 6100497e8e | |
tzlil.gon | efde36d869 | |
tzlil.gon | ae2b7c51f0 | |
tzlil.gon | 9bb2f47b50 | |
Arbel Deutsch Peled | 3fed32f9e6 | |
Arbel Deutsch Peled | 141d286af2 | |
Arbel Deutsch Peled | 7e542a804c | |
tzlil.gon | 8a07f86c0f | |
tzlil.gon | 8f75bebaea | |
tzlil.gon | 026a879de3 | |
tzlil.gon | 75c411a5e7 | |
Arbel Deutsch Peled | d643932ef9 | |
Arbel Deutsch Peled | 88b8f6d8ea | |
Arbel Deutsch Peled | b5237d6c9f | |
Arbel Deutsch Peled | 37f962d520 | |
Arbel Deutsch Peled | 37fdc0bb83 | |
tzlil.gon | be6449f27d | |
Arbel Deutsch Peled | c4b0d8f23c | |
tzlil.gon | c8646712c0 | |
tzlil.gon | 767d73c143 | |
Arbel Deutsch Peled | b17954adc2 | |
tzlil.gon | c37d30baf6 | |
tzlil.gon | 23573666ec | |
tzlil.gon | b502dc82d3 | |
tzlil.gon | a834194d50 | |
tzlil.gon | 12ed7a679d |
|
@ -13,4 +13,26 @@ out
|
||||||
*.prefs
|
*.prefs
|
||||||
*.project
|
*.project
|
||||||
*.classpath
|
*.classpath
|
||||||
bulletin-board-server/local-instances/meerkat.db
|
*.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
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
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)
|
|
@ -0,0 +1,82 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../gradlew
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,13 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,508 @@
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?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.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
</resources>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
</resources>
|
|
@ -0,0 +1,80 @@
|
||||||
|
<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 & 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>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
52
build.gradle
52
build.gradle
|
@ -1,10 +1,56 @@
|
||||||
|
apply plugin: com.google.gradle.osdetector.OsDetectorPlugin
|
||||||
|
|
||||||
|
|
||||||
subprojects { proj ->
|
subprojects { proj ->
|
||||||
proj.afterEvaluate {
|
proj.afterEvaluate {
|
||||||
// Used to generate initial maven-dir layout
|
// Used to generate initial maven-dir layout
|
||||||
task "create-dirs" { description = "Create default maven directory structure" } << {
|
task "create-dirs" {
|
||||||
sourceSets*.java.srcDirs*.each { it.mkdirs() }
|
description = "Create default maven directory structure"
|
||||||
sourceSets*.resources.srcDirs*.each { it.mkdirs() }
|
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}/"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "us.kirchmeier.capsule" version "1.0.1"
|
id "us.kirchmeier.capsule" version "1.0.2"
|
||||||
id 'com.google.protobuf' version '0.7.0'
|
id 'com.google.protobuf' version '0.8.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
@ -25,8 +25,11 @@ ext {
|
||||||
|
|
||||||
// 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') : ""
|
|
||||||
nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : ""
|
// Credentials for publishing repositories
|
||||||
|
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"
|
description = "TODO: Add a description"
|
||||||
|
@ -131,17 +134,19 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) {
|
||||||
|
|
||||||
destinationDir = buildDir
|
destinationDir = buildDir
|
||||||
|
|
||||||
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
|
def fatMain
|
||||||
|
|
||||||
|
if (this.hasProperty('fatmain')) {
|
||||||
|
fatMain = fatmain
|
||||||
|
appendix = "fat-${fatMain}"
|
||||||
|
} else {
|
||||||
|
fatMain = mainClassName
|
||||||
|
appendix = "fat"
|
||||||
|
}
|
||||||
|
|
||||||
applicationClass fatMain
|
applicationClass fatMain
|
||||||
|
|
||||||
def testJar = hasProperty('test')
|
def testJar = this.hasProperty('test')
|
||||||
|
|
||||||
if (hasProperty('fatmain')) {
|
|
||||||
appendix = "fat-${fatMain}"
|
|
||||||
} else {
|
|
||||||
appendix = "fat"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (testJar) {
|
if (testJar) {
|
||||||
from sourceSets.test.output
|
from sourceSets.test.output
|
||||||
|
@ -155,21 +160,6 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) {
|
||||||
*===================================*/
|
*===================================*/
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
|
||||||
// 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 local maven repository
|
// Use local maven repository
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
|
||||||
|
@ -177,13 +167,15 @@ repositories {
|
||||||
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'
|
||||||
|
|
||||||
|
@ -205,12 +197,12 @@ publishing {
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}"
|
url publishRepository
|
||||||
credentials { username
|
credentials { username
|
||||||
password
|
password
|
||||||
|
|
||||||
username nexusUser
|
username publishUser
|
||||||
password nexusPassword
|
password publishPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
../gradlew
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
implementation-class=com.google.gradle.osdetector.OsDetectorPlugin
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: meerkat.voting.gui.configuration.Converter
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "us.kirchmeier.capsule" version '1.0.2'
|
||||||
|
id 'com.google.protobuf' version '0.8.1'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'java'
|
||||||
|
apply plugin: 'com.google.protobuf'
|
||||||
|
apply plugin: 'eclipse'
|
||||||
|
apply plugin: 'idea'
|
||||||
|
|
||||||
|
apply plugin: 'maven-publish'
|
||||||
|
|
||||||
|
// Is this a snapshot version?
|
||||||
|
ext { isSnapshot = false }
|
||||||
|
|
||||||
|
ext {
|
||||||
|
groupId = 'org.factcenter.meerkat'
|
||||||
|
|
||||||
|
// Credentials for publishing repositories
|
||||||
|
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 = "Meerkat Voting Common Library"
|
||||||
|
|
||||||
|
// Your project version
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
version += "${isSnapshot ? '-SNAPSHOT' : ''}"
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
// Meerkat common
|
||||||
|
compile project(':meerkat-common')
|
||||||
|
compile project(':restful-api-common')
|
||||||
|
|
||||||
|
|
||||||
|
// Databases
|
||||||
|
compile 'org.xerial:sqlite-jdbc:3.7.+'
|
||||||
|
|
||||||
|
// Depend on test resources from meerkat-common
|
||||||
|
testCompile project(path: ':meerkat-common', configuration: 'testOutput')
|
||||||
|
|
||||||
|
// Depend on server compilation for the non-integration tests
|
||||||
|
testCompile project(path: ':bulletin-board-server')
|
||||||
|
|
||||||
|
testCompile 'junit:junit:4.+'
|
||||||
|
testCompile 'org.hamcrest:hamcrest-all:1.3'
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
exclude '**/*IntegrationTest*'
|
||||||
|
// outputs.upToDateWhen { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
task integrationTest(type: Test) {
|
||||||
|
include '**/*IntegrationTest*'
|
||||||
|
// debug = true
|
||||||
|
outputs.upToDateWhen { false }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*==== You probably don't have to edit below this line =======*/
|
||||||
|
|
||||||
|
|
||||||
|
// Setup test configuration that can appear as a dependency in
|
||||||
|
// other subprojects
|
||||||
|
configurations {
|
||||||
|
testOutput.extendsFrom (testCompile)
|
||||||
|
}
|
||||||
|
|
||||||
|
task testJar(type: Jar, dependsOn: testClasses) {
|
||||||
|
classifier = 'tests'
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
|
||||||
|
artifacts {
|
||||||
|
testOutput testJar
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// The run task added by the application plugin
|
||||||
|
// is also of type JavaExec.
|
||||||
|
tasks.withType(JavaExec) {
|
||||||
|
// Assign all Java system properties from
|
||||||
|
// the command line to the JavaExec task.
|
||||||
|
systemProperties System.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
// Configure the protoc executable
|
||||||
|
protoc {
|
||||||
|
// Download from repositories
|
||||||
|
artifact = 'com.google.protobuf:protoc:3.+'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idea {
|
||||||
|
module {
|
||||||
|
project.sourceSets.each { sourceSet ->
|
||||||
|
|
||||||
|
def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java"
|
||||||
|
|
||||||
|
// add protobuf generated sources to generated source dir.
|
||||||
|
if ("test".equals(sourceSet.name)) {
|
||||||
|
testSourceDirs += file(srcDir)
|
||||||
|
} else {
|
||||||
|
sourceDirs += file(srcDir)
|
||||||
|
}
|
||||||
|
generatedSourceDirs += file(srcDir)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't exclude build directory
|
||||||
|
excludeDirs -= file(buildDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* "Fat" Build targets
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
if (project.hasProperty('mainClassName') && (mainClassName != null)) {
|
||||||
|
|
||||||
|
task mavenCapsule(type: MavenCapsule) {
|
||||||
|
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"
|
||||||
|
|
||||||
|
destinationDir = buildDir
|
||||||
|
|
||||||
|
def fatMain = hasProperty('fatmain') ? fatmain : mainClassName
|
||||||
|
|
||||||
|
applicationClass fatMain
|
||||||
|
|
||||||
|
def testJar = hasProperty('test')
|
||||||
|
|
||||||
|
if (hasProperty('fatmain')) {
|
||||||
|
appendix = "fat-${fatMain}"
|
||||||
|
} else {
|
||||||
|
appendix = "fat"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (testJar) {
|
||||||
|
from sourceSets.test.output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* Repositories
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
// Use local repo if possible
|
||||||
|
mavenLocal();
|
||||||
|
|
||||||
|
// Use 'maven central' for other dependencies.
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
task "info" {
|
||||||
|
doLast {
|
||||||
|
println "Project: ${project.name}"
|
||||||
|
println "Description: ${project.description}"
|
||||||
|
println "--------------------------"
|
||||||
|
println "GroupId: $groupId"
|
||||||
|
println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})"
|
||||||
|
println ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info.description 'Print some information about project parameters'
|
||||||
|
|
||||||
|
|
||||||
|
/*===================================
|
||||||
|
* Publishing
|
||||||
|
*===================================*/
|
||||||
|
|
||||||
|
publishing {
|
||||||
|
publications {
|
||||||
|
mavenJava(MavenPublication) {
|
||||||
|
groupId project.groupId
|
||||||
|
pom.withXml {
|
||||||
|
asNode().appendNode('description', project.description)
|
||||||
|
}
|
||||||
|
from project.components.java
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url publishRepository
|
||||||
|
credentials { username
|
||||||
|
password
|
||||||
|
|
||||||
|
username publishUser
|
||||||
|
password publishPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../gradlew
|
|
@ -0,0 +1,23 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BatchChunk;
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 17-Jan-16.
|
||||||
|
* Used to store the complete data required for sending a batch data list inside a single object
|
||||||
|
*/
|
||||||
|
public class BatchDataContainer {
|
||||||
|
|
||||||
|
public final MultiServerBatchIdentifier batchId;
|
||||||
|
public final List<BatchChunk> batchChunkList;
|
||||||
|
public final int startPosition;
|
||||||
|
|
||||||
|
public BatchDataContainer(MultiServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
|
||||||
|
this.batchId = batchId;
|
||||||
|
this.batchChunkList = batchChunkList;
|
||||||
|
this.startPosition = startPosition;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,82 +0,0 @@
|
||||||
package meerkat.bulletinboard;
|
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
|
||||||
*
|
|
||||||
* This class specifies the job that is required of a Bulletin Board Client Worker
|
|
||||||
*/
|
|
||||||
public class BulletinClientJob {
|
|
||||||
|
|
||||||
public static enum JobType{
|
|
||||||
POST_MESSAGE, // Post a message to servers
|
|
||||||
READ_MESSAGES, // Read messages according to some given filter (any server will do)
|
|
||||||
GET_REDUNDANCY // Check the redundancy of a specific message in the databases
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> serverAddresses;
|
|
||||||
|
|
||||||
private int minServers; // The minimal number of servers the job must be successful on for the job to be completed
|
|
||||||
|
|
||||||
private final JobType jobType;
|
|
||||||
|
|
||||||
private final Message payload; // The information associated with the job type
|
|
||||||
|
|
||||||
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
|
|
||||||
|
|
||||||
public BulletinClientJob(List<String> serverAddresses, int minServers, JobType jobType, Message payload, int maxRetry) {
|
|
||||||
this.serverAddresses = serverAddresses;
|
|
||||||
this.minServers = minServers;
|
|
||||||
this.jobType = jobType;
|
|
||||||
this.payload = payload;
|
|
||||||
this.maxRetry = maxRetry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateServerAddresses(List<String> newServerAdresses) {
|
|
||||||
this.serverAddresses = newServerAdresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getServerAddresses() {
|
|
||||||
return serverAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMinServers() {
|
|
||||||
return minServers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JobType getJobType() {
|
|
||||||
return jobType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message getPayload() {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxRetry() {
|
|
||||||
return maxRetry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void shuffleAddresses() {
|
|
||||||
Collections.shuffle(serverAddresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decMinServers(){
|
|
||||||
minServers--;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void decMaxRetry(){
|
|
||||||
if (maxRetry > 0) {
|
|
||||||
maxRetry--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRetry(){
|
|
||||||
return (maxRetry != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
package meerkat.bulletinboard;
|
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
|
||||||
*
|
|
||||||
* This class contains the end status and result of a Bulletin Board Client Job.
|
|
||||||
*/
|
|
||||||
public final class BulletinClientJobResult {
|
|
||||||
|
|
||||||
private final BulletinClientJob job; // Stores the job the result refers to
|
|
||||||
|
|
||||||
private final Message result; // The result of the job; valid only if success==true
|
|
||||||
|
|
||||||
public BulletinClientJobResult(BulletinClientJob job, Message result) {
|
|
||||||
this.job = job;
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BulletinClientJob getJob() {
|
|
||||||
return job;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Message getResult() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,217 +1,38 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
import com.google.protobuf.Message;
|
|
||||||
import meerkat.comm.CommunicationException;
|
|
||||||
import meerkat.crypto.Digest;
|
|
||||||
import meerkat.crypto.concrete.SHA256Digest;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
|
||||||
import meerkat.rest.Constants;
|
|
||||||
import meerkat.rest.ProtobufMessageBodyReader;
|
|
||||||
import meerkat.rest.ProtobufMessageBodyWriter;
|
|
||||||
|
|
||||||
import javax.ws.rs.ProcessingException;
|
|
||||||
import javax.ws.rs.client.Client;
|
|
||||||
import javax.ws.rs.client.ClientBuilder;
|
|
||||||
import javax.ws.rs.client.Entity;
|
|
||||||
import javax.ws.rs.client.WebTarget;
|
|
||||||
import javax.ws.rs.core.Response;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
||||||
*
|
*
|
||||||
* This class implements the actual communication with the Bulletin Board Servers.
|
* 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.
|
||||||
*/
|
*/
|
||||||
//TODO: Maybe make this abstract and inherit from it.
|
public abstract class BulletinClientWorker<IN> {
|
||||||
public class BulletinClientWorker implements Callable<BulletinClientJobResult> {
|
|
||||||
|
|
||||||
private final BulletinClientJob job; // The requested job to be handled
|
protected final IN payload; // Payload of the job
|
||||||
|
|
||||||
public BulletinClientWorker(BulletinClientJob job){
|
private int maxRetry; // Number of retries for this job; set to -1 for infinite retries
|
||||||
this.job = job;
|
|
||||||
|
public BulletinClientWorker(IN payload, int maxRetry) {
|
||||||
|
this.payload = payload;
|
||||||
|
this.maxRetry = maxRetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This resource enabled creation of a single Client per thread.
|
public IN getPayload() {
|
||||||
private static final ThreadLocal<Client> clientLocal =
|
return payload;
|
||||||
new ThreadLocal<Client> () {
|
}
|
||||||
@Override protected Client initialValue() {
|
|
||||||
Client client;
|
|
||||||
client = ClientBuilder.newClient();
|
|
||||||
client.register(ProtobufMessageBodyReader.class);
|
|
||||||
client.register(ProtobufMessageBodyWriter.class);
|
|
||||||
|
|
||||||
return client;
|
public int getMaxRetry() {
|
||||||
}
|
return maxRetry;
|
||||||
};
|
}
|
||||||
|
|
||||||
// This resource enables creation of a single Digest per thread.
|
|
||||||
private static final ThreadLocal<Digest> digestLocal =
|
|
||||||
new ThreadLocal<Digest> () {
|
|
||||||
@Override protected Digest initialValue() {
|
|
||||||
Digest digest;
|
|
||||||
digest = new SHA256Digest(); //TODO: Make this generic.
|
|
||||||
|
|
||||||
return digest;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method carries out the actual communication with the servers via HTTP Post
|
|
||||||
* It accesses the servers according to the job it received and updates said job as it goes
|
|
||||||
* The method will only iterate once through the server list, removing servers from the list when they are no longer required
|
|
||||||
* In a POST_MESSAGE job: successful post to a server results in removing the server from the list
|
|
||||||
* In a GET_REDUNDANCY job: no server is removed from the list and the (absolute) number of servers in which the message was found is returned
|
|
||||||
* In a READ_MESSAGES job: successful retrieval from any server terminates the method and returns the received values; The list is not changed
|
|
||||||
* @return The original job, modified to fit the current state and the required output (if any) of the operation
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* @throws CommunicationException
|
|
||||||
*/
|
|
||||||
public BulletinClientJobResult call() throws IllegalArgumentException, CommunicationException{
|
|
||||||
|
|
||||||
Client client = clientLocal.get();
|
|
||||||
Digest digest = digestLocal.get();
|
|
||||||
|
|
||||||
WebTarget webTarget;
|
|
||||||
Response response;
|
|
||||||
|
|
||||||
String requestPath;
|
|
||||||
Message msg;
|
|
||||||
|
|
||||||
List<String> serverAddresses = new LinkedList<String>(job.getServerAddresses());
|
|
||||||
|
|
||||||
Message payload = job.getPayload();
|
|
||||||
|
|
||||||
BulletinBoardMessageList msgList;
|
|
||||||
|
|
||||||
int count = 0; // Used to count number of servers which contain the required message in a GET_REDUNDANCY request.
|
|
||||||
|
|
||||||
job.shuffleAddresses(); // This is done to randomize the order of access to servers primarily for READ operations
|
|
||||||
|
|
||||||
// Prepare the request.
|
|
||||||
switch(job.getJobType()) {
|
|
||||||
|
|
||||||
case POST_MESSAGE:
|
|
||||||
// Make sure the payload is a BulletinBoardMessage
|
|
||||||
if (!(payload instanceof BulletinBoardMessage)) {
|
|
||||||
throw new IllegalArgumentException("Cannot post an object that is not an instance of BulletinBoardMessage");
|
|
||||||
}
|
|
||||||
|
|
||||||
msg = payload;
|
|
||||||
requestPath = Constants.POST_MESSAGE_PATH;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case READ_MESSAGES:
|
|
||||||
// Make sure the payload is a MessageFilterList
|
|
||||||
if (!(payload instanceof MessageFilterList)) {
|
|
||||||
throw new IllegalArgumentException("Read failed: an instance of MessageFilterList is required as payload for a READ_MESSAGES operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
msg = payload;
|
|
||||||
requestPath = Constants.READ_MESSAGES_PATH;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_REDUNDANCY:
|
|
||||||
// Make sure the payload is a MessageId
|
|
||||||
if (!(payload instanceof MessageID)) {
|
|
||||||
throw new IllegalArgumentException("Cannot search for an object that is not an instance of MessageID");
|
|
||||||
}
|
|
||||||
|
|
||||||
requestPath = Constants.READ_MESSAGES_PATH;
|
|
||||||
|
|
||||||
msg = MessageFilterList.newBuilder()
|
|
||||||
.addFilter(MessageFilter.newBuilder()
|
|
||||||
.setType(FilterType.MSG_ID)
|
|
||||||
.setId(((MessageID) payload).getID())
|
|
||||||
.build()
|
|
||||||
).build();
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported job type");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through servers
|
|
||||||
|
|
||||||
Iterator<String> addressIterator = serverAddresses.iterator();
|
|
||||||
|
|
||||||
while (addressIterator.hasNext()) {
|
|
||||||
|
|
||||||
// Send request to Server
|
|
||||||
String address = addressIterator.next();
|
|
||||||
webTarget = client.target(address).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(requestPath);
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
|
|
||||||
|
|
||||||
// Retrieve answer
|
|
||||||
switch(job.getJobType()) {
|
|
||||||
|
|
||||||
case POST_MESSAGE:
|
|
||||||
try {
|
|
||||||
|
|
||||||
response.readEntity(BoolMsg.class); // If a BoolMsg entity is returned: the post was successful
|
|
||||||
addressIterator.remove(); // Post to this server succeeded: remove server from list
|
|
||||||
job.decMinServers();
|
|
||||||
|
|
||||||
} catch (ProcessingException | IllegalStateException e) {} // Post to this server failed: retry next time
|
|
||||||
finally {
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GET_REDUNDANCY:
|
|
||||||
try {
|
|
||||||
msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful
|
|
||||||
|
|
||||||
if (msgList.getMessageList().size() > 0){ // Message was found in the server.
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
} catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server
|
|
||||||
finally {
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case READ_MESSAGES:
|
|
||||||
try {
|
|
||||||
msgList = response.readEntity(BulletinBoardMessageList.class); // If a BulletinBoardMessageList is returned: the read was successful
|
|
||||||
return new BulletinClientJobResult(job, msgList); // Return the result
|
|
||||||
} catch (ProcessingException | IllegalStateException e) {} // Read failed: try with next server
|
|
||||||
finally {
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return result (if haven't done so yet)
|
|
||||||
switch(job.getJobType()) {
|
|
||||||
|
|
||||||
case POST_MESSAGE:
|
|
||||||
// The job now contains the information required to ascertain whether enough server posts have succeeded
|
|
||||||
// It will also contain the list of servers in which the post was not successful
|
|
||||||
job.updateServerAddresses(serverAddresses);
|
|
||||||
return new BulletinClientJobResult(job, null);
|
|
||||||
|
|
||||||
case GET_REDUNDANCY:
|
|
||||||
// Return the number of servers in which the message was found
|
|
||||||
// The job now contains the list of these servers
|
|
||||||
return new BulletinClientJobResult(job, IntMsg.newBuilder().setValue(count).build());
|
|
||||||
|
|
||||||
case READ_MESSAGES:
|
|
||||||
// A successful operation would have already returned an output
|
|
||||||
// Therefore: no server access was successful
|
|
||||||
throw new CommunicationException("Could not access any server");
|
|
||||||
|
|
||||||
default: // This is required for successful compilation
|
|
||||||
throw new IllegalArgumentException("Unsupported job type");
|
|
||||||
|
|
||||||
|
public void decMaxRetry(){
|
||||||
|
if (maxRetry > 0) {
|
||||||
|
maxRetry--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isRetry(){
|
||||||
|
return (maxRetry != 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,490 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 03-Mar-16.
|
||||||
|
* 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
|
||||||
|
* Read operations are performed on the local server
|
||||||
|
* Batch reads are performed on the local server and, if they fail, also on the remote servers
|
||||||
|
* Write operations are performed on the local server
|
||||||
|
* A Synchronizer is employed in order to keep the remote server up to date
|
||||||
|
* After any read is carried out, a subscription is made for the specific query to make sure the local DB will be updated
|
||||||
|
* The database also employs a synchronizer which makes sure local data is sent to the remote servers
|
||||||
|
*/
|
||||||
|
public class CachedBulletinBoardClient implements SubscriptionBulletinBoardClient {
|
||||||
|
|
||||||
|
private final AsyncBulletinBoardClient localClient;
|
||||||
|
private final AsyncBulletinBoardClient remoteClient;
|
||||||
|
private final AsyncBulletinBoardClient queueClient;
|
||||||
|
private final BulletinBoardSubscriber subscriber;
|
||||||
|
private final BulletinBoardSynchronizer synchronizer;
|
||||||
|
|
||||||
|
private Thread syncThread;
|
||||||
|
|
||||||
|
private final static int DEFAULT_WAIT_CAP = 3000;
|
||||||
|
private final static int DEFAULT_SLEEP_INTERVAL = 3000;
|
||||||
|
|
||||||
|
private class SubscriptionStoreCallback implements FutureCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
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.remoteClient = remoteClient;
|
||||||
|
this.subscriber = subscriber;
|
||||||
|
this.queueClient = queueClient;
|
||||||
|
|
||||||
|
this.synchronizer = new SimpleBulletinBoardSynchronizer(sleepInterval,waitCap);
|
||||||
|
synchronizer.init(queueClient, remoteClient);
|
||||||
|
syncThread = new Thread(synchronizer);
|
||||||
|
syncThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Cached Client
|
||||||
|
* Used default values foe the time caps
|
||||||
|
* */
|
||||||
|
public CachedBulletinBoardClient(AsyncBulletinBoardClient localClient,
|
||||||
|
AsyncBulletinBoardClient remoteClient,
|
||||||
|
BulletinBoardSubscriber subscriber,
|
||||||
|
DeletableSubscriptionBulletinBoardClient queue) {
|
||||||
|
|
||||||
|
this(localClient, remoteClient, subscriber, queue, DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postMessage(final BulletinBoardMessage msg, final FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
return localClient.postMessage(msg, new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
remoteClient.postMessage(msg, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(final BulletinBoardMessage msg, final int chunkSize, final FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
return localClient.postAsBatch(msg, chunkSize, new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
remoteClient.postAsBatch(msg, chunkSize, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(final Iterable<String> tags, final FutureCallback<BatchIdentifier> callback) {
|
||||||
|
|
||||||
|
localClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
|
||||||
|
|
||||||
|
private BatchIdentifier localIdentifier;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BatchIdentifier result) {
|
||||||
|
|
||||||
|
localIdentifier = result;
|
||||||
|
|
||||||
|
remoteClient.beginBatch(tags, new FutureCallback<BatchIdentifier>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BatchIdentifier result) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(new CachedClientBatchIdentifier(localIdentifier, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList,
|
||||||
|
final int startPosition, final FutureCallback<Boolean> callback) throws IllegalArgumentException{
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, startPosition, new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, startPosition, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(final BatchIdentifier batchIdentifier, final List<BatchChunk> batchChunkList, final FutureCallback<Boolean> callback)
|
||||||
|
throws IllegalArgumentException{
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
localClient.postBatchData(identifier.getLocalIdentifier(), batchChunkList, new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
remoteClient.postBatchData(identifier.getRemoteIdentifier(), batchChunkList, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeBatch(final BatchIdentifier batchIdentifier, final Timestamp timestamp, final Iterable<Signature> signatures,
|
||||||
|
final FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof CachedClientBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final CachedClientBatchIdentifier identifier = (CachedClientBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
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
|
||||||
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
|
||||||
|
remoteClient.getRedundancy(id, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessages(MessageFilterList filterList, final FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
localClient.readMessages(filterList, callback);
|
||||||
|
|
||||||
|
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessage(final MessageID msgID, final FutureCallback<BulletinBoardMessage> callback) {
|
||||||
|
|
||||||
|
localClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BulletinBoardMessage result) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result); // Read from local client was successful
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Read from local unsuccessful: try to read from remote
|
||||||
|
|
||||||
|
remoteClient.readMessage(msgID, new FutureCallback<BulletinBoardMessage>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BulletinBoardMessage result) {
|
||||||
|
|
||||||
|
// Read from remote was successful: store in local and return result
|
||||||
|
|
||||||
|
localClient.postMessage(result, null);
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Read from remote was unsuccessful: report error
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatchData(final BulletinBoardMessage stub, final FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
localClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BulletinBoardMessage result) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result); // Read from local client was successful
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Read from local unsuccessful: try to read from remote
|
||||||
|
|
||||||
|
remoteClient.readBatchData(stub, new FutureCallback<BulletinBoardMessage>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BulletinBoardMessage result) {
|
||||||
|
|
||||||
|
// Read from remote was successful: store in local and return result
|
||||||
|
|
||||||
|
localClient.postMessage(result, null);
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Read from remote was unsuccessful: report error
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
|
||||||
|
|
||||||
|
localClient.querySync(syncQuery, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/**
|
||||||
|
* This is a stub method
|
||||||
|
* All resources are assumed to be initialized
|
||||||
|
*/
|
||||||
|
public void init(BulletinBoardClientParams clientParams) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
|
return localClient.postMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
|
||||||
|
MessageID result = localClient.postAsBatch(msg, chunkSize);
|
||||||
|
remoteClient.postAsBatch(msg, chunkSize);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getRedundancy(MessageID id) throws CommunicationException {
|
||||||
|
return remoteClient.getRedundancy(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException {
|
||||||
|
subscriber.subscribe(filterList, new SubscriptionStoreCallback());
|
||||||
|
return localClient.readMessages(filterList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
|
||||||
|
|
||||||
|
BulletinBoardMessage result = null;
|
||||||
|
try {
|
||||||
|
result = localClient.readMessage(msgID);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
//TODO: log
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null){
|
||||||
|
result = remoteClient.readMessage(msgID);
|
||||||
|
|
||||||
|
if (result != null){
|
||||||
|
localClient.postMessage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
|
||||||
|
|
||||||
|
BulletinBoardMessage result = null;
|
||||||
|
try {
|
||||||
|
result = localClient.readBatchData(stub);
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
//TODO: log
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null){
|
||||||
|
result = remoteClient.readBatchData(stub);
|
||||||
|
|
||||||
|
if (result != null){
|
||||||
|
localClient.postMessage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
|
||||||
|
return localClient.generateSyncQuery(generateSyncQueryParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
localClient.close();
|
||||||
|
remoteClient.close();
|
||||||
|
synchronizer.stop();
|
||||||
|
try {
|
||||||
|
syncThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//TODO: log interruption
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
subscriber.subscribe(filterList, new SubscriptionStoreCallback(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
subscriber.subscribe(filterList, startEntry, new SubscriptionStoreCallback(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 17-Jun-16.
|
||||||
|
*/
|
||||||
|
public final class CachedClientBatchIdentifier implements BatchIdentifier {
|
||||||
|
|
||||||
|
// Per-server identifiers
|
||||||
|
private final BatchIdentifier localIdentifier;
|
||||||
|
private final BatchIdentifier remoteIdentifier;
|
||||||
|
|
||||||
|
public CachedClientBatchIdentifier(BatchIdentifier localIdentifier, BatchIdentifier remoteIdentifier) {
|
||||||
|
this.localIdentifier = localIdentifier;
|
||||||
|
this.remoteIdentifier = remoteIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchIdentifier getLocalIdentifier() {
|
||||||
|
return localIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchIdentifier getRemoteIdentifier() {
|
||||||
|
return remoteIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,689 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.*;
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.comm.MessageInputStream.MessageInputStreamFactory;
|
||||||
|
import meerkat.comm.MessageOutputStream;
|
||||||
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 15-Mar-16.
|
||||||
|
* This client wraps a BulletinBoardServer in an asynchronous client.
|
||||||
|
* It is meant to be used as a local cache handler and for testing purposes.
|
||||||
|
* This means the access to the server is direct (via method calls) instead of through a TCP connection.
|
||||||
|
* The client implements both synchronous and asynchronous method calls, but calls to the server itself are performed synchronously.
|
||||||
|
*/
|
||||||
|
public class LocalBulletinBoardClient implements DeletableSubscriptionBulletinBoardClient {
|
||||||
|
|
||||||
|
private final DeletableBulletinBoardServer server;
|
||||||
|
private final ListeningScheduledExecutorService executorService;
|
||||||
|
private final BulletinBoardDigest digest;
|
||||||
|
private final long subsrciptionDelay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes an instance of the client
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
public LocalBulletinBoardClient(DeletableBulletinBoardServer server, int threadNum, int subscriptionDelay) {
|
||||||
|
this.server = server;
|
||||||
|
this.executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadNum));
|
||||||
|
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
|
||||||
|
this.subsrciptionDelay = subscriptionDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessagePoster implements Callable<Boolean> {
|
||||||
|
|
||||||
|
private final BulletinBoardMessage msg;
|
||||||
|
|
||||||
|
public MessagePoster(BulletinBoardMessage msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws CommunicationException {
|
||||||
|
return server.postMessage(msg).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new MessagePoster(msg)), callback);
|
||||||
|
|
||||||
|
digest.update(msg.getMsg());
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CompleteBatchPoster implements Callable<Boolean> {
|
||||||
|
|
||||||
|
private final BulletinBoardMessage msg;
|
||||||
|
private final int chunkSize;
|
||||||
|
|
||||||
|
public CompleteBatchPoster(BulletinBoardMessage msg, int chunkSize) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.chunkSize = chunkSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws CommunicationException {
|
||||||
|
|
||||||
|
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
|
||||||
|
.addAllTag(msg.getMsg().getTagList())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Int64Value batchId = server.beginBatch(beginBatchMessage);
|
||||||
|
|
||||||
|
BatchMessage.Builder builder = BatchMessage.newBuilder()
|
||||||
|
.setBatchId(batchId.getValue());
|
||||||
|
|
||||||
|
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
|
||||||
|
|
||||||
|
int i=0;
|
||||||
|
for (BatchChunk chunk : batchChunkList){
|
||||||
|
|
||||||
|
server.postBatchMessage(builder.setSerialNum(i).setData(chunk).build());
|
||||||
|
|
||||||
|
i++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, batchChunkList.size(), msg);
|
||||||
|
|
||||||
|
return server.closeBatch(closeBatchMessage).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new CompleteBatchPoster(msg, chunkSize)), callback);
|
||||||
|
|
||||||
|
digest.reset();
|
||||||
|
digest.update(msg);
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BatchBeginner implements Callable<SingleServerBatchIdentifier> {
|
||||||
|
|
||||||
|
private final BeginBatchMessage msg;
|
||||||
|
|
||||||
|
public BatchBeginner(BeginBatchMessage msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SingleServerBatchIdentifier call() throws Exception {
|
||||||
|
return new SingleServerBatchIdentifier(server.beginBatch(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
|
||||||
|
|
||||||
|
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
|
||||||
|
.addAllTag(tags)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new BatchBeginner(beginBatchMessage)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BatchDataPoster implements Callable<Boolean> {
|
||||||
|
|
||||||
|
private final SingleServerBatchIdentifier batchId;
|
||||||
|
private final List<BatchChunk> batchChunkList;
|
||||||
|
private final int startPosition;
|
||||||
|
|
||||||
|
public BatchDataPoster(SingleServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) {
|
||||||
|
this.batchId = batchId;
|
||||||
|
this.batchChunkList = batchChunkList;
|
||||||
|
this.startPosition = startPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws Exception {
|
||||||
|
|
||||||
|
BatchMessage.Builder msgBuilder = BatchMessage.newBuilder()
|
||||||
|
.setBatchId(batchId.getBatchId().getValue());
|
||||||
|
|
||||||
|
int i = startPosition;
|
||||||
|
for (BatchChunk data : batchChunkList){
|
||||||
|
|
||||||
|
msgBuilder.setSerialNum(i)
|
||||||
|
.setData(data);
|
||||||
|
|
||||||
|
if (!server.postBatchMessage(msgBuilder.build()).getValue())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
batchId.setLength(i);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition, FutureCallback<Boolean> callback)
|
||||||
|
throws IllegalArgumentException{
|
||||||
|
|
||||||
|
// Cast identifier to usable form
|
||||||
|
|
||||||
|
if (!(batchId instanceof SingleServerBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
|
||||||
|
|
||||||
|
// Add worker
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new BatchDataPoster(identifier, batchChunkList, startPosition)), callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchId, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) throws IllegalArgumentException{
|
||||||
|
postBatchData(batchId, batchChunkList, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class BatchCloser implements Callable<Boolean> {
|
||||||
|
|
||||||
|
private final CloseBatchMessage msg;
|
||||||
|
|
||||||
|
public BatchCloser(CloseBatchMessage msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call() throws Exception {
|
||||||
|
return server.closeBatch(msg).getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeBatch(BatchIdentifier batchId, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Cast identifier to usable form
|
||||||
|
|
||||||
|
if (!(batchId instanceof SingleServerBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchId;
|
||||||
|
|
||||||
|
// Add worker
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
|
||||||
|
.setBatchId(identifier.getBatchId().getValue())
|
||||||
|
.setBatchLength(identifier.getLength())
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.addAllSig(signatures)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new BatchCloser(closeBatchMessage)), callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RedundancyGetter implements Callable<Float> {
|
||||||
|
|
||||||
|
private final MessageID msgId;
|
||||||
|
|
||||||
|
public RedundancyGetter(MessageID msgId) {
|
||||||
|
this.msgId = msgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float call() throws Exception {
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(msgId.getID())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
|
||||||
|
MessageOutputStream<BulletinBoardMessage> outputStream = new MessageOutputStream<>(byteOutputStream);
|
||||||
|
server.readMessages(filterList,outputStream);
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(
|
||||||
|
new ByteArrayInputStream(byteOutputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
if (inputStream.isAvailable())
|
||||||
|
return 1.0f;
|
||||||
|
else
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
Futures.addCallback(executorService.submit(new RedundancyGetter(id)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageReader implements Callable<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
private final MessageFilterList filterList;
|
||||||
|
|
||||||
|
public MessageReader(MessageFilterList filterList) {
|
||||||
|
this.filterList = filterList;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BulletinBoardMessage> call() throws Exception {
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
|
||||||
|
MessageOutputStream<BulletinBoardMessage> outputStream = new MessageOutputStream<>(byteOutputStream);
|
||||||
|
server.readMessages(filterList, outputStream);
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(
|
||||||
|
new ByteArrayInputStream(byteOutputStream.toByteArray()),
|
||||||
|
BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
Futures.addCallback(executorService.submit(new MessageReader(filterList)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubscriptionCallback implements FutureCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
private MessageFilterList filterList;
|
||||||
|
private final FutureCallback<List<BulletinBoardMessage>> callback;
|
||||||
|
|
||||||
|
public SubscriptionCallback(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
this.filterList = filterList;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Report new messages to user
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
MessageFilterList.Builder filterBuilder = filterList.toBuilder();
|
||||||
|
|
||||||
|
// If any new messages arrived: update the MIN_ENTRY condition
|
||||||
|
if (result.size() > 0) {
|
||||||
|
|
||||||
|
// Remove last filter from list (MIN_ENTRY one)
|
||||||
|
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
filterList = filterBuilder.build();
|
||||||
|
|
||||||
|
// Reschedule job
|
||||||
|
Futures.addCallback(executorService.schedule(new MessageReader(filterList), subsrciptionDelay, TimeUnit.MILLISECONDS), this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify caller about failure and terminate subscription
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
MessageFilterList subscriptionFilterList =
|
||||||
|
filterList.toBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MIN_ENTRY)
|
||||||
|
.setEntry(startEntry)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new MessageReader(subscriptionFilterList)), new SubscriptionCallback(subscriptionFilterList, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
subscribe(filterList, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BatchDataReader implements Callable<List<BatchChunk>> {
|
||||||
|
|
||||||
|
private final MessageID msgID;
|
||||||
|
|
||||||
|
public BatchDataReader(MessageID msgID) {
|
||||||
|
this.msgID = msgID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BatchChunk> call() throws Exception {
|
||||||
|
|
||||||
|
BatchQuery batchQuery = BatchQuery.newBuilder()
|
||||||
|
.setMsgID(msgID)
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
|
||||||
|
MessageOutputStream<BatchChunk> batchOutputStream = new MessageOutputStream<>(byteOutputStream);
|
||||||
|
server.readBatch(batchQuery,batchOutputStream);
|
||||||
|
|
||||||
|
MessageInputStream<BatchChunk> inputStream =
|
||||||
|
MessageInputStreamFactory.createMessageInputStream(
|
||||||
|
new ByteArrayInputStream(byteOutputStream.toByteArray()),
|
||||||
|
BatchChunk.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CompleteBatchReader implements Callable<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
private final MessageID msgID;
|
||||||
|
|
||||||
|
public CompleteBatchReader(MessageID msgID) {
|
||||||
|
this.msgID = msgID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage call() throws Exception {
|
||||||
|
|
||||||
|
// Read message (mat be a stub)
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(msgID.getID())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MessageReader messageReader = new MessageReader(filterList);
|
||||||
|
List<BulletinBoardMessage> bulletinBoardMessages = messageReader.call();
|
||||||
|
|
||||||
|
if (bulletinBoardMessages.size() <= 0) {
|
||||||
|
throw new NotFoundException("Message does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
BulletinBoardMessage msg = bulletinBoardMessages.get(0);
|
||||||
|
|
||||||
|
if (msg.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
|
||||||
|
BatchDataReader batchDataReader = new BatchDataReader(msgID);
|
||||||
|
List<BatchChunk> batchChunkList = batchDataReader.call();
|
||||||
|
|
||||||
|
// Combine and return
|
||||||
|
|
||||||
|
return BulletinBoardUtils.gatherBatch(msg, batchChunkList);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BatchDataCombiner implements Callable<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
private final BulletinBoardMessage stub;
|
||||||
|
|
||||||
|
public BatchDataCombiner(BulletinBoardMessage stub) {
|
||||||
|
this.stub = stub;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage call() throws Exception {
|
||||||
|
|
||||||
|
MessageID msgID = MessageID.newBuilder().setID(stub.getMsg().getMsgId()).build();
|
||||||
|
|
||||||
|
BatchDataReader batchDataReader = new BatchDataReader(msgID);
|
||||||
|
|
||||||
|
List<BatchChunk> batchChunkList = batchDataReader.call();
|
||||||
|
|
||||||
|
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
|
||||||
|
Futures.addCallback(executorService.submit(new CompleteBatchReader(msgID)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
|
||||||
|
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
Futures.addCallback(executorService.submit(new BatchDataCombiner(stub)),callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncQueryHandler implements Callable<SyncQueryResponse> {
|
||||||
|
|
||||||
|
private final SyncQuery syncQuery;
|
||||||
|
|
||||||
|
public SyncQueryHandler(SyncQuery syncQuery) {
|
||||||
|
this.syncQuery = syncQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncQueryResponse call() throws Exception {
|
||||||
|
return server.querySync(syncQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
|
||||||
|
Futures.addCallback(executorService.submit(new SyncQueryHandler(syncQuery)), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is a stub, since the implementation only considers one server, and that is given in the constructor
|
||||||
|
* @param ignored is ignored
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init(BulletinBoardClientParams ignored) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
|
|
||||||
|
MessagePoster poster = new MessagePoster(msg);
|
||||||
|
poster.call();
|
||||||
|
|
||||||
|
digest.update(msg.getMsg());
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getRedundancy(MessageID id) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
RedundancyGetter getter = new RedundancyGetter(id);
|
||||||
|
return getter.call();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
MessageReader reader = new MessageReader(filterList);
|
||||||
|
return reader.call();
|
||||||
|
|
||||||
|
} catch (Exception e){
|
||||||
|
throw new CommunicationException("Error reading from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
|
||||||
|
|
||||||
|
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
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
server.close();
|
||||||
|
} catch (CommunicationException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 17-Jun-16.
|
||||||
|
*/
|
||||||
|
public final class MultiServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
|
||||||
|
|
||||||
|
// Per-server identifiers
|
||||||
|
private final Iterable<BatchIdentifier> identifiers;
|
||||||
|
|
||||||
|
public MultiServerBatchIdentifier(Iterable<BatchIdentifier> identifiers) {
|
||||||
|
this.identifiers = identifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiServerBatchIdentifier(BatchIdentifier[] identifiers) {
|
||||||
|
this.identifiers = Arrays.asList(identifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<BatchIdentifier> getIdentifiers() {
|
||||||
|
return identifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 09-Dec-15.
|
||||||
|
*
|
||||||
|
* This is a general class for handling multi-server work
|
||||||
|
* It utilizes Single Server Clients to perform the actual per-server work
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{
|
||||||
|
|
||||||
|
protected final List<SingleServerBulletinBoardClient> clients;
|
||||||
|
|
||||||
|
protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed
|
||||||
|
|
||||||
|
protected AtomicInteger maxFailedServers; // The maximal number of allowed server failures
|
||||||
|
|
||||||
|
private AtomicBoolean returnedResult;
|
||||||
|
|
||||||
|
private final FutureCallback<OUT> futureCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param clients contains a list of Single Server clients to handle requests
|
||||||
|
* @param shuffleClients is a boolean stating whether or not it is needed to shuffle the clients
|
||||||
|
* @param minServers is the minimal amount of servers needed in order to successfully complete the job
|
||||||
|
* @param payload is the payload for the job
|
||||||
|
* @param maxRetry is the maximal per-server retry count
|
||||||
|
* @param futureCallback contains the callback methods used to report the result back to the client
|
||||||
|
*/
|
||||||
|
public MultiServerWorker(List<SingleServerBulletinBoardClient> clients, boolean shuffleClients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
super(payload,maxRetry);
|
||||||
|
|
||||||
|
this.clients = clients;
|
||||||
|
if (shuffleClients){
|
||||||
|
Collections.shuffle(clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minServers = new AtomicInteger(minServers);
|
||||||
|
maxFailedServers = new AtomicInteger(clients.size() - minServers);
|
||||||
|
this.futureCallback = futureCallback;
|
||||||
|
|
||||||
|
returnedResult = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor overload without client shuffling
|
||||||
|
*/
|
||||||
|
public MultiServerWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
this(clients, false, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to report a successful operation to the client
|
||||||
|
* Only reports once to the client
|
||||||
|
* @param result is the result
|
||||||
|
*/
|
||||||
|
protected void succeed(OUT result){
|
||||||
|
if (returnedResult.compareAndSet(false, true)) {
|
||||||
|
if (futureCallback != null)
|
||||||
|
futureCallback.onSuccess(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to report a failed operation to the client
|
||||||
|
* Only reports once to the client
|
||||||
|
* @param t contains the error/exception that occurred
|
||||||
|
*/
|
||||||
|
protected void fail(Throwable t){
|
||||||
|
if (returnedResult.compareAndSet(false, true)) {
|
||||||
|
if (futureCallback != null)
|
||||||
|
futureCallback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getClientNumber() {
|
||||||
|
return clients.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,13 +1,16 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.BoolValue;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
import meerkat.bulletinboard.workers.singleserver.*;
|
||||||
import meerkat.comm.CommunicationException;
|
import meerkat.comm.CommunicationException;
|
||||||
import meerkat.crypto.Digest;
|
|
||||||
import meerkat.crypto.concrete.SHA256Digest;
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
import meerkat.protobuf.BulletinBoardApi;
|
||||||
import meerkat.protobuf.Voting;
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
import meerkat.protobuf.Voting.BulletinBoardClientParams;
|
import meerkat.protobuf.Voting.*;
|
||||||
import meerkat.rest.*;
|
import meerkat.rest.*;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -17,31 +20,35 @@ import javax.ws.rs.client.Entity;
|
||||||
import javax.ws.rs.client.WebTarget;
|
import javax.ws.rs.client.WebTarget;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
* 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{
|
||||||
|
|
||||||
private List<String> meerkatDBs;
|
protected List<String> meerkatDBs;
|
||||||
|
|
||||||
private Client client;
|
protected Client client;
|
||||||
|
|
||||||
private Digest digest;
|
protected BulletinBoardDigest digest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores database locations and initializes the web Client
|
* Stores database locations and initializes the web Client
|
||||||
* @param clientParams contains the data needed to access the DBs
|
* @param clientParams contains the data needed to access the DBs
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public void init(Voting.BulletinBoardClientParams clientParams) {
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
meerkatDBs = clientParams.getBulletinBoardAddressList();
|
this.meerkatDBs = clientParams.getBulletinBoardAddressList();
|
||||||
|
|
||||||
client = ClientBuilder.newClient();
|
client = ClientBuilder.newClient();
|
||||||
client.register(ProtobufMessageBodyReader.class);
|
client.register(ProtobufMessageBodyReader.class);
|
||||||
client.register(ProtobufMessageBodyWriter.class);
|
client.register(ProtobufMessageBodyWriter.class);
|
||||||
|
|
||||||
digest = new SHA256Digest();
|
// Wrap the Digest into a BatchDigest
|
||||||
|
digest = new GenericBulletinBoardDigest(new SHA256Digest());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,23 +59,20 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* @return the message ID for later retrieval
|
* @return the message ID for later retrieval
|
||||||
* @throws CommunicationException
|
* @throws CommunicationException
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException {
|
||||||
|
|
||||||
WebTarget webTarget;
|
WebTarget webTarget;
|
||||||
Response response;
|
Response response = null;
|
||||||
|
|
||||||
// Post message to all databases
|
// Post message to all databases
|
||||||
try {
|
try {
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.POST_MESSAGE_PATH);
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF));
|
|
||||||
|
|
||||||
// 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(BoolMsg.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());
|
||||||
|
@ -88,7 +92,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* @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;
|
||||||
|
@ -104,7 +108,7 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
|
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
try {
|
try {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.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));
|
||||||
|
|
||||||
|
@ -123,39 +127,203 @@ public class SimpleBulletinBoardClient{ //implements BulletinBoardClient {
|
||||||
* 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
|
* @return the list of Bulletin Board messages that are returned from a server
|
||||||
*/
|
*/
|
||||||
// @Override
|
@Override
|
||||||
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) {
|
public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{
|
||||||
WebTarget webTarget;
|
|
||||||
Response response;
|
|
||||||
BulletinBoardMessageList messageList;
|
|
||||||
|
|
||||||
// Replace null filter list with blank one.
|
// Replace null filter list with blank one.
|
||||||
if (filterList == null){
|
if (filterList == null){
|
||||||
filterList = MessageFilterList.newBuilder().build();
|
filterList = MessageFilterList.getDefaultInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String exceptionString = "";
|
||||||
|
|
||||||
for (String db : meerkatDBs) {
|
for (String db : meerkatDBs) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
webTarget = client.target(db).path(Constants.BULLETIN_BOARD_SERVER_PATH).path(Constants.READ_MESSAGES_PATH);
|
|
||||||
|
|
||||||
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF));
|
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0);
|
||||||
|
|
||||||
messageList = response.readEntity(BulletinBoardMessageList.class);
|
List<BulletinBoardMessage> result = worker.call();
|
||||||
|
|
||||||
if (messageList != null){
|
return result;
|
||||||
return messageList.getMessageList();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) {
|
||||||
|
//TODO: log
|
||||||
|
exceptionString += e.getMessage() + "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException {
|
||||||
|
|
||||||
|
List<BatchChunk> chunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
|
||||||
|
|
||||||
|
BeginBatchMessage beginBatchMessage = BulletinBoardUtils.generateBeginBatchMessage(msg);
|
||||||
|
|
||||||
|
boolean posted = false;
|
||||||
|
|
||||||
|
// Post message to all databases
|
||||||
|
|
||||||
|
for (String db : meerkatDBs) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
|
||||||
|
SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(db, beginBatchMessage, 0);
|
||||||
|
|
||||||
|
Int64Value batchId = beginBatchWorker.call();
|
||||||
|
|
||||||
|
BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(batchId.getValue());
|
||||||
|
|
||||||
|
for (BatchChunk batchChunk : chunkList) {
|
||||||
|
|
||||||
|
SingleServerPostBatchWorker postBatchWorker =
|
||||||
|
new SingleServerPostBatchWorker(
|
||||||
|
db,
|
||||||
|
builder.setData(batchChunk).setSerialNum(pos).build(),
|
||||||
|
0);
|
||||||
|
|
||||||
|
postBatchWorker.call();
|
||||||
|
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage = BulletinBoardUtils.generateCloseBatchMessage(batchId, chunkList.size(), msg);
|
||||||
|
|
||||||
|
SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(db, closeBatchMessage, 0);
|
||||||
|
|
||||||
|
closeBatchWorker.call();
|
||||||
|
|
||||||
|
posted = true;
|
||||||
|
|
||||||
|
} catch(Exception ignored) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!posted){
|
||||||
|
throw new CommunicationException("Could not post to any server");
|
||||||
|
}
|
||||||
|
|
||||||
|
digest.reset();
|
||||||
|
digest.update(msg);
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException {
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(msgID.getID())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BatchQuery batchQuery = BatchQuery.newBuilder()
|
||||||
|
.setMsgID(msgID)
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String exceptionString = "";
|
||||||
|
|
||||||
|
for (String db : meerkatDBs) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
SingleServerReadMessagesWorker messagesWorker = new SingleServerReadMessagesWorker(db, filterList, 0);
|
||||||
|
|
||||||
|
List<BulletinBoardMessage> messages = messagesWorker.call();
|
||||||
|
|
||||||
|
if (messages == null || messages.size() < 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BulletinBoardMessage stub = messages.get(0);
|
||||||
|
|
||||||
|
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
|
||||||
|
|
||||||
|
List<BatchChunk> batchChunkList = batchWorker.call();
|
||||||
|
|
||||||
|
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
//TODO: log
|
||||||
|
exceptionString += e.getMessage() + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException {
|
||||||
|
|
||||||
|
if (stub.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID){
|
||||||
|
throw new IllegalArgumentException("Message is not a stub and does not contain the required message ID");
|
||||||
|
}
|
||||||
|
|
||||||
|
BatchQuery batchQuery = BatchQuery.newBuilder()
|
||||||
|
.setMsgID(MessageID.newBuilder()
|
||||||
|
.setID(stub.getMsg().getMsgId())
|
||||||
|
.build())
|
||||||
|
.setStartPosition(0)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String exceptionString = "";
|
||||||
|
|
||||||
|
for (String db : meerkatDBs) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(db, batchQuery, 0);
|
||||||
|
|
||||||
|
List<BatchChunk> batchChunkList = batchWorker.call();
|
||||||
|
|
||||||
|
return BulletinBoardUtils.gatherBatch(stub, batchChunkList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
//TODO: log
|
||||||
|
exceptionString += e.getMessage() + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CommunicationException("Could not find message in any DB. Errors follow:\n" + exceptionString);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException {
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
for (String db : meerkatDBs) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
|
||||||
|
|
||||||
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(generateSyncQueryParams, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
return response.readEntity(SyncQuery.class);
|
||||||
|
|
||||||
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CommunicationException("Could not contact any server");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
client.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void registerNewMessageCallback(MessageCallback callback, MessageFilterList filterList) {
|
|
||||||
// callback.handleNewMessage(readMessages(filterList));
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,241 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel on 13/04/2016.
|
||||||
|
* Simple, straightforward implementation of the {@link BulletinBoardSynchronizer} interface
|
||||||
|
*/
|
||||||
|
public class SimpleBulletinBoardSynchronizer implements BulletinBoardSynchronizer {
|
||||||
|
|
||||||
|
private DeletableSubscriptionBulletinBoardClient localClient;
|
||||||
|
private AsyncBulletinBoardClient remoteClient;
|
||||||
|
|
||||||
|
private AtomicBoolean running;
|
||||||
|
private volatile SyncStatus syncStatus;
|
||||||
|
|
||||||
|
private List<FutureCallback<Integer>> messageCountCallbacks;
|
||||||
|
private List<FutureCallback<SyncStatus>> syncStatusCallbacks;
|
||||||
|
|
||||||
|
private static final MessageFilterList EMPTY_FILTER = MessageFilterList.getDefaultInstance();
|
||||||
|
private static final int DEFAULT_SLEEP_INTERVAL = 10000; // 10 Seconds
|
||||||
|
private static final int DEFAULT_WAIT_CAP = 300000; // 5 minutes wait before deciding that the sync has failed fatally
|
||||||
|
|
||||||
|
private final int SLEEP_INTERVAL;
|
||||||
|
private final int WAIT_CAP;
|
||||||
|
|
||||||
|
private Semaphore semaphore;
|
||||||
|
|
||||||
|
private class SyncCallback implements FutureCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Notify Message Count callbacks if needed
|
||||||
|
|
||||||
|
if (syncStatus != SyncStatus.SYNCHRONIZED || result.size() > 0) {
|
||||||
|
|
||||||
|
for (FutureCallback<Integer> callback : messageCountCallbacks){
|
||||||
|
callback.onSuccess(result.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle upload and status change
|
||||||
|
|
||||||
|
SyncStatus newStatus = SyncStatus.PENDING;
|
||||||
|
|
||||||
|
if (result.size() == 0) {
|
||||||
|
newStatus = SyncStatus.SYNCHRONIZED;
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
else{ // Upload messages
|
||||||
|
|
||||||
|
for (BulletinBoardMessage message : result){
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (message.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
|
||||||
|
|
||||||
|
// This is a batch message: need to upload batch data as well as the message itself
|
||||||
|
|
||||||
|
BulletinBoardMessage completeMsg = localClient.readBatchData(message);
|
||||||
|
|
||||||
|
remoteClient.postMessage(completeMsg);
|
||||||
|
|
||||||
|
localClient.deleteMessage(completeMsg.getEntryNum());
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// This is a regular message: post it
|
||||||
|
remoteClient.postMessage(message);
|
||||||
|
|
||||||
|
localClient.deleteMessage(message.getEntryNum());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
// This is an error with the local server
|
||||||
|
// TODO: log
|
||||||
|
updateSyncStatus(SyncStatus.SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSyncStatus(newStatus);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
updateSyncStatus(SyncStatus.SERVER_ERROR);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleBulletinBoardSynchronizer(int sleepInterval, int waitCap) {
|
||||||
|
this.syncStatus = SyncStatus.STOPPED;
|
||||||
|
this.SLEEP_INTERVAL = sleepInterval;
|
||||||
|
this.WAIT_CAP = waitCap;
|
||||||
|
this.running = new AtomicBoolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleBulletinBoardSynchronizer() {
|
||||||
|
this(DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void updateSyncStatus(SyncStatus newStatus) {
|
||||||
|
|
||||||
|
if (!running.get()) {
|
||||||
|
|
||||||
|
newStatus = SyncStatus.STOPPED;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newStatus != syncStatus){
|
||||||
|
|
||||||
|
syncStatus = newStatus;
|
||||||
|
|
||||||
|
for (FutureCallback<SyncStatus> callback : syncStatusCallbacks){
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(syncStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(DeletableSubscriptionBulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient) {
|
||||||
|
|
||||||
|
updateSyncStatus(SyncStatus.STOPPED);
|
||||||
|
|
||||||
|
this.localClient = localClient;
|
||||||
|
this.remoteClient = remoteClient;
|
||||||
|
|
||||||
|
messageCountCallbacks = new LinkedList<>();
|
||||||
|
syncStatusCallbacks = new LinkedList<>();
|
||||||
|
|
||||||
|
semaphore = new Semaphore(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncStatus getSyncStatus() {
|
||||||
|
return syncStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribeToSyncStatus(FutureCallback<SyncStatus> callback) {
|
||||||
|
syncStatusCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BulletinBoardMessage> getRemainingMessages() throws CommunicationException{
|
||||||
|
return localClient.readMessages(EMPTY_FILTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getRemainingMessages(FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
localClient.readMessages(EMPTY_FILTER, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getRemainingMessagesCount() throws CommunicationException {
|
||||||
|
return localClient.readMessages(EMPTY_FILTER).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribeToRemainingMessagesCount(FutureCallback<Integer> callback) {
|
||||||
|
messageCountCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
if (running.compareAndSet(false,true)){
|
||||||
|
|
||||||
|
updateSyncStatus(SyncStatus.PENDING);
|
||||||
|
SyncCallback callback = new SyncCallback();
|
||||||
|
|
||||||
|
while (syncStatus != SyncStatus.STOPPED) {
|
||||||
|
|
||||||
|
do {
|
||||||
|
|
||||||
|
localClient.readMessages(EMPTY_FILTER, callback);
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
semaphore.tryAcquire(WAIT_CAP, TimeUnit.MILLISECONDS);
|
||||||
|
//TODO: log hard error. Too much time trying to upload data.
|
||||||
|
|
||||||
|
} catch (InterruptedException ignored) {
|
||||||
|
// We expect an interruption when the upload will complete
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (syncStatus == SyncStatus.PENDING);
|
||||||
|
|
||||||
|
// Database is synced. Wait for new data.
|
||||||
|
|
||||||
|
try {
|
||||||
|
semaphore.tryAcquire(SLEEP_INTERVAL, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//TODO: log (probably nudged)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void nudge() {
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stop() {
|
||||||
|
|
||||||
|
running.set(false);
|
||||||
|
updateSyncStatus(SyncStatus.STOPPED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 16-Jun-16.
|
||||||
|
* Single-server implementation of the BatchIdentifier interface
|
||||||
|
*/
|
||||||
|
final class SingleServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier {
|
||||||
|
|
||||||
|
private final Int64Value batchId;
|
||||||
|
|
||||||
|
private int length;
|
||||||
|
|
||||||
|
public SingleServerBatchIdentifier(Int64Value batchId) {
|
||||||
|
this.batchId = batchId;
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleServerBatchIdentifier(long batchId) {
|
||||||
|
this(Int64Value.newBuilder().setValue(batchId).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Int64Value getBatchId() {
|
||||||
|
return batchId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides the existing length with the new one only if the new length is longer
|
||||||
|
* @param newLength
|
||||||
|
*/
|
||||||
|
public void setLength(int newLength) {
|
||||||
|
if (newLength > length) {
|
||||||
|
length = newLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,875 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.bulletinboard.workers.singleserver.*;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.protobuf.Voting.BulletinBoardClientParams;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import java.lang.Iterable;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 28-Dec-15.
|
||||||
|
*
|
||||||
|
* This class implements the asynchronous Bulletin Board Client interface
|
||||||
|
* It only handles a single Bulletin Board Server
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoardClient {
|
||||||
|
|
||||||
|
protected Client client;
|
||||||
|
|
||||||
|
protected BulletinBoardDigest digest;
|
||||||
|
|
||||||
|
private String dbAddress;
|
||||||
|
|
||||||
|
private final int MAX_RETRIES = 11;
|
||||||
|
|
||||||
|
private final ListeningScheduledExecutorService executorService;
|
||||||
|
|
||||||
|
private long lastServerErrorTime;
|
||||||
|
|
||||||
|
private final long FAIL_DELAY_IN_MILLISECONDS;
|
||||||
|
|
||||||
|
private final long SUBSCRIPTION_INTERVAL_IN_MILLISECONDS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the client that a job has failed
|
||||||
|
* This makes new scheduled jobs be scheduled for a later time (after the given delay)
|
||||||
|
*/
|
||||||
|
protected void fail() {
|
||||||
|
|
||||||
|
// Update last fail time
|
||||||
|
lastServerErrorTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
* If the server is in an accessible state: the job is submitted for immediate handling
|
||||||
|
* If the server is not accessible: the job is scheduled for a later time
|
||||||
|
* @param worker is the worker that should be scheduled for work
|
||||||
|
* @param callback is the class containing callbacks for handling job completion/failure
|
||||||
|
*/
|
||||||
|
protected void scheduleWorker(SingleServerWorker worker, FutureCallback callback){
|
||||||
|
|
||||||
|
long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime;
|
||||||
|
|
||||||
|
if (timeSinceLastServerError >= FAIL_DELAY_IN_MILLISECONDS) {
|
||||||
|
|
||||||
|
// Schedule for immediate processing
|
||||||
|
Futures.addCallback(executorService.submit(worker), callback);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Schedule for processing immediately following delay expiry
|
||||||
|
Futures.addCallback(executorService.schedule(
|
||||||
|
worker,
|
||||||
|
FAIL_DELAY_IN_MILLISECONDS - timeSinceLastServerError,
|
||||||
|
TimeUnit.MILLISECONDS),
|
||||||
|
callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class for handling simple operation results and retrying if needed
|
||||||
|
*/
|
||||||
|
class RetryCallback<T> implements FutureCallback<T> {
|
||||||
|
|
||||||
|
private final SingleServerWorker worker;
|
||||||
|
private final FutureCallback<T> futureCallback;
|
||||||
|
|
||||||
|
public RetryCallback(SingleServerWorker worker, FutureCallback<T> futureCallback) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.futureCallback = futureCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(T result) {
|
||||||
|
if (futureCallback != null)
|
||||||
|
futureCallback.onSuccess(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify client about failure
|
||||||
|
fail();
|
||||||
|
|
||||||
|
// Check if another attempt should be made
|
||||||
|
|
||||||
|
worker.decMaxRetry();
|
||||||
|
|
||||||
|
if (worker.isRetry()) {
|
||||||
|
// Perform another attempt
|
||||||
|
scheduleWorker(worker, this);
|
||||||
|
} else {
|
||||||
|
// No more retries: notify caller about failure
|
||||||
|
if (futureCallback != null)
|
||||||
|
futureCallback.onFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback ties together all the per-batch-data callbacks into a single callback
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
class PostBatchChunkListCallback implements FutureCallback<Boolean> {
|
||||||
|
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
private AtomicInteger batchDataRemaining;
|
||||||
|
private AtomicBoolean aggregatedResult;
|
||||||
|
|
||||||
|
public PostBatchChunkListCallback(int batchDataLength, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
|
this.batchDataRemaining = new AtomicInteger(batchDataLength);
|
||||||
|
this.aggregatedResult = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
|
||||||
|
if (result){
|
||||||
|
this.aggregatedResult.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchDataRemaining.decrementAndGet() == 0){
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(this.aggregatedResult.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This callback receives a message which may be a stub
|
||||||
|
* If the message is not a stub: it returns it as is to a callback function
|
||||||
|
* If it is a stub: it schedules a read of the batch data which will return a complete message to the callback function
|
||||||
|
*/
|
||||||
|
class CompleteMessageReadCallback implements FutureCallback<List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
private final FutureCallback<BulletinBoardMessage> callback;
|
||||||
|
|
||||||
|
public CompleteMessageReadCallback(FutureCallback<BulletinBoardMessage> callback) {
|
||||||
|
|
||||||
|
this.callback = callback;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) {
|
||||||
|
callback.onSuccess(msg);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Create job with MAX retries for retrieval of the Batch Data List
|
||||||
|
|
||||||
|
BatchQuery batchQuery = BatchQuery.newBuilder()
|
||||||
|
.setMsgID(MessageID.newBuilder()
|
||||||
|
.setID(msg.getMsg().getMsgId())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES);
|
||||||
|
|
||||||
|
scheduleWorker(batchWorker, new ReadBatchCallback(msg, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class for handling returned values of subscription operations
|
||||||
|
* This class's methods also ensure continued operation of the subscription
|
||||||
|
*/
|
||||||
|
class SubscriptionCallback implements FutureCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
private SingleServerReadMessagesWorker worker;
|
||||||
|
private final FutureCallback<List<BulletinBoardMessage>> callback;
|
||||||
|
|
||||||
|
private MessageFilterList.Builder filterBuilder;
|
||||||
|
|
||||||
|
public SubscriptionCallback(SingleServerReadMessagesWorker worker, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
this.worker = worker;
|
||||||
|
this.callback = callback;
|
||||||
|
filterBuilder = worker.getPayload().toBuilder();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Report new messages to user
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
// Update filter if needed
|
||||||
|
|
||||||
|
if (result.size() > 0) {
|
||||||
|
|
||||||
|
// Remove last filter from list (MIN_ENTRY one)
|
||||||
|
filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), MAX_RETRIES);
|
||||||
|
|
||||||
|
RetryCallback<List<BulletinBoardMessage>> retryCallback = new RetryCallback<>(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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// Notify client about failure
|
||||||
|
fail();
|
||||||
|
|
||||||
|
// Notify caller about failure and terminate subscription
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleServerBulletinBoardClient(ListeningScheduledExecutorService executorService,
|
||||||
|
long failDelayInMilliseconds,
|
||||||
|
long subscriptionIntervalInMilliseconds) {
|
||||||
|
|
||||||
|
this.executorService = executorService;
|
||||||
|
|
||||||
|
this.FAIL_DELAY_IN_MILLISECONDS = failDelayInMilliseconds;
|
||||||
|
this.SUBSCRIPTION_INTERVAL_IN_MILLISECONDS = subscriptionIntervalInMilliseconds;
|
||||||
|
|
||||||
|
// Set server error time to a time sufficiently in the past to make new jobs go through
|
||||||
|
lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public SingleServerBulletinBoardClient(int threadPoolSize, long failDelayInMilliseconds, long subscriptionIntervalInMilliseconds) {
|
||||||
|
|
||||||
|
this(MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize)),
|
||||||
|
failDelayInMilliseconds,
|
||||||
|
subscriptionIntervalInMilliseconds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores database location, initializes the web Client and
|
||||||
|
* @param clientParams contains the data needed to access the DBs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
|
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
|
||||||
|
|
||||||
|
// Remove all but first DB address
|
||||||
|
this.dbAddress = clientParams.getBulletinBoardAddress(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
// Calculate the correct message ID and return it
|
||||||
|
digest.reset();
|
||||||
|
digest.update(msg.getMsg());
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostBatchDataCallback implements FutureCallback<Boolean> {
|
||||||
|
|
||||||
|
private final BulletinBoardMessage msg;
|
||||||
|
private final BatchIdentifier identifier;
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
public PostBatchDataCallback(BulletinBoardMessage msg, BatchIdentifier identifier, FutureCallback<Boolean> callback) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.identifier = identifier;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
closeBatch(
|
||||||
|
identifier,
|
||||||
|
msg.getMsg().getTimestamp(),
|
||||||
|
msg.getSigList(),
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ContinueBatchCallback implements FutureCallback<BatchIdentifier> {
|
||||||
|
|
||||||
|
private final BulletinBoardMessage msg;
|
||||||
|
private final int chunkSize;
|
||||||
|
private final FutureCallback<Boolean> callback;
|
||||||
|
|
||||||
|
public ContinueBatchCallback(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
|
||||||
|
this.msg = msg;
|
||||||
|
this.chunkSize = chunkSize;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BatchIdentifier identifier) {
|
||||||
|
|
||||||
|
List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize);
|
||||||
|
|
||||||
|
postBatchData(
|
||||||
|
identifier,
|
||||||
|
batchChunkList,
|
||||||
|
0,
|
||||||
|
new PostBatchDataCallback(msg, identifier, callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
beginBatch(
|
||||||
|
msg.getMsg().getTagList(),
|
||||||
|
new ContinueBatchCallback(msg, chunkSize, callback)
|
||||||
|
);
|
||||||
|
|
||||||
|
digest.update(msg);
|
||||||
|
|
||||||
|
return digest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BeginBatchCallback implements FutureCallback<Int64Value> {
|
||||||
|
|
||||||
|
private final FutureCallback<BatchIdentifier> callback;
|
||||||
|
|
||||||
|
public BeginBatchCallback(FutureCallback<BatchIdentifier> callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Int64Value result) {
|
||||||
|
callback.onSuccess(new SingleServerBatchIdentifier(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
callback.onFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
|
||||||
|
|
||||||
|
BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder()
|
||||||
|
.addAllTag(tags)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerBeginBatchWorker worker =
|
||||||
|
new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, new BeginBatchCallback(callback)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException{
|
||||||
|
|
||||||
|
// Cast identifier to usable form
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
// Update batch size
|
||||||
|
|
||||||
|
identifier.setLength(startPosition + batchChunkList.size());
|
||||||
|
|
||||||
|
// Create a unified callback to aggregate successful posts
|
||||||
|
|
||||||
|
PostBatchChunkListCallback listCallback = new PostBatchChunkListCallback(batchChunkList.size(), callback);
|
||||||
|
|
||||||
|
// Iterate through data list
|
||||||
|
|
||||||
|
BatchMessage.Builder builder = BatchMessage.newBuilder()
|
||||||
|
.setBatchId(identifier.getBatchId().getValue());
|
||||||
|
|
||||||
|
for (BatchChunk data : batchChunkList) {
|
||||||
|
builder.setSerialNum(startPosition).setData(data);
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerPostBatchWorker worker =
|
||||||
|
new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES);
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, listCallback));
|
||||||
|
|
||||||
|
// Increment position in batch
|
||||||
|
startPosition++;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
|
postBatchData(batchIdentifier, batchChunkList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Crypto.Signature> signatures, FutureCallback<Boolean> callback)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder()
|
||||||
|
.setBatchId(identifier.getBatchId().getValue())
|
||||||
|
.setBatchLength(identifier.getLength())
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.addAllSig(signatures)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Create worker with redundancy 1 and MAX_RETRIES retries
|
||||||
|
SingleServerCloseBatchWorker worker =
|
||||||
|
new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit worker and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
|
||||||
|
// Create worker with no retries
|
||||||
|
SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, 1);
|
||||||
|
|
||||||
|
// Submit job and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
// Create job with no retries
|
||||||
|
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, 1);
|
||||||
|
|
||||||
|
// Submit job and create callback
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
|
||||||
|
|
||||||
|
// Create job with MAX retries for retrieval of the Bulletin Board Message (which may be a stub)
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit jobs with wrapped callbacks
|
||||||
|
scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, new CompleteMessageReadCallback(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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
|
||||||
|
|
||||||
|
SingleServerQuerySyncWorker worker = new SingleServerQuerySyncWorker(dbAddress, syncQuery, MAX_RETRIES);
|
||||||
|
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
// Remove all existing MIN_ENTRY filters and create new one that starts at 0
|
||||||
|
|
||||||
|
MessageFilterList.Builder filterListBuilder = filterList.toBuilder();
|
||||||
|
|
||||||
|
Iterator<MessageFilter> iterator = filterListBuilder.getFilterList().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
MessageFilter filter = iterator.next();
|
||||||
|
|
||||||
|
if (filter.getType() == FilterType.MIN_ENTRY){
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filterListBuilder.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MIN_ENTRY)
|
||||||
|
.setEntry(startEntry)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// Create job with no retries
|
||||||
|
SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterListBuilder.build(), MAX_RETRIES);
|
||||||
|
|
||||||
|
// Submit job and create callback that retries on failure and handles repeated subscription
|
||||||
|
scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
subscribe(filterList, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
executorService.shutdown();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.rest.ProtobufMessageBodyReader;
|
||||||
|
import meerkat.rest.ProtobufMessageBodyWriter;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.ClientBuilder;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 02-Jan-16.
|
||||||
|
*/
|
||||||
|
public abstract class SingleServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Callable<OUT>{
|
||||||
|
|
||||||
|
// This resource enabled creation of a single Client per thread.
|
||||||
|
protected static final ThreadLocal<Client> clientLocal =
|
||||||
|
new ThreadLocal<Client> () {
|
||||||
|
@Override protected Client initialValue() {
|
||||||
|
Client client;
|
||||||
|
client = ClientBuilder.newClient();
|
||||||
|
client.register(ProtobufMessageBodyReader.class);
|
||||||
|
client.register(ProtobufMessageBodyWriter.class);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected final String serverAddress;
|
||||||
|
|
||||||
|
public SingleServerWorker(String serverAddress, IN payload, int maxRetry) {
|
||||||
|
super(payload, maxRetry);
|
||||||
|
this.serverAddress = serverAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServerAddress() {
|
||||||
|
return serverAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,42 +1,62 @@
|
||||||
package meerkat.bulletinboard;
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import meerkat.bulletinboard.callbacks.GetRedundancyFutureCallback;
|
|
||||||
import meerkat.bulletinboard.callbacks.PostMessageFutureCallback;
|
|
||||||
import meerkat.bulletinboard.callbacks.ReadMessagesFutureCallback;
|
|
||||||
import meerkat.comm.CommunicationException;
|
|
||||||
import meerkat.crypto.Digest;
|
|
||||||
import meerkat.crypto.concrete.SHA256Digest;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
|
||||||
import meerkat.protobuf.Voting;
|
|
||||||
|
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.bulletinboard.workers.multiserver.*;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
* Thread-based implementation of a Bulletin Board Client.
|
* Thread-based implementation of a Async Bulletin Board Client.
|
||||||
* Features:
|
* Features:
|
||||||
* 1. Handles tasks concurrently.
|
* 1. Handles tasks concurrently.
|
||||||
* 2. Retries submitting
|
* 2. Retries submitting
|
||||||
*/
|
*/
|
||||||
public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient {
|
||||||
|
|
||||||
private final static int THREAD_NUM = 10;
|
// Executor service for handling jobs
|
||||||
ListeningExecutorService listeningExecutor;
|
private final static int JOBS_THREAD_NUM = 5;
|
||||||
|
private ExecutorService executorService;
|
||||||
|
|
||||||
private Digest digest;
|
// Per-server clients
|
||||||
|
private List<SingleServerBulletinBoardClient> clients;
|
||||||
|
|
||||||
private List<String> meerkatDBs;
|
private BulletinBoardDigest batchDigest;
|
||||||
private String postSubAddress;
|
|
||||||
private String readSubAddress;
|
|
||||||
|
|
||||||
|
private final static int POST_MESSAGE_RETRY_NUM = 3;
|
||||||
private final static int READ_MESSAGES_RETRY_NUM = 1;
|
private final static int READ_MESSAGES_RETRY_NUM = 1;
|
||||||
|
private final static int GET_REDUNDANCY_RETRY_NUM = 1;
|
||||||
|
|
||||||
|
private final int SERVER_THREADPOOL_SIZE;
|
||||||
|
private final long FAIL_DELAY;
|
||||||
|
private final long SUBSCRIPTION_INTERVAL;
|
||||||
|
|
||||||
|
private static final int DEFAULT_SERVER_THREADPOOL_SIZE = 5;
|
||||||
|
private static final long DEFAULT_FAIL_DELAY = 5000;
|
||||||
|
private static final long DEFAULT_SUBSCRIPTION_INTERVAL = 10000;
|
||||||
|
|
||||||
private int minAbsoluteRedundancy;
|
private int minAbsoluteRedundancy;
|
||||||
|
|
||||||
|
|
||||||
|
public ThreadedBulletinBoardClient(int serverThreadpoolSize, long failDelay, long subscriptionInterval) {
|
||||||
|
SERVER_THREADPOOL_SIZE = serverThreadpoolSize;
|
||||||
|
FAIL_DELAY = failDelay;
|
||||||
|
SUBSCRIPTION_INTERVAL = subscriptionInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreadedBulletinBoardClient() {
|
||||||
|
this(DEFAULT_SERVER_THREADPOOL_SIZE, DEFAULT_FAIL_DELAY, DEFAULT_SUBSCRIPTION_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores database locations and initializes the web Client
|
* Stores database locations and initializes the web Client
|
||||||
* Stores the required minimum redundancy.
|
* Stores the required minimum redundancy.
|
||||||
|
@ -44,15 +64,29 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* @param clientParams contains the required information
|
* @param clientParams contains the required information
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void init(Voting.BulletinBoardClientParams clientParams) {
|
public void init(BulletinBoardClientParams clientParams) {
|
||||||
|
|
||||||
meerkatDBs = clientParams.getBulletinBoardAddressList();
|
super.init(clientParams);
|
||||||
|
|
||||||
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * meerkatDBs.size());
|
batchDigest = new GenericBulletinBoardDigest(digest);
|
||||||
|
|
||||||
listeningExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(THREAD_NUM));
|
minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount());
|
||||||
|
|
||||||
digest = new SHA256Digest();
|
executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM);
|
||||||
|
|
||||||
|
clients = new ArrayList<>(clientParams.getBulletinBoardAddressCount());
|
||||||
|
for (String address : clientParams.getBulletinBoardAddressList()){
|
||||||
|
|
||||||
|
SingleServerBulletinBoardClient client =
|
||||||
|
new SingleServerBulletinBoardClient(SERVER_THREADPOOL_SIZE, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
|
||||||
|
|
||||||
|
client.init(BulletinBoardClientParams.newBuilder()
|
||||||
|
.addBulletinBoardAddress(address)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
clients.add(client);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,21 +95,101 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* Retry failed DBs
|
* Retry failed DBs
|
||||||
* @param msg is the message,
|
* @param msg is the message,
|
||||||
* @return the message ID for later retrieval
|
* @return the message ID for later retrieval
|
||||||
* @throws CommunicationException
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public MessageID postMessage(BulletinBoardMessage msg, ClientCallback<?> callback){
|
public MessageID postMessage(BulletinBoardMessage msg, FutureCallback<Boolean> callback){
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.POST_MESSAGE, msg, -1);
|
MultiServerPostMessageWorker worker =
|
||||||
|
new MultiServerPostMessageWorker(clients, minAbsoluteRedundancy, msg, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new PostMessageFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
// 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 MessageID.newBuilder().setID(ByteString.copyFrom(digest.digest())).build();
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerPostBatchWorker worker =
|
||||||
|
new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, msg, chunkSize, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
// Calculate the correct message ID and return it
|
||||||
|
batchDigest.reset();
|
||||||
|
batchDigest.update(msg);
|
||||||
|
return batchDigest.digestAsMessageID();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) {
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerBeginBatchWorker worker =
|
||||||
|
new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, tags, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList,
|
||||||
|
int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
// Cast identifier to usable form
|
||||||
|
|
||||||
|
if (!(batchIdentifier instanceof MultiServerBatchIdentifier)){
|
||||||
|
throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) batchIdentifier;
|
||||||
|
|
||||||
|
BatchDataContainer batchDataContainer = new BatchDataContainer(identifier, batchChunkList, startPosition);
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerPostBatchDataWorker worker =
|
||||||
|
new MultiServerPostBatchDataWorker(clients, minAbsoluteRedundancy, batchDataContainer, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
|
||||||
|
postBatchData(batchIdentifier, batchChunkList, 0, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) payload;
|
||||||
|
|
||||||
|
// Create job
|
||||||
|
MultiServerCloseBatchWorker worker =
|
||||||
|
new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, identifier, timestamp, signatures, POST_MESSAGE_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,17 +197,16 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* 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
|
|
||||||
* @return the number of DBs in which retrieval was successful
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void getRedundancy(MessageID id, ClientCallback<Float> callback) {
|
public void getRedundancy(MessageID id, FutureCallback<Float> callback) {
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.GET_REDUNDANCY, id, 1);
|
MultiServerGetRedundancyWorker worker =
|
||||||
|
new MultiServerGetRedundancyWorker(clients, minAbsoluteRedundancy, id, GET_REDUNDANCY_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new GetRedundancyFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,27 +214,69 @@ public class ThreadedBulletinBoardClient implements BulletinBoardClient {
|
||||||
* 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).
|
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void readMessages(MessageFilterList filterList, ClientCallback<List<BulletinBoardMessage>> callback) {
|
public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
// Create job
|
// Create job
|
||||||
BulletinClientJob job = new BulletinClientJob(meerkatDBs, minAbsoluteRedundancy, BulletinClientJob.JobType.READ_MESSAGES,
|
MultiServerReadMessagesWorker worker =
|
||||||
filterList, READ_MESSAGES_RETRY_NUM);
|
new MultiServerReadMessagesWorker(clients, minAbsoluteRedundancy, filterList, READ_MESSAGES_RETRY_NUM, callback);
|
||||||
|
|
||||||
// Submit job and create callback
|
// Submit job
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), new ReadMessagesFutureCallback(listeningExecutor, callback));
|
executorService.submit(worker);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) {
|
||||||
|
|
||||||
|
//Create job
|
||||||
|
MultiServerReadMessageWorker worker =
|
||||||
|
new MultiServerReadMessageWorker(clients, minAbsoluteRedundancy, msgID, READ_MESSAGES_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
MultiServerReadBatchDataWorker worker =
|
||||||
|
new MultiServerReadBatchDataWorker(clients, minAbsoluteRedundancy, stub, READ_MESSAGES_RETRY_NUM, callback);
|
||||||
|
|
||||||
|
// Submit job
|
||||||
|
executorService.submit(worker);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is not supported by this class!
|
||||||
|
* This is because it has no meaning when considering more than one server without knowing which server will be contacted
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void querySync(SyncQuery syncQuery, FutureCallback<SyncQueryResponse> callback) {
|
||||||
|
callback.onFailure(new IllegalAccessError("querySync is not supported by this class"));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
listeningExecutor.shutdown();
|
|
||||||
while (! listeningExecutor.isShutdown()) {
|
for (SingleServerBulletinBoardClient client : clients){
|
||||||
listeningExecutor.awaitTermination(10, TimeUnit.SECONDS);
|
client.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
executorService.shutdown();
|
||||||
|
while (! executorService.isShutdown()) {
|
||||||
|
executorService.awaitTermination(10, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
System.err.println(e.getCause() + " " + e.getMessage());
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
|
||||||
|
import static meerkat.protobuf.BulletinBoardApi.FilterType.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 03-Mar-16.
|
||||||
|
* A multi-server implementation of the {@link BulletinBoardSubscriber}
|
||||||
|
*/
|
||||||
|
public class ThreadedBulletinBoardSubscriber implements BulletinBoardSubscriber {
|
||||||
|
|
||||||
|
protected final Collection<SubscriptionBulletinBoardClient> clients;
|
||||||
|
protected final BulletinBoardClient localClient;
|
||||||
|
|
||||||
|
protected Iterator<SubscriptionBulletinBoardClient> clientIterator;
|
||||||
|
protected SubscriptionBulletinBoardClient currentClient;
|
||||||
|
|
||||||
|
private long lastServerSwitchTime;
|
||||||
|
|
||||||
|
private AtomicBoolean isSyncInProgress;
|
||||||
|
private Semaphore rescheduleSemaphore;
|
||||||
|
|
||||||
|
private AtomicBoolean stopped;
|
||||||
|
|
||||||
|
private static final Float[] BREAKPOINTS = {0.5f, 0.75f, 0.9f, 0.95f, 0.99f, 0.999f};
|
||||||
|
|
||||||
|
public ThreadedBulletinBoardSubscriber(Collection<SubscriptionBulletinBoardClient> clients, BulletinBoardClient localClient) {
|
||||||
|
|
||||||
|
this.clients = clients;
|
||||||
|
this.localClient = localClient;
|
||||||
|
|
||||||
|
lastServerSwitchTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
clientIterator = clients.iterator();
|
||||||
|
currentClient = clientIterator.next();
|
||||||
|
|
||||||
|
isSyncInProgress = new AtomicBoolean(false);
|
||||||
|
rescheduleSemaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
stopped = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves to next client and performs resync with it
|
||||||
|
*/
|
||||||
|
private void nextClient() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
rescheduleSemaphore.acquire();
|
||||||
|
|
||||||
|
if (!clientIterator.hasNext()){
|
||||||
|
clientIterator = clients.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClient = clientIterator.next();
|
||||||
|
|
||||||
|
lastServerSwitchTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
isSyncInProgress.set(false);
|
||||||
|
|
||||||
|
rescheduleSemaphore.release();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TODO: log
|
||||||
|
// Do not change client
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class SubscriberCallback<T> implements FutureCallback<T> {
|
||||||
|
|
||||||
|
protected final MessageFilterList filterList;
|
||||||
|
protected final FutureCallback<List<BulletinBoardMessage>> callback;
|
||||||
|
private final long invocationTime;
|
||||||
|
|
||||||
|
public SubscriberCallback(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
this.filterList = filterList;
|
||||||
|
this.callback = callback;
|
||||||
|
this.invocationTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles resyncing process for the given subscription after a server is switched
|
||||||
|
* Specifically: generates a sync query from the local database and uses it to query the current server
|
||||||
|
*/
|
||||||
|
private void reSync() {
|
||||||
|
|
||||||
|
SyncQuery syncQuery = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
syncQuery = localClient.generateSyncQuery(GenerateSyncQueryParams.newBuilder()
|
||||||
|
.setFilterList(filterList)
|
||||||
|
.addAllBreakpointList(Arrays.asList(BREAKPOINTS))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
|
||||||
|
// Handle failure in standard way
|
||||||
|
onFailure(e);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
currentClient.querySync(syncQuery, new SyncQueryCallback(filterList, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reschedules the subscription
|
||||||
|
*/
|
||||||
|
private void reschedule() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
rescheduleSemaphore.acquire();
|
||||||
|
|
||||||
|
reSync();
|
||||||
|
|
||||||
|
rescheduleSemaphore.release();
|
||||||
|
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
|
||||||
|
//TODO: log
|
||||||
|
|
||||||
|
if (callback != null)
|
||||||
|
callback.onFailure(e); // Hard error: Cannot guarantee subscription safety
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
|
||||||
|
// If server failure is not already known: switch to next client and resync
|
||||||
|
if (invocationTime > lastServerSwitchTime){
|
||||||
|
|
||||||
|
// Make sure only what thread switches the client
|
||||||
|
if (isSyncInProgress.compareAndSet(false, true)){
|
||||||
|
nextClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
reschedule();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides handling logic for resync query callback operation
|
||||||
|
* Receives a SyncQueryResponse and reads the missing data (starting from the received timestamp) if needed
|
||||||
|
*/
|
||||||
|
protected class SyncQueryCallback extends SubscriberCallback<SyncQueryResponse> {
|
||||||
|
|
||||||
|
public SyncQueryCallback (MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
super(filterList, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SyncQueryResponse result) {
|
||||||
|
|
||||||
|
final Timestamp DEFAULT_TIME = BulletinBoardUtils.toTimestampProto(946728000); // Year 2000
|
||||||
|
|
||||||
|
// Read required messages according to received Timestamp
|
||||||
|
|
||||||
|
Timestamp syncTimestamp;
|
||||||
|
|
||||||
|
if (result.hasLastTimeOfSync()) {
|
||||||
|
syncTimestamp = result.getLastTimeOfSync(); // Use returned time of sync
|
||||||
|
} else {
|
||||||
|
syncTimestamp = DEFAULT_TIME; // Get all messages
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageFilterList timestampedFilterList = filterList.toBuilder()
|
||||||
|
.removeFilter(filterList.getFilterCount()-1) // Remove MIN_ENTRY filter
|
||||||
|
.addFilter(MessageFilter.newBuilder() // Add timestamp filter
|
||||||
|
.setType(AFTER_TIME)
|
||||||
|
.setTimestamp(syncTimestamp)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
currentClient.readMessages(timestampedFilterList, new ReSyncCallback(filterList, callback, result.getLastEntryNum()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides handling logic for callback of resyncing process
|
||||||
|
* Receives the missing messages, handles them and resubscribes
|
||||||
|
*/
|
||||||
|
protected class ReSyncCallback extends SubscriberCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
private long minEntry;
|
||||||
|
|
||||||
|
public ReSyncCallback (MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback, long minEntry) {
|
||||||
|
|
||||||
|
super(filterList, callback);
|
||||||
|
|
||||||
|
this.minEntry = minEntry;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Propagate result to caller
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
// Renew subscription
|
||||||
|
|
||||||
|
MessageFilterList newFilterList = filterList.toBuilder()
|
||||||
|
.removeFilter(filterList.getFilterCount()-1) // Remove current MIN_ENTRY filter
|
||||||
|
.addFilter(MessageFilter.newBuilder() // Add new MIN_ENTRY filter for current server
|
||||||
|
.setType(MIN_ENTRY)
|
||||||
|
.setEntry(minEntry)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
currentClient.subscribe(newFilterList, callback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the handling logic for results and failures of main subscription (while there are no errors)
|
||||||
|
*/
|
||||||
|
protected class SubscriptionCallback extends SubscriberCallback<List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
public SubscriptionCallback(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback){
|
||||||
|
super(filterList, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
// Propagate result to caller
|
||||||
|
if (callback != null)
|
||||||
|
callback.onSuccess(result);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, long startEntry, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
|
||||||
|
currentClient.subscribe(filterList, startEntry, new SubscriptionCallback(filterList, callback));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) {
|
||||||
|
subscribe(filterList, 0, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
package meerkat.bulletinboard.callbacks;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJob;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJobResult;
|
|
||||||
import meerkat.bulletinboard.BulletinClientWorker;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a future callback used to listen to workers and run on job finish
|
|
||||||
* Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error
|
|
||||||
*/
|
|
||||||
public abstract class ClientFutureCallback implements FutureCallback<BulletinClientJobResult> {
|
|
||||||
|
|
||||||
protected ListeningExecutorService listeningExecutor;
|
|
||||||
|
|
||||||
ClientFutureCallback(ListeningExecutorService listeningExecutor) {
|
|
||||||
this.listeningExecutor = listeningExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package meerkat.bulletinboard.callbacks;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import meerkat.bulletinboard.BulletinBoardClient;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJobResult;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a future callback used to listen to workers and run on job finish
|
|
||||||
* Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error
|
|
||||||
*/
|
|
||||||
public class GetRedundancyFutureCallback extends ClientFutureCallback {
|
|
||||||
|
|
||||||
private BulletinBoardClient.ClientCallback<Float> callback;
|
|
||||||
|
|
||||||
public GetRedundancyFutureCallback(ListeningExecutorService listeningExecutor,
|
|
||||||
BulletinBoardClient.ClientCallback<Float> callback) {
|
|
||||||
super(listeningExecutor);
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(BulletinClientJobResult result) {
|
|
||||||
|
|
||||||
int absoluteRedundancy = ((IntMsg) result.getResult()).getValue();
|
|
||||||
int totalServers = result.getJob().getServerAddresses().size();
|
|
||||||
|
|
||||||
callback.handleCallback( ((float) absoluteRedundancy) / ((float) totalServers) );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
callback.handleFailure(t);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package meerkat.bulletinboard.callbacks;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import meerkat.bulletinboard.BulletinBoardClient;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJob;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJobResult;
|
|
||||||
import meerkat.bulletinboard.BulletinClientWorker;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a future callback used to listen to workers and run on job finish
|
|
||||||
* Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error
|
|
||||||
*/
|
|
||||||
public class PostMessageFutureCallback extends ClientFutureCallback {
|
|
||||||
|
|
||||||
private BulletinBoardClient.ClientCallback<?> callback;
|
|
||||||
|
|
||||||
public PostMessageFutureCallback(ListeningExecutorService listeningExecutor,
|
|
||||||
BulletinBoardClient.ClientCallback<?> callback) {
|
|
||||||
super(listeningExecutor);
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(BulletinClientJobResult result) {
|
|
||||||
|
|
||||||
BulletinClientJob job = result.getJob();
|
|
||||||
|
|
||||||
job.decMaxRetry();
|
|
||||||
|
|
||||||
// If redundancy is below threshold: retry
|
|
||||||
if (job.getMinServers() > 0 && job.isRetry()) {
|
|
||||||
Futures.addCallback(listeningExecutor.submit(new BulletinClientWorker(job)), this);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback.handleCallback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
callback.handleFailure(t);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package meerkat.bulletinboard.callbacks;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
||||||
import meerkat.bulletinboard.BulletinBoardClient;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJob;
|
|
||||||
import meerkat.bulletinboard.BulletinClientJobResult;
|
|
||||||
import meerkat.bulletinboard.BulletinClientWorker;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a future callback used to listen to workers and run on job finish
|
|
||||||
* Depending on the type of job and the finishing status of the worker: a decision is made whether to retry or return an error
|
|
||||||
*/
|
|
||||||
public class ReadMessagesFutureCallback extends ClientFutureCallback {
|
|
||||||
|
|
||||||
private BulletinBoardClient.ClientCallback<List<BulletinBoardAPI.BulletinBoardMessage>> callback;
|
|
||||||
|
|
||||||
public ReadMessagesFutureCallback(ListeningExecutorService listeningExecutor,
|
|
||||||
BulletinBoardClient.ClientCallback<List<BulletinBoardAPI.BulletinBoardMessage>> callback) {
|
|
||||||
super(listeningExecutor);
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(BulletinClientJobResult result) {
|
|
||||||
|
|
||||||
callback.handleCallback(((BulletinBoardAPI.BulletinBoardMessageList) result.getResult()).getMessageList());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
callback.handleFailure(t);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerBatchIdentifier;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerBeginBatchWorker extends MultiServerWorker<Iterable<String>, BatchIdentifier> {
|
||||||
|
|
||||||
|
private BatchIdentifier[] identifiers;
|
||||||
|
private AtomicInteger remainingServers;
|
||||||
|
|
||||||
|
public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, Iterable<String> payload, int maxRetry,
|
||||||
|
FutureCallback<BatchIdentifier> 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
|
||||||
|
public void onSuccess(BatchIdentifier result) {
|
||||||
|
succeed(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
fail(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
int clientNum = 0;
|
||||||
|
|
||||||
|
for (SingleServerBulletinBoardClient client : clients){
|
||||||
|
|
||||||
|
client.beginBatch(payload, new BeginBatchCallback(clientNum));
|
||||||
|
|
||||||
|
clientNum++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
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.crypto.DigitalSignature;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.protobuf.Crypto.Signature;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerCloseBatchWorker extends MultiServerWorker<MultiServerBatchIdentifier, Boolean> {
|
||||||
|
|
||||||
|
private final Timestamp timestamp;
|
||||||
|
private final Iterable<Crypto.Signature> signatures;
|
||||||
|
|
||||||
|
public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MultiServerBatchIdentifier payload, Timestamp timestamp, Iterable<Crypto.Signature> signatures,
|
||||||
|
int maxRetry, FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.signatures = signatures;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerGenericPostWorker<T> extends MultiServerWorker<T, Boolean> {
|
||||||
|
|
||||||
|
public MultiServerGenericPostWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, T payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doPost(SingleServerBulletinBoardClient client, T payload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers one by one and tries to post the payload to each in turn
|
||||||
|
* The method will only iterate once through the server list
|
||||||
|
* Successful post to a server results in removing the server from the list
|
||||||
|
*/
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
// Iterate through servers
|
||||||
|
for (SingleServerBulletinBoardClient client : clients) {
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
doPost(client, payload);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
if (result){
|
||||||
|
if (minServers.decrementAndGet() <= 0){
|
||||||
|
succeed(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
if (maxFailedServers.decrementAndGet() < 0){
|
||||||
|
fail(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{
|
||||||
|
|
||||||
|
private Iterator<SingleServerBulletinBoardClient> clientIterator;
|
||||||
|
|
||||||
|
private String errorString;
|
||||||
|
|
||||||
|
public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, IN payload, int maxRetry,
|
||||||
|
FutureCallback<OUT> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
|
||||||
|
|
||||||
|
clientIterator = clients.iterator();
|
||||||
|
errorString = "";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void doRead(IN payload, SingleServerBulletinBoardClient client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers in a random order until one answers it
|
||||||
|
* Successful retrieval from any server terminates the method and returns the received values; The list is not changed
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
|
||||||
|
// Iterate through servers
|
||||||
|
|
||||||
|
if (clientIterator.hasNext()) {
|
||||||
|
|
||||||
|
// Get next server
|
||||||
|
SingleServerBulletinBoardClient client = clientIterator.next();
|
||||||
|
|
||||||
|
// Retrieve answer from server
|
||||||
|
doRead(payload, client);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
fail(new CommunicationException("Could not contact any server. Errors follow:\n" + errorString));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(OUT msg) {
|
||||||
|
succeed(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
//TODO: log
|
||||||
|
errorString += t.getCause() + " " + t.getMessage() + "\n";
|
||||||
|
run(); // Retry with next server
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerGetRedundancyWorker extends MultiServerWorker<MessageID, Float> {
|
||||||
|
|
||||||
|
private AtomicInteger serversContainingMessage;
|
||||||
|
private AtomicInteger totalContactedServers;
|
||||||
|
|
||||||
|
public MultiServerGetRedundancyWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MessageID payload, int maxRetry,
|
||||||
|
FutureCallback<Float> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
|
||||||
|
|
||||||
|
serversContainingMessage = new AtomicInteger(0);
|
||||||
|
totalContactedServers = new AtomicInteger(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the servers via HTTP Post
|
||||||
|
* It accesses the servers in a random order until one answers it
|
||||||
|
* Successful retrieval from any server terminates the method and returns the received values; The list is not changed
|
||||||
|
*/
|
||||||
|
public void run(){
|
||||||
|
|
||||||
|
// Iterate through clients
|
||||||
|
for (SingleServerBulletinBoardClient client : clients) {
|
||||||
|
|
||||||
|
// Send request to client
|
||||||
|
client.getRedundancy(payload,this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Float result) {
|
||||||
|
|
||||||
|
if (result > 0.5) {
|
||||||
|
serversContainingMessage.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalContactedServers.incrementAndGet() >= getClientNumber()){
|
||||||
|
succeed(((float) serversContainingMessage.get()) / ((float) getClientNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
onSuccess(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
import meerkat.bulletinboard.MultiServerWorker;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.bulletinboard.BatchDataContainer;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerPostBatchDataWorker extends MultiServerWorker<BatchDataContainer, Boolean> {
|
||||||
|
|
||||||
|
public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BatchDataContainer payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
private final int chunkSize;
|
||||||
|
|
||||||
|
public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BulletinBoardMessage payload, int chunkSize, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
this.chunkSize = chunkSize;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) {
|
||||||
|
|
||||||
|
client.postAsBatch(payload, chunkSize, this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
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 MultiServerPostMessageWorker extends MultiServerGenericPostWorker<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public MultiServerPostMessageWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BulletinBoardMessage payload, int maxRetry,
|
||||||
|
FutureCallback<Boolean> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(SingleServerBulletinBoardClient client, BulletinBoardMessage payload) {
|
||||||
|
client.postMessage(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerReadBatchDataWorker extends MultiServerGenericReadWorker<BulletinBoardMessage, BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public MultiServerReadBatchDataWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, BulletinBoardMessage payload, int maxRetry,
|
||||||
|
FutureCallback<BulletinBoardMessage> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRead(BulletinBoardMessage payload, SingleServerBulletinBoardClient client) {
|
||||||
|
client.readBatchData(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.MessageID;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerReadMessageWorker extends MultiServerGenericReadWorker<MessageID, BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public MultiServerReadMessageWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MessageID payload, int maxRetry,
|
||||||
|
FutureCallback<BulletinBoardMessage> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRead(MessageID payload, SingleServerBulletinBoardClient client) {
|
||||||
|
client.readMessage(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package meerkat.bulletinboard.workers.multiserver;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import meerkat.bulletinboard.SingleServerBulletinBoardClient;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class MultiServerReadMessagesWorker extends MultiServerGenericReadWorker<MessageFilterList,List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
public MultiServerReadMessagesWorker(List<SingleServerBulletinBoardClient> clients,
|
||||||
|
int minServers, MessageFilterList payload, int maxRetry,
|
||||||
|
FutureCallback<List<BulletinBoardMessage>> futureCallback) {
|
||||||
|
|
||||||
|
super(clients, minServers, payload, maxRetry, futureCallback);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doRead(MessageFilterList payload, SingleServerBulletinBoardClient client) {
|
||||||
|
client.readMessages(payload, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BeginBatchMessage;
|
||||||
|
import meerkat.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.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerBeginBatchWorker extends SingleServerWorker<BeginBatchMessage,Int64Value> {
|
||||||
|
|
||||||
|
public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Int64Value call() throws Exception {
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(BEGIN_BATCH_PATH);
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(
|
||||||
|
Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
Int64Value result = response.readEntity(Int64Value.class);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Post to this server failed
|
||||||
|
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.CloseBatchMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.CLOSE_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a stop batch operation
|
||||||
|
*/
|
||||||
|
public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker<CloseBatchMessage> {
|
||||||
|
|
||||||
|
public SingleServerCloseBatchWorker(String serverAddress, CloseBatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, CLOSE_BATCH_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import com.google.protobuf.Int64Value;
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.SyncQuery;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.GenerateSyncQueryParams;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.ProcessingException;
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.GENERATE_SYNC_QUERY_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a Sync Query Generation operation
|
||||||
|
*/
|
||||||
|
public class SingleServerGenerateSyncQueryWorker extends SingleServerWorker<GenerateSyncQueryParams,SyncQuery> {
|
||||||
|
|
||||||
|
public SingleServerGenerateSyncQueryWorker(String serverAddress, GenerateSyncQueryParams payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncQuery call() throws Exception {
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH);
|
||||||
|
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
SyncQuery result = response.readEntity(SyncQuery.class);
|
||||||
|
return result;
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Post to this server failed
|
||||||
|
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import com.google.protobuf.BoolValue;
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.Comm.*;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerGenericPostWorker<T> extends SingleServerWorker<T, Boolean> {
|
||||||
|
|
||||||
|
private final String subPath;
|
||||||
|
|
||||||
|
public SingleServerGenericPostWorker(String serverAddress, String subPath, T payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
this.subPath = subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* It accesses the server and tries to post the payload to it
|
||||||
|
* Successful post to a server results
|
||||||
|
* @return TRUE if the operation is successful
|
||||||
|
* @throws CommunicationException if the operation is unsuccessful
|
||||||
|
*/
|
||||||
|
public Boolean call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(subPath);
|
||||||
|
Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(
|
||||||
|
Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// If a BoolValue entity is returned: the post was successful
|
||||||
|
response.readEntity(BoolValue.class);
|
||||||
|
return Boolean.TRUE;
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Post to this server failed
|
||||||
|
throw new CommunicationException("Could not contact the server");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerGetRedundancyWorker extends SingleServerWorker<MessageID, Float> {
|
||||||
|
|
||||||
|
public SingleServerGetRedundancyWorker(String serverAddress, MessageID payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* It queries the server for a message with the given ID
|
||||||
|
* @return TRUE if the message exists in the server and FALSE otherwise
|
||||||
|
* @throws CommunicationException if the server does not return a valid answer
|
||||||
|
*/
|
||||||
|
public Float call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
MessageFilterList msgFilterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(payload.getID())
|
||||||
|
.build()
|
||||||
|
).build();
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msgFilterList, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream = null;
|
||||||
|
|
||||||
|
// Retrieve answer
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
if (inputStream.asList().size() > 0){
|
||||||
|
// Message exists in the server
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Message does not exist in the server
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Server access failed");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BatchMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post batch operation
|
||||||
|
*/
|
||||||
|
public class SingleServerPostBatchWorker extends SingleServerGenericPostWorker<BatchMessage> {
|
||||||
|
|
||||||
|
public SingleServerPostBatchWorker(String serverAddress, BatchMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, POST_BATCH_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerPostMessageWorker extends SingleServerGenericPostWorker<BulletinBoardMessage> {
|
||||||
|
|
||||||
|
public SingleServerPostMessageWorker(String serverAddress, BulletinBoardMessage payload, int maxRetry) {
|
||||||
|
super(serverAddress, POST_MESSAGE_PATH, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.SyncQuery;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.SyncQueryResponse;
|
||||||
|
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.SYNC_QUERY_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
* Tries to contact server once and perform a post operation
|
||||||
|
*/
|
||||||
|
public class SingleServerQuerySyncWorker extends SingleServerWorker<SyncQuery, SyncQueryResponse> {
|
||||||
|
|
||||||
|
public SingleServerQuerySyncWorker(String serverAddress, SyncQuery payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SyncQueryResponse call() throws Exception {
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
Response response;
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(SYNC_QUERY_PATH);
|
||||||
|
response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF));
|
||||||
|
|
||||||
|
// Retrieve answer
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
// If a BulletinBoardMessageList is returned: the read was successful
|
||||||
|
return response.readEntity(SyncQueryResponse.class);
|
||||||
|
|
||||||
|
} catch (ProcessingException | IllegalStateException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Server access failed");
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.rest.Constants;
|
||||||
|
|
||||||
|
import javax.ws.rs.client.Client;
|
||||||
|
import javax.ws.rs.client.Entity;
|
||||||
|
import javax.ws.rs.client.WebTarget;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, List<BatchChunk>> {
|
||||||
|
|
||||||
|
public SingleServerReadBatchWorker(String serverAddress, BatchQuery payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* Upon successful retrieval from the server the method returns the received values
|
||||||
|
* @return the complete batch as read from the server
|
||||||
|
* @throws CommunicationException if the server's response is invalid
|
||||||
|
*/
|
||||||
|
public List<BatchChunk> call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
|
||||||
|
// Get the batch data
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
MessageInputStream<BatchChunk> inputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
} catch (IOException | InvocationTargetException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Could not contact the server or server returned illegal result");
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
|
||||||
|
throw new CommunicationException("MessageInputStream error");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
package meerkat.bulletinboard.workers.singleserver;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.SingleServerWorker;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.comm.MessageInputStream;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessageList;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.MessageFilterList;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage;
|
||||||
|
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 java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH;
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 27-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerReadMessagesWorker extends SingleServerWorker<MessageFilterList, List<BulletinBoardMessage>> {
|
||||||
|
|
||||||
|
public SingleServerReadMessagesWorker(String serverAddress, MessageFilterList payload, int maxRetry) {
|
||||||
|
super(serverAddress, payload, maxRetry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method carries out the actual communication with the server via HTTP Post
|
||||||
|
* Upon successful retrieval from the server the method returns the received values
|
||||||
|
* @return The list of messages returned by the server
|
||||||
|
* @throws CommunicationException if the server's response is invalid
|
||||||
|
*/
|
||||||
|
public List<BulletinBoardMessage> call() throws CommunicationException{
|
||||||
|
|
||||||
|
Client client = clientLocal.get();
|
||||||
|
|
||||||
|
WebTarget webTarget;
|
||||||
|
|
||||||
|
// Send request to Server
|
||||||
|
webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH);
|
||||||
|
InputStream in = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF), InputStream.class);
|
||||||
|
|
||||||
|
MessageInputStream<BulletinBoardMessage> inputStream = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class);
|
||||||
|
|
||||||
|
return inputStream.asList();
|
||||||
|
|
||||||
|
} catch (IOException | InvocationTargetException e) {
|
||||||
|
|
||||||
|
// Read failed
|
||||||
|
throw new CommunicationException("Could not contact the server or server returned illegal result");
|
||||||
|
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
|
||||||
|
throw new CommunicationException("MessageInputStream error");
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
inputStream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,214 +0,0 @@
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import meerkat.bulletinboard.BulletinBoardClient;
|
|
||||||
import meerkat.bulletinboard.BulletinBoardClient.ClientCallback;
|
|
||||||
import meerkat.bulletinboard.ThreadedBulletinBoardClient;
|
|
||||||
import meerkat.protobuf.BulletinBoardAPI.*;
|
|
||||||
import meerkat.protobuf.Crypto;
|
|
||||||
|
|
||||||
import meerkat.protobuf.Voting.*;
|
|
||||||
import meerkat.util.BulletinBoardMessageComparator;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.hamcrest.CoreMatchers.*;
|
|
||||||
import static org.hamcrest.number.OrderingComparison.*;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.Semaphore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
|
||||||
*/
|
|
||||||
public class BulletinBoardClientIntegrationTest {
|
|
||||||
|
|
||||||
Semaphore jobSemaphore;
|
|
||||||
Vector<Throwable> thrown;
|
|
||||||
|
|
||||||
private class PostCallback implements ClientCallback<Object>{
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleCallback(Object msg) {
|
|
||||||
System.err.println("Post operation completed");
|
|
||||||
jobSemaphore.release();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleFailure(Throwable t) {
|
|
||||||
thrown.add(t);
|
|
||||||
jobSemaphore.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class RedundancyCallback implements ClientCallback<Float>{
|
|
||||||
|
|
||||||
private float minRedundancy;
|
|
||||||
|
|
||||||
public RedundancyCallback(float minRedundancy) {
|
|
||||||
this.minRedundancy = minRedundancy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleCallback(Float redundancy) {
|
|
||||||
System.err.println("Redundancy found is: " + redundancy);
|
|
||||||
jobSemaphore.release();
|
|
||||||
assertThat(redundancy, greaterThanOrEqualTo(minRedundancy));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleFailure(Throwable t) {
|
|
||||||
thrown.add(t);
|
|
||||||
jobSemaphore.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ReadCallback implements ClientCallback<List<BulletinBoardMessage>>{
|
|
||||||
|
|
||||||
private List<BulletinBoardMessage> expectedMsgList;
|
|
||||||
|
|
||||||
public ReadCallback(List<BulletinBoardMessage> expectedMsgList) {
|
|
||||||
this.expectedMsgList = expectedMsgList;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleCallback(List<BulletinBoardMessage> messages) {
|
|
||||||
|
|
||||||
System.err.println(messages);
|
|
||||||
jobSemaphore.release();
|
|
||||||
|
|
||||||
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
|
|
||||||
|
|
||||||
assertThat(messages.size(), is(expectedMsgList.size()));
|
|
||||||
|
|
||||||
Iterator<BulletinBoardMessage> expectedMessageIterator = expectedMsgList.iterator();
|
|
||||||
Iterator<BulletinBoardMessage> receivedMessageIterator = messages.iterator();
|
|
||||||
|
|
||||||
while (expectedMessageIterator.hasNext()) {
|
|
||||||
assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleFailure(Throwable t) {
|
|
||||||
thrown.add(t);
|
|
||||||
jobSemaphore.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BulletinBoardClient bulletinBoardClient;
|
|
||||||
|
|
||||||
private PostCallback postCallback;
|
|
||||||
private RedundancyCallback redundancyCallback;
|
|
||||||
private ReadCallback readCallback;
|
|
||||||
|
|
||||||
private static String PROP_GETTY_URL = "gretty.httpBaseURI";
|
|
||||||
private static String DEFAULT_BASE_URL = "http://localhost:8081";
|
|
||||||
private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void init(){
|
|
||||||
|
|
||||||
bulletinBoardClient = new ThreadedBulletinBoardClient();
|
|
||||||
|
|
||||||
List<String> testDB = new LinkedList<String>();
|
|
||||||
testDB.add(BASE_URL);
|
|
||||||
|
|
||||||
bulletinBoardClient.init(BulletinBoardClientParams.newBuilder()
|
|
||||||
.addBulletinBoardAddress("http://localhost:8081")
|
|
||||||
.setMinRedundancy((float) 1.0)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
postCallback = new PostCallback();
|
|
||||||
redundancyCallback = new RedundancyCallback((float) 1.0);
|
|
||||||
|
|
||||||
thrown = new Vector<>();
|
|
||||||
jobSemaphore = new Semaphore(0);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void postTest() {
|
|
||||||
|
|
||||||
byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
|
|
||||||
byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
|
|
||||||
byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
|
|
||||||
byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0};
|
|
||||||
|
|
||||||
BulletinBoardMessage msg;
|
|
||||||
|
|
||||||
MessageFilterList filterList;
|
|
||||||
List<BulletinBoardMessage> msgList;
|
|
||||||
|
|
||||||
MessageID messageID;
|
|
||||||
|
|
||||||
Comparator<BulletinBoardMessage> msgComparator = new BulletinBoardMessageComparator();
|
|
||||||
|
|
||||||
msg = BulletinBoardMessage.newBuilder()
|
|
||||||
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
|
||||||
.addTag("Signature")
|
|
||||||
.addTag("Trustee")
|
|
||||||
.setData(ByteString.copyFrom(b1))
|
|
||||||
.build())
|
|
||||||
.addSig(Crypto.Signature.newBuilder()
|
|
||||||
.setType(Crypto.SignatureType.DSA)
|
|
||||||
.setData(ByteString.copyFrom(b2))
|
|
||||||
.setSignerId(ByteString.copyFrom(b3))
|
|
||||||
.build())
|
|
||||||
.addSig(Crypto.Signature.newBuilder()
|
|
||||||
.setType(Crypto.SignatureType.ECDSA)
|
|
||||||
.setData(ByteString.copyFrom(b3))
|
|
||||||
.setSignerId(ByteString.copyFrom(b2))
|
|
||||||
.build())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
messageID = bulletinBoardClient.postMessage(msg,postCallback);
|
|
||||||
|
|
||||||
try {
|
|
||||||
jobSemaphore.acquire();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
System.err.println(e.getCause() + " " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
bulletinBoardClient.getRedundancy(messageID,redundancyCallback);
|
|
||||||
|
|
||||||
filterList = MessageFilterList.newBuilder()
|
|
||||||
.addFilter(
|
|
||||||
MessageFilter.newBuilder()
|
|
||||||
.setType(FilterType.TAG)
|
|
||||||
.setTag("Signature")
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.addFilter(
|
|
||||||
MessageFilter.newBuilder()
|
|
||||||
.setType(FilterType.TAG)
|
|
||||||
.setTag("Trustee")
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
msgList = new LinkedList<BulletinBoardMessage>();
|
|
||||||
msgList.add(msg);
|
|
||||||
|
|
||||||
readCallback = new ReadCallback(msgList);
|
|
||||||
|
|
||||||
bulletinBoardClient.readMessages(filterList, readCallback);
|
|
||||||
try {
|
|
||||||
jobSemaphore.acquire(2);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
System.err.println(e.getCause() + " " + e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
bulletinBoardClient.close();
|
|
||||||
|
|
||||||
for (Throwable t : thrown) {
|
|
||||||
System.err.println(t.getMessage());
|
|
||||||
}
|
|
||||||
if (thrown.size() > 0) {
|
|
||||||
assert false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,318 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.*;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
|
||||||
|
import static meerkat.bulletinboard.BulletinBoardSynchronizer.SyncStatus;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
|
||||||
|
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
|
||||||
|
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
import meerkat.util.BulletinBoardMessageGenerator;
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel on 6/1/2016.
|
||||||
|
*/
|
||||||
|
public class BulletinBoardSynchronizerTest {
|
||||||
|
|
||||||
|
private static final String REMOTE_SERVER_ADDRESS = "remoteDB";
|
||||||
|
private static final String LOCAL_SERVER_ADDRESS = "localDB";
|
||||||
|
private static int testCount;
|
||||||
|
|
||||||
|
private static final int THREAD_NUM = 3;
|
||||||
|
private static final int SUBSCRIPTION_INTERVAL = 1000;
|
||||||
|
|
||||||
|
private static final int SYNC_SLEEP_INTERVAL = 500;
|
||||||
|
private static final int SYNC_WAIT_CAP = 1000;
|
||||||
|
|
||||||
|
private DeletableSubscriptionBulletinBoardClient localClient;
|
||||||
|
private AsyncBulletinBoardClient remoteClient;
|
||||||
|
|
||||||
|
private BulletinBoardSynchronizer synchronizer;
|
||||||
|
|
||||||
|
private static BulletinBoardMessageGenerator messageGenerator;
|
||||||
|
private static BulletinBoardMessageComparator messageComparator;
|
||||||
|
|
||||||
|
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
|
||||||
|
private static String KEYFILE_PASSWORD1 = "secret";
|
||||||
|
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
|
||||||
|
|
||||||
|
private static BulletinBoardSignature[] signers;
|
||||||
|
private static ByteString[] signerIDs;
|
||||||
|
|
||||||
|
private Semaphore semaphore;
|
||||||
|
private List<Throwable> thrown;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void build() {
|
||||||
|
|
||||||
|
messageGenerator = new BulletinBoardMessageGenerator(new Random(0));
|
||||||
|
messageComparator = new BulletinBoardMessageComparator();
|
||||||
|
|
||||||
|
signers = new BulletinBoardSignature[1];
|
||||||
|
signerIDs = new ByteString[1];
|
||||||
|
|
||||||
|
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
|
||||||
|
signerIDs[0] = signers[0].getSignerID();
|
||||||
|
|
||||||
|
InputStream keyStream = BulletinBoardSynchronizerTest.class.getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
|
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
KeyStore.Builder keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
|
||||||
|
signers[0].loadSigningCertificate(keyStoreBuilder);
|
||||||
|
|
||||||
|
signers[0].loadVerificationCertificates(BulletinBoardSynchronizerTest.class.getResourceAsStream(CERT1_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed reading from signature file " + e.getMessage());
|
||||||
|
fail("Failed reading from signature file " + e.getMessage());
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
System.err.println("Failed reading certificate " + e.getMessage());
|
||||||
|
fail("Failed reading certificate " + e.getMessage());
|
||||||
|
} catch (KeyStoreException e) {
|
||||||
|
System.err.println("Failed reading keystore " + e.getMessage());
|
||||||
|
fail("Failed reading keystore " + e.getMessage());
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
System.err.println("Couldn't find signing algorithm " + e.getMessage());
|
||||||
|
fail("Couldn't find signing algorithm " + e.getMessage());
|
||||||
|
} catch (UnrecoverableKeyException e) {
|
||||||
|
System.err.println("Couldn't find signing key " + e.getMessage());
|
||||||
|
fail("Couldn't find signing key " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
signerIDs[0] = signers[0].getSignerID();
|
||||||
|
|
||||||
|
testCount = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() throws CommunicationException {
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_SERVER_ADDRESS + testCount));
|
||||||
|
remoteServer.init();
|
||||||
|
|
||||||
|
remoteClient = new LocalBulletinBoardClient(
|
||||||
|
remoteServer,
|
||||||
|
THREAD_NUM,
|
||||||
|
SUBSCRIPTION_INTERVAL);
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_SERVER_ADDRESS + testCount));
|
||||||
|
localServer.init();
|
||||||
|
|
||||||
|
localClient = new LocalBulletinBoardClient(
|
||||||
|
localServer,
|
||||||
|
THREAD_NUM,
|
||||||
|
SUBSCRIPTION_INTERVAL);
|
||||||
|
|
||||||
|
synchronizer = new SimpleBulletinBoardSynchronizer(SYNC_SLEEP_INTERVAL, SYNC_WAIT_CAP);
|
||||||
|
synchronizer.init(localClient, remoteClient);
|
||||||
|
|
||||||
|
semaphore = new Semaphore(0);
|
||||||
|
thrown = new LinkedList<>();
|
||||||
|
|
||||||
|
testCount++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncStatusCallback implements FutureCallback<SyncStatus> {
|
||||||
|
|
||||||
|
private final SyncStatus statusToWaitFor;
|
||||||
|
private AtomicBoolean stillWaiting;
|
||||||
|
|
||||||
|
public SyncStatusCallback(SyncStatus statusToWaitFor) {
|
||||||
|
this.statusToWaitFor = statusToWaitFor;
|
||||||
|
stillWaiting = new AtomicBoolean(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(SyncStatus result) {
|
||||||
|
|
||||||
|
if (result == statusToWaitFor && stillWaiting.compareAndSet(true, false)){
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
thrown.add(t);
|
||||||
|
if (stillWaiting.compareAndSet(true,false)) {
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MessageCountCallback implements FutureCallback<Integer> {
|
||||||
|
|
||||||
|
private int[] expectedCounts;
|
||||||
|
private int currentIteration;
|
||||||
|
|
||||||
|
public MessageCountCallback(int[] expectedCounts) {
|
||||||
|
this.expectedCounts = expectedCounts;
|
||||||
|
this.currentIteration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Integer result) {
|
||||||
|
|
||||||
|
if (currentIteration < expectedCounts.length){
|
||||||
|
if (result != expectedCounts[currentIteration]){
|
||||||
|
onFailure(new AssertionError("Wrong message count. Expected " + expectedCounts[currentIteration] + " but received " + result));
|
||||||
|
currentIteration = expectedCounts.length;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIteration++;
|
||||||
|
|
||||||
|
if (currentIteration == expectedCounts.length)
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
thrown.add(t);
|
||||||
|
semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSync() throws SignatureException, CommunicationException, InterruptedException {
|
||||||
|
|
||||||
|
Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(15252162)
|
||||||
|
.setNanos(85914)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
|
||||||
|
|
||||||
|
MessageID msgID = localClient.postMessage(msg);
|
||||||
|
|
||||||
|
timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(51511653)
|
||||||
|
.setNanos(3625)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BulletinBoardMessage batchMessage = messageGenerator.generateRandomMessage(signers,timestamp, 100, 10);
|
||||||
|
|
||||||
|
MessageID batchMsgID = localClient.postAsBatch(batchMessage, 10);
|
||||||
|
|
||||||
|
BulletinBoardMessage test = localClient.readMessage(batchMsgID);
|
||||||
|
|
||||||
|
BulletinBoardMessage stub = localClient.readMessages(MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(batchMsgID.getID())
|
||||||
|
.build())
|
||||||
|
.build()).get(0);
|
||||||
|
|
||||||
|
BulletinBoardMessage test2 = localClient.readBatchData(stub);
|
||||||
|
|
||||||
|
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SYNCHRONIZED));
|
||||||
|
|
||||||
|
int[] expectedCounts = {2,0};
|
||||||
|
synchronizer.subscribeToRemainingMessagesCount(new MessageCountCallback(expectedCounts));
|
||||||
|
|
||||||
|
Thread syncThread = new Thread(synchronizer);
|
||||||
|
syncThread.start();
|
||||||
|
|
||||||
|
if (!semaphore.tryAcquire(2, 4000, TimeUnit.MILLISECONDS)) {
|
||||||
|
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronizer.stop();
|
||||||
|
syncThread.join();
|
||||||
|
|
||||||
|
if (thrown.size() > 0) {
|
||||||
|
for (Throwable t : thrown)
|
||||||
|
System.err.println(t.getMessage());
|
||||||
|
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BulletinBoardMessage> msgList = remoteClient.readMessages(MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(msgID.getID())
|
||||||
|
.build())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
assertThat("Wrong number of messages returned.", msgList.size() == 1);
|
||||||
|
assertThat("Returned message is not equal to original one", messageComparator.compare(msgList.get(0),msg) == 0);
|
||||||
|
|
||||||
|
BulletinBoardMessage returnedBatchMsg = remoteClient.readMessage(batchMsgID);
|
||||||
|
|
||||||
|
assertThat("Returned batch does not equal original one.", messageComparator.compare(returnedBatchMsg, batchMessage) == 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerError() throws SignatureException, CommunicationException, InterruptedException {
|
||||||
|
|
||||||
|
Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(945736256)
|
||||||
|
.setNanos(276788)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10);
|
||||||
|
|
||||||
|
remoteClient.close();
|
||||||
|
|
||||||
|
synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SERVER_ERROR));
|
||||||
|
|
||||||
|
localClient.postMessage(msg);
|
||||||
|
|
||||||
|
Thread thread = new Thread(synchronizer);
|
||||||
|
|
||||||
|
thread.start();
|
||||||
|
|
||||||
|
if (!semaphore.tryAcquire(4000, TimeUnit.MILLISECONDS)) {
|
||||||
|
thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync."));
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronizer.stop();
|
||||||
|
thread.join();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
if (thrown.size() > 0) {
|
||||||
|
for (Throwable t : thrown) {
|
||||||
|
System.err.println(t.getMessage());
|
||||||
|
}
|
||||||
|
assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronizer.stop();
|
||||||
|
localClient.close();
|
||||||
|
remoteClient.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer;
|
||||||
|
import meerkat.bulletinboard.sqlserver.H2QueryProvider;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel on 6/27/2016.
|
||||||
|
*/
|
||||||
|
public class CachedBulletinBoardClientTest {
|
||||||
|
|
||||||
|
private static final int THREAD_NUM = 3;
|
||||||
|
|
||||||
|
private static final String LOCAL_DB_NAME = "localDB";
|
||||||
|
private static final String REMOTE_DB_NAME = "remoteDB";
|
||||||
|
private static final String QUEUE_DB_NAME = "queueDB";
|
||||||
|
|
||||||
|
private static final int SUBSRCIPTION_DELAY = 500;
|
||||||
|
private static final int SYNC_DELAY = 500;
|
||||||
|
|
||||||
|
// Testers
|
||||||
|
private CachedBulletinBoardClient cachedClient;
|
||||||
|
private GenericBulletinBoardClientTester clientTest;
|
||||||
|
private GenericSubscriptionClientTester subscriptionTester;
|
||||||
|
|
||||||
|
public CachedBulletinBoardClientTest() throws CommunicationException {
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_DB_NAME));
|
||||||
|
localServer.init();
|
||||||
|
LocalBulletinBoardClient localClient = new LocalBulletinBoardClient(localServer, THREAD_NUM, SUBSRCIPTION_DELAY);
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_DB_NAME));
|
||||||
|
remoteServer.init();
|
||||||
|
LocalBulletinBoardClient remoteClient = new LocalBulletinBoardClient(remoteServer, THREAD_NUM, SUBSRCIPTION_DELAY);
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer queueServer = new BulletinBoardSQLServer(new H2QueryProvider(QUEUE_DB_NAME));
|
||||||
|
queueServer.init();
|
||||||
|
LocalBulletinBoardClient queueClient = new LocalBulletinBoardClient(queueServer, THREAD_NUM, SUBSRCIPTION_DELAY);
|
||||||
|
|
||||||
|
List<SubscriptionBulletinBoardClient> clientList = new LinkedList<>();
|
||||||
|
clientList.add(remoteClient);
|
||||||
|
|
||||||
|
BulletinBoardSubscriber subscriber = new ThreadedBulletinBoardSubscriber(clientList, localClient);
|
||||||
|
|
||||||
|
cachedClient = new CachedBulletinBoardClient(localClient, remoteClient, subscriber, queueClient, SYNC_DELAY, SYNC_DELAY);
|
||||||
|
subscriptionTester = new GenericSubscriptionClientTester(cachedClient);
|
||||||
|
clientTest = new GenericBulletinBoardClientTester(cachedClient, 87351);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of initializing the client and the test resources
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void init(){
|
||||||
|
|
||||||
|
clientTest.init();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
|
||||||
|
*/
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
cachedClient.close();
|
||||||
|
clientTest.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPost() {
|
||||||
|
|
||||||
|
clientTest.testPost();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
clientTest.testBatchPost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
clientTest.testCompleteBatchPost();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubscription() throws SignatureException, CommunicationException {
|
||||||
|
|
||||||
|
// subscriptionTester.init();
|
||||||
|
// subscriptionTester.subscriptionTest();
|
||||||
|
// subscriptionTester.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,497 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
|
import meerkat.crypto.concrete.SHA256Digest;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.protobuf.Crypto;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
import meerkat.util.BulletinBoardMessageGenerator;
|
||||||
|
import meerkat.util.BulletinBoardUtils;
|
||||||
|
import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
import static org.hamcrest.number.OrderingComparison.greaterThanOrEqualTo;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
|
*/
|
||||||
|
public class GenericBulletinBoardClientTester {
|
||||||
|
|
||||||
|
// Signature resources
|
||||||
|
|
||||||
|
private BulletinBoardSignature signers[];
|
||||||
|
private ByteString[] signerIDs;
|
||||||
|
|
||||||
|
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
|
||||||
|
private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12";
|
||||||
|
|
||||||
|
private static String KEYFILE_PASSWORD1 = "secret";
|
||||||
|
private static String KEYFILE_PASSWORD3 = "shh";
|
||||||
|
|
||||||
|
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
|
||||||
|
private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt";
|
||||||
|
|
||||||
|
// Client and callbacks
|
||||||
|
|
||||||
|
private AsyncBulletinBoardClient bulletinBoardClient;
|
||||||
|
|
||||||
|
private PostCallback postCallback;
|
||||||
|
|
||||||
|
private RedundancyCallback redundancyCallback;
|
||||||
|
private ReadCallback readCallback;
|
||||||
|
|
||||||
|
// Sync and misc
|
||||||
|
|
||||||
|
private Semaphore jobSemaphore;
|
||||||
|
private Vector<Throwable> thrown;
|
||||||
|
private Random random;
|
||||||
|
private BulletinBoardMessageGenerator generator;
|
||||||
|
|
||||||
|
private BulletinBoardDigest digest;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
|
||||||
|
public GenericBulletinBoardClientTester(AsyncBulletinBoardClient bulletinBoardClient, int seed){
|
||||||
|
|
||||||
|
this.bulletinBoardClient = bulletinBoardClient;
|
||||||
|
|
||||||
|
signers = new GenericBulletinBoardSignature[2];
|
||||||
|
signerIDs = new ByteString[signers.length];
|
||||||
|
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
|
||||||
|
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
|
||||||
|
|
||||||
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
|
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
||||||
|
|
||||||
|
KeyStore.Builder keyStoreBuilder;
|
||||||
|
try {
|
||||||
|
keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
|
||||||
|
signers[0].loadSigningCertificate(keyStoreBuilder);
|
||||||
|
|
||||||
|
signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3);
|
||||||
|
password = KEYFILE_PASSWORD3.toCharArray();
|
||||||
|
|
||||||
|
keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
signers[1].loadSigningCertificate(keyStoreBuilder);
|
||||||
|
|
||||||
|
signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
for (int i = 0 ; i < signers.length ; i++) {
|
||||||
|
signerIDs[i] = signers[i].getSignerID();
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.random = new Random(seed);
|
||||||
|
this.generator = new BulletinBoardMessageGenerator(random);
|
||||||
|
this.digest = new GenericBulletinBoardDigest(new SHA256Digest());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback definitions
|
||||||
|
|
||||||
|
protected void genericHandleFailure(Throwable t){
|
||||||
|
System.err.println(t.getCause() + " " + t.getMessage());
|
||||||
|
thrown.add(t);
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostCallback implements FutureCallback<Boolean>{
|
||||||
|
|
||||||
|
private boolean isAssert;
|
||||||
|
private boolean assertValue;
|
||||||
|
|
||||||
|
public PostCallback() {
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostCallback(boolean isAssert) {
|
||||||
|
this(isAssert,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PostCallback(boolean isAssert, boolean assertValue) {
|
||||||
|
this.isAssert = isAssert;
|
||||||
|
this.assertValue = assertValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean msg) {
|
||||||
|
|
||||||
|
System.err.println("Post operation completed");
|
||||||
|
|
||||||
|
if (isAssert) {
|
||||||
|
if (assertValue && !msg) {
|
||||||
|
genericHandleFailure(new AssertionError("Post operation failed"));
|
||||||
|
} else if (!assertValue && msg){
|
||||||
|
genericHandleFailure(new AssertionError("Post operation succeeded unexpectedly"));
|
||||||
|
} else {
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RedundancyCallback implements FutureCallback<Float>{
|
||||||
|
|
||||||
|
private float minRedundancy;
|
||||||
|
|
||||||
|
public RedundancyCallback(float minRedundancy) {
|
||||||
|
this.minRedundancy = minRedundancy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Float redundancy) {
|
||||||
|
System.err.println("Redundancy found is: " + redundancy);
|
||||||
|
jobSemaphore.release();
|
||||||
|
assertThat(redundancy, greaterThanOrEqualTo(minRedundancy));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadCallback implements FutureCallback<List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
private List<BulletinBoardMessage> expectedMsgList;
|
||||||
|
|
||||||
|
public ReadCallback(List<BulletinBoardMessage> expectedMsgList) {
|
||||||
|
this.expectedMsgList = expectedMsgList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> messages) {
|
||||||
|
|
||||||
|
System.err.println(messages);
|
||||||
|
jobSemaphore.release();
|
||||||
|
|
||||||
|
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
|
||||||
|
|
||||||
|
assertThat(messages.size(), is(expectedMsgList.size()));
|
||||||
|
|
||||||
|
Iterator<BulletinBoardMessage> expectedMessageIterator = expectedMsgList.iterator();
|
||||||
|
Iterator<BulletinBoardMessage> receivedMessageIterator = messages.iterator();
|
||||||
|
|
||||||
|
while (expectedMessageIterator.hasNext()) {
|
||||||
|
assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReadBatchCallback implements FutureCallback<BulletinBoardMessage>{
|
||||||
|
|
||||||
|
private BulletinBoardMessage expectedMsg;
|
||||||
|
|
||||||
|
public ReadBatchCallback(BulletinBoardMessage expectedMsg) {
|
||||||
|
this.expectedMsg = expectedMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(BulletinBoardMessage msg) {
|
||||||
|
|
||||||
|
BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();
|
||||||
|
|
||||||
|
if (msgComparator.compare(msg, expectedMsg) != 0) {
|
||||||
|
genericHandleFailure(new AssertionError("Batch read returned different message.\nExpected:" + expectedMsg + "\nRecieved:" + msg + "\n"));
|
||||||
|
} else {
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of initializing the client and the test resources
|
||||||
|
*/
|
||||||
|
public void init(){
|
||||||
|
|
||||||
|
random = new Random(0); // We use insecure randomness in tests for repeatability
|
||||||
|
|
||||||
|
postCallback = new PostCallback();
|
||||||
|
redundancyCallback = new RedundancyCallback((float) 1.0);
|
||||||
|
|
||||||
|
thrown = new Vector<>();
|
||||||
|
jobSemaphore = new Semaphore(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
if (thrown.size() > 0) {
|
||||||
|
|
||||||
|
for (Throwable t : thrown){
|
||||||
|
System.err.println(t.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the standard post, redundancy and read methods
|
||||||
|
*/
|
||||||
|
public void testPost() {
|
||||||
|
|
||||||
|
byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
|
||||||
|
byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
|
||||||
|
byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
|
||||||
|
|
||||||
|
BulletinBoardMessage msg;
|
||||||
|
|
||||||
|
MessageFilterList filterList;
|
||||||
|
List<BulletinBoardMessage> msgList;
|
||||||
|
|
||||||
|
MessageID messageID;
|
||||||
|
|
||||||
|
msg = BulletinBoardMessage.newBuilder()
|
||||||
|
.setMsg(UnsignedBulletinBoardMessage.newBuilder()
|
||||||
|
.addTag("Signature")
|
||||||
|
.addTag("Trustee")
|
||||||
|
.setData(ByteString.copyFrom(b1))
|
||||||
|
.setTimestamp(Timestamp.newBuilder()
|
||||||
|
.setSeconds(20)
|
||||||
|
.setNanos(30)
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.addSig(Crypto.Signature.newBuilder()
|
||||||
|
.setType(Crypto.SignatureType.DSA)
|
||||||
|
.setData(ByteString.copyFrom(b2))
|
||||||
|
.setSignerId(ByteString.copyFrom(b3))
|
||||||
|
.build())
|
||||||
|
.addSig(Crypto.Signature.newBuilder()
|
||||||
|
.setType(Crypto.SignatureType.ECDSA)
|
||||||
|
.setData(ByteString.copyFrom(b3))
|
||||||
|
.setSignerId(ByteString.copyFrom(b2))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
messageID = bulletinBoardClient.postMessage(msg,postCallback);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
bulletinBoardClient.getRedundancy(messageID,redundancyCallback);
|
||||||
|
|
||||||
|
filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(
|
||||||
|
MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag("Signature")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.addFilter(
|
||||||
|
MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag("Trustee")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
msgList = new LinkedList<>();
|
||||||
|
msgList.add(msg);
|
||||||
|
|
||||||
|
readCallback = new ReadCallback(msgList);
|
||||||
|
|
||||||
|
bulletinBoardClient.readMessages(filterList, readCallback);
|
||||||
|
try {
|
||||||
|
jobSemaphore.acquire(2);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests posting a batch by parts
|
||||||
|
* @throws CommunicationException, SignatureException, InterruptedException
|
||||||
|
*/
|
||||||
|
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
final int BATCH_LENGTH = 100;
|
||||||
|
final int CHUNK_SIZE = 10;
|
||||||
|
final int TAG_NUM = 10;
|
||||||
|
|
||||||
|
final Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(141515)
|
||||||
|
.setNanos(859018)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
|
||||||
|
|
||||||
|
// Begin batch
|
||||||
|
|
||||||
|
bulletinBoardClient.beginBatch(msg.getMsg().getTagList(), new FutureCallback<BatchIdentifier>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(final BatchIdentifier identifier) {
|
||||||
|
|
||||||
|
bulletinBoardClient.postBatchData(identifier, BulletinBoardUtils.breakToBatch(msg, CHUNK_SIZE), new FutureCallback<Boolean>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
|
||||||
|
bulletinBoardClient.closeBatch(identifier, msg.getMsg().getTimestamp(), msg.getSigList(), new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Boolean result) {
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
digest.reset();
|
||||||
|
digest.update(msg);
|
||||||
|
|
||||||
|
bulletinBoardClient.readMessage(digest.digestAsMessageID(), new ReadBatchCallback(msg));
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Posts a complete batch message
|
||||||
|
* Checks reading of the message in two parts
|
||||||
|
* @throws CommunicationException, SignatureException, InterruptedException
|
||||||
|
*/
|
||||||
|
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
final int BATCH_LENGTH = 100;
|
||||||
|
final int CHUNK_SIZE = 99;
|
||||||
|
final int TAG_NUM = 8;
|
||||||
|
|
||||||
|
final Timestamp timestamp = Timestamp.newBuilder()
|
||||||
|
.setSeconds(7776151)
|
||||||
|
.setNanos(252616)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final BulletinBoardMessage msg = generator.generateRandomMessage(signers, timestamp, BATCH_LENGTH, TAG_NUM);
|
||||||
|
|
||||||
|
// Post batch
|
||||||
|
|
||||||
|
MessageID msgID = bulletinBoardClient.postAsBatch(msg, CHUNK_SIZE, postCallback);
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
// Read batch
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.MSG_ID)
|
||||||
|
.setId(msgID.getID())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
bulletinBoardClient.readMessages(filterList, new FutureCallback<List<BulletinBoardMessage>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> msgList) {
|
||||||
|
|
||||||
|
if (msgList.size() != 1) {
|
||||||
|
|
||||||
|
genericHandleFailure(new AssertionError("Wrong number of stubs returned. Expected: 1; Found: " + msgList.size()));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
BulletinBoardMessage retrievedMsg = msgList.get(0);
|
||||||
|
bulletinBoardClient.readBatchData(retrievedMsg, new ReadBatchCallback(msg));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
genericHandleFailure(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
jobSemaphore.acquire();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.google.protobuf.Timestamp;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.crypto.concrete.ECDSASignature;
|
||||||
|
import meerkat.protobuf.BulletinBoardApi.*;
|
||||||
|
import meerkat.util.BulletinBoardMessageComparator;
|
||||||
|
import meerkat.util.BulletinBoardMessageGenerator;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 22-Mar-16.
|
||||||
|
*/
|
||||||
|
public class GenericSubscriptionClientTester {
|
||||||
|
|
||||||
|
private BulletinBoardSignature signers[];
|
||||||
|
private ByteString[] signerIDs;
|
||||||
|
|
||||||
|
private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12";
|
||||||
|
private static String KEYFILE_EXAMPLE3 = "/certs/enduser-certs/user3-key-with-password-shh.p12";
|
||||||
|
|
||||||
|
private static String KEYFILE_PASSWORD1 = "secret";
|
||||||
|
private static String KEYFILE_PASSWORD3 = "shh";
|
||||||
|
|
||||||
|
private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt";
|
||||||
|
private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt";
|
||||||
|
|
||||||
|
private SubscriptionBulletinBoardClient bulletinBoardClient;
|
||||||
|
|
||||||
|
private Random random;
|
||||||
|
private BulletinBoardMessageGenerator generator;
|
||||||
|
|
||||||
|
private Semaphore jobSemaphore;
|
||||||
|
private Vector<Throwable> thrown;
|
||||||
|
|
||||||
|
public GenericSubscriptionClientTester(SubscriptionBulletinBoardClient bulletinBoardClient){
|
||||||
|
|
||||||
|
this.bulletinBoardClient = bulletinBoardClient;
|
||||||
|
|
||||||
|
signers = new BulletinBoardSignature[2];
|
||||||
|
signerIDs = new ByteString[signers.length];
|
||||||
|
signers[0] = new GenericBulletinBoardSignature(new ECDSASignature());
|
||||||
|
signers[1] = new GenericBulletinBoardSignature(new ECDSASignature());
|
||||||
|
|
||||||
|
InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE);
|
||||||
|
char[] password = KEYFILE_PASSWORD1.toCharArray();
|
||||||
|
|
||||||
|
KeyStore.Builder keyStoreBuilder;
|
||||||
|
try {
|
||||||
|
keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
|
||||||
|
signers[0].loadSigningCertificate(keyStoreBuilder);
|
||||||
|
|
||||||
|
signers[0].loadVerificationCertificates(getClass().getResourceAsStream(CERT1_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE3);
|
||||||
|
password = KEYFILE_PASSWORD3.toCharArray();
|
||||||
|
|
||||||
|
keyStoreBuilder = signers[1].getPKCS12KeyStoreBuilder(keyStream, password);
|
||||||
|
signers[1].loadSigningCertificate(keyStoreBuilder);
|
||||||
|
|
||||||
|
signers[1].loadVerificationCertificates(getClass().getResourceAsStream(CERT3_PEM_EXAMPLE));
|
||||||
|
|
||||||
|
for (int i = 0 ; i < signers.length ; i++) {
|
||||||
|
signerIDs[i] = signers[i].getSignerID();
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of initializing the client and the test resources
|
||||||
|
*/
|
||||||
|
public void init(){
|
||||||
|
|
||||||
|
random = new Random(0); // We use insecure randomness in tests for repeatability
|
||||||
|
generator = new BulletinBoardMessageGenerator(random);
|
||||||
|
|
||||||
|
thrown = new Vector<>();
|
||||||
|
jobSemaphore = new Semaphore(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
if (thrown.size() > 0) {
|
||||||
|
assert false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SubscriptionCallback implements FutureCallback<List<BulletinBoardMessage>>{
|
||||||
|
|
||||||
|
private int stage;
|
||||||
|
private final List<List<BulletinBoardMessage>> expectedMessages;
|
||||||
|
private final List<BulletinBoardMessage> messagesToPost;
|
||||||
|
private final BulletinBoardMessageComparator comparator;
|
||||||
|
|
||||||
|
public SubscriptionCallback(List<List<BulletinBoardMessage>> expectedMessages, List<BulletinBoardMessage> messagesToPost) {
|
||||||
|
|
||||||
|
this.expectedMessages = expectedMessages;
|
||||||
|
this.messagesToPost = messagesToPost;
|
||||||
|
this.stage = 0;
|
||||||
|
this.comparator = new BulletinBoardMessageComparator();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSuccess(List<BulletinBoardMessage> result) {
|
||||||
|
|
||||||
|
if (stage >= expectedMessages.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Check for consistency
|
||||||
|
|
||||||
|
List<BulletinBoardMessage> expectedMsgList = expectedMessages.get(stage);
|
||||||
|
|
||||||
|
if (expectedMsgList.size() != result.size()){
|
||||||
|
onFailure(new AssertionError("Received wrong number of messages"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<BulletinBoardMessage> expectedMessageIterator = expectedMsgList.iterator();
|
||||||
|
Iterator<BulletinBoardMessage> receivedMessageIterator = result.iterator();
|
||||||
|
|
||||||
|
while (expectedMessageIterator.hasNext()) {
|
||||||
|
if(comparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()) != 0){
|
||||||
|
onFailure(new AssertionError("Received unexpected message"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post new message
|
||||||
|
try {
|
||||||
|
if (stage < messagesToPost.size()) {
|
||||||
|
bulletinBoardClient.postMessage(messagesToPost.get(stage));
|
||||||
|
}
|
||||||
|
} catch (CommunicationException e) {
|
||||||
|
onFailure(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stage++;
|
||||||
|
jobSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
System.err.println(t.getCause() + " " + t.getMessage());
|
||||||
|
thrown.add(t);
|
||||||
|
jobSemaphore.release();
|
||||||
|
stage = expectedMessages.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void subscriptionTest() throws SignatureException, CommunicationException {
|
||||||
|
|
||||||
|
final String COMMON_TAG = "SUBSCRIPTION_TEST";
|
||||||
|
|
||||||
|
List<String> tags = new LinkedList<>();
|
||||||
|
tags.add(COMMON_TAG);
|
||||||
|
|
||||||
|
BulletinBoardMessage msg1 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(1000).setNanos(900).build(), 10, 4, tags);
|
||||||
|
BulletinBoardMessage msg2 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(800).setNanos(300).build(), 10, 4);
|
||||||
|
BulletinBoardMessage msg3 = generator.generateRandomMessage(signers, Timestamp.newBuilder().setSeconds(2000).setNanos(0).build(), 10, 4, tags);
|
||||||
|
|
||||||
|
MessageFilterList filterList = MessageFilterList.newBuilder()
|
||||||
|
.addFilter(MessageFilter.newBuilder()
|
||||||
|
.setType(FilterType.TAG)
|
||||||
|
.setTag(COMMON_TAG)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<List<BulletinBoardMessage>> expectedMessages = new ArrayList<>(3);
|
||||||
|
expectedMessages.add(new LinkedList<>());
|
||||||
|
expectedMessages.add(new LinkedList<>());
|
||||||
|
expectedMessages.add(new LinkedList<>());
|
||||||
|
expectedMessages.get(0).add(msg1);
|
||||||
|
expectedMessages.get(2).add(msg3);
|
||||||
|
|
||||||
|
List<BulletinBoardMessage> messagesToPost = new ArrayList<>(2);
|
||||||
|
messagesToPost.add(msg2);
|
||||||
|
messagesToPost.add(msg3);
|
||||||
|
|
||||||
|
bulletinBoardClient.postMessage(msg1);
|
||||||
|
bulletinBoardClient.subscribe(filterList, new SubscriptionCallback(expectedMessages, messagesToPost));
|
||||||
|
|
||||||
|
try {
|
||||||
|
jobSemaphore.acquire(3);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.err.println(e.getCause() + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.bulletinboard.sqlserver.*;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
|
*/
|
||||||
|
public class LocalBulletinBoardClientTest {
|
||||||
|
|
||||||
|
private static final int THREAD_NUM = 3;
|
||||||
|
private static final String DB_NAME = "TestDB";
|
||||||
|
|
||||||
|
private static final int SUBSRCIPTION_DELAY = 3000;
|
||||||
|
|
||||||
|
// Testers
|
||||||
|
private GenericBulletinBoardClientTester clientTest;
|
||||||
|
private GenericSubscriptionClientTester subscriptionTester;
|
||||||
|
|
||||||
|
public LocalBulletinBoardClientTest() throws CommunicationException {
|
||||||
|
|
||||||
|
H2QueryProvider queryProvider = new H2QueryProvider(DB_NAME);
|
||||||
|
|
||||||
|
DeletableBulletinBoardServer server = new BulletinBoardSQLServer(queryProvider);
|
||||||
|
server.init();
|
||||||
|
|
||||||
|
LocalBulletinBoardClient client = new LocalBulletinBoardClient(server, THREAD_NUM, SUBSRCIPTION_DELAY);
|
||||||
|
subscriptionTester = new GenericSubscriptionClientTester(client);
|
||||||
|
clientTest = new GenericBulletinBoardClientTester(client, 98354);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
import meerkat.protobuf.Voting.BulletinBoardClientParams;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by Arbel Deutsch Peled on 05-Dec-15.
|
||||||
|
*/
|
||||||
|
public class SingleServerBulletinBoardClientIntegrationTest {
|
||||||
|
|
||||||
|
// Server data
|
||||||
|
|
||||||
|
private static final String PROP_GETTY_URL = "gretty.httpBaseURI";
|
||||||
|
private static final String DEFAULT_BASE_URL = "http://localhost:8081";
|
||||||
|
private static final String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
|
||||||
|
|
||||||
|
private static final int THREAD_NUM = 3;
|
||||||
|
private static final long FAIL_DELAY = 3000;
|
||||||
|
private static final long SUBSCRIPTION_INTERVAL = 500;
|
||||||
|
|
||||||
|
// Testers
|
||||||
|
private GenericBulletinBoardClientTester clientTest;
|
||||||
|
private GenericSubscriptionClientTester subscriptionTester;
|
||||||
|
|
||||||
|
public SingleServerBulletinBoardClientIntegrationTest(){
|
||||||
|
|
||||||
|
SingleServerBulletinBoardClient client = new SingleServerBulletinBoardClient(THREAD_NUM, FAIL_DELAY, SUBSCRIPTION_INTERVAL);
|
||||||
|
|
||||||
|
List<String> testDB = new LinkedList<>();
|
||||||
|
testDB.add(BASE_URL);
|
||||||
|
|
||||||
|
client.init(BulletinBoardClientParams.newBuilder()
|
||||||
|
.addAllBulletinBoardAddress(testDB)
|
||||||
|
.setMinRedundancy((float) 1.0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
clientTest = new GenericBulletinBoardClientTester(client, 981541);
|
||||||
|
subscriptionTester = new GenericSubscriptionClientTester(client);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of initializing the client and the test resources
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void init(){
|
||||||
|
|
||||||
|
clientTest.init();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the client and makes sure the test fails when an exception occurred in a separate thread
|
||||||
|
*/
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
clientTest.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPost() {
|
||||||
|
|
||||||
|
clientTest.testPost();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
clientTest.testBatchPost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException {
|
||||||
|
|
||||||
|
clientTest.testCompleteBatchPost();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubscription() throws SignatureException, CommunicationException {
|
||||||
|
|
||||||
|
subscriptionTester.init();
|
||||||
|
subscriptionTester.subscriptionTest();
|
||||||
|
subscriptionTester.close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package meerkat.bulletinboard;
|
||||||
|
|
||||||
|
import meerkat.comm.CommunicationException;
|
||||||
|
|
||||||
|
import meerkat.protobuf.Voting.*;
|
||||||
|
|
||||||
|
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 Deutsch Peled on 05-Dec-15.
|
||||||
|
*/
|
||||||
|
public class ThreadedBulletinBoardClientIntegrationTest {
|
||||||
|
|
||||||
|
// Server data
|
||||||
|
|
||||||
|
private static String PROP_GETTY_URL = "gretty.httpBaseURI";
|
||||||
|
private static String DEFAULT_BASE_URL = "http://localhost:8081";
|
||||||
|
private static String BASE_URL = System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);
|
||||||
|
|
||||||
|
// Tester
|
||||||
|
private GenericBulletinBoardClientTester clientTest;
|
||||||
|
|
||||||
|
public ThreadedBulletinBoardClientIntegrationTest(){
|
||||||
|
|
||||||
|
ThreadedBulletinBoardClient client = new ThreadedBulletinBoardClient(3,0,500);
|
||||||
|
|
||||||
|
List<String> testDB = new LinkedList<>();
|
||||||
|
testDB.add(BASE_URL);
|
||||||
|
|
||||||
|
client.init(BulletinBoardClientParams.newBuilder()
|
||||||
|
.addAllBulletinBoardAddress(testDB)
|
||||||
|
.setMinRedundancy((float) 1.0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
clientTest = new GenericBulletinBoardClientTester(client, 52351);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"$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": {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,42 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,28 @@
|
||||||
|
# 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).
|
|
@ -0,0 +1,60 @@
|
||||||
|
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);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
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!!');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { browser, by, element } from 'protractor';
|
||||||
|
|
||||||
|
export class BulletinBoardServerFrontendPage {
|
||||||
|
navigateTo() {
|
||||||
|
return browser.get('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
getParagraphText() {
|
||||||
|
return element(by.css('app-root h1')).getText();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "../out-tsc/e2e",
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"types": [
|
||||||
|
"jasmine",
|
||||||
|
"jasminewd2",
|
||||||
|
"node"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/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);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue