Compare commits
	
		
			No commits in common. "master" and "Bulletin-Board-Batch" have entirely different histories. 
		
	
	
		
			master
			...
			Bulletin-B
		
	
		|  | @ -13,26 +13,4 @@ out | |||
| *.prefs | ||||
| *.project | ||||
| *.classpath | ||||
| *.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 | ||||
| 
 | ||||
| bulletin-board-server/local-instances/meerkat.db | ||||
|  |  | |||
|  | @ -1,10 +0,0 @@ | |||
| This file describes Wombat Code & Documentation Conventions: | ||||
| 
 | ||||
| Code Conventions: | ||||
|     *   Code-       The good old classic java code conventions | ||||
|                     camelCase convention, constants should be capital letters with underscore etc... | ||||
| 
 | ||||
| Documentation Conventions: | ||||
|     *   Comments-   The good old classic java code documentation, Block Comments (to describe | ||||
|                     method/class/interface etc...) // comments too describe complex code | ||||
|                     (only if the code block complicated) | ||||
|  | @ -1,82 +0,0 @@ | |||
| buildscript { | ||||
|     repositories { | ||||
|         jcenter() | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:2.3.3' | ||||
| 
 | ||||
|         // NOTE: Do not place your application dependencies here; they belong | ||||
|         // in the individual module build.gradle files | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| apply plugin: 'com.android.application' | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 26 | ||||
|     buildToolsVersion "26.0.0" | ||||
|     defaultConfig { | ||||
|         applicationId "com.meerkat.laura.fakescannerapp" | ||||
|         minSdkVersion 14 | ||||
|         targetSdkVersion 26 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||||
| 
 | ||||
|         // Enabling multidex support. | ||||
|         multiDexEnabled true | ||||
|         vectorDrawables.useSupportLibrary = true | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     buildTypes { | ||||
|         debug { | ||||
|             debuggable true | ||||
|         } | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| configurations.all { | ||||
|     resolutionStrategy.dependencySubstitution { | ||||
|         // Avoid Android compilation error caused by two different javax.inject dependencies. | ||||
|         substitute module('org.glassfish.hk2.external:javax.inject:2.4.0-b34') with module('javax.inject:javax.inject:1') | ||||
|     } | ||||
| 
 | ||||
|     // Exclude non-android logback modules | ||||
|     exclude group:"ch.qos.logback", module:"logback-core" | ||||
|     exclude group:"ch.qos.logback", module:"logback-classic" | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| dependencies { | ||||
| //    compile fileTree(dir: 'libs', include: ['*.jar']) | ||||
| 
 | ||||
|     compile project(':meerkat-common') | ||||
|     compile project(':scanner-api-common') | ||||
| 
 | ||||
|     compile 'com.android.support:appcompat-v7:26.0.+' | ||||
|     compile 'com.google.protobuf:protobuf-java:3.+' | ||||
|     compile 'com.android.support:support-v4:26.0.+' | ||||
|     compile 'com.android.support:support-vector-drawable:26.0.+' | ||||
|     testCompile 'junit:junit:4.12' | ||||
| 
 | ||||
|     // Android logging | ||||
|     compile 'com.github.tony19:logback-android-core:1.1.1-6' | ||||
|     compile('com.github.tony19:logback-android-classic:1.1.1-6') { | ||||
|         // workaround issue #73 | ||||
|         exclude group: 'com.google.android', module: 'android' | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| repositories { | ||||
|     mavenLocal() | ||||
| 
 | ||||
|     jcenter() | ||||
| } | ||||
|  | @ -1 +0,0 @@ | |||
| ../gradlew | ||||
|  | @ -1,7 +0,0 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <lint> | ||||
|     <issue id="InvalidPackage"> | ||||
|         <!-- Ignore javax.mail from logback https://github.com/tony19/logback-android/issues/140--> | ||||
|         <ignore regexp="javax.mail"/> | ||||
|     </issue> | ||||
| </lint> | ||||
|  | @ -1,13 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| import android.app.Application; | ||||
| import android.test.ApplicationTestCase; | ||||
| 
 | ||||
| /** | ||||
|  * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> | ||||
|  */ | ||||
| public class ApplicationTest extends ApplicationTestCase<Application> { | ||||
|     public ApplicationTest() { | ||||
|         super(Application.class); | ||||
|     } | ||||
| } | ||||
|  | @ -1,28 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|           package="com.meerkat.laura.fakescannerapp"> | ||||
| 
 | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> | ||||
|     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> | ||||
| 
 | ||||
|     <application | ||||
|             android:allowBackup="true" | ||||
|             android:icon="@mipmap/ic_launcher" | ||||
|             android:label="@string/app_name" | ||||
|             android:supportsRtl="true" | ||||
|             android:theme="@style/AppTheme"> | ||||
|         <activity android:name=".MainActivity"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN"/> | ||||
| 
 | ||||
|                 <category android:name="android.intent.category.LAUNCHER"/> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|         <activity | ||||
|                 android:name=".SettingsActivity" | ||||
|                 android:label="@string/title_activity_settings"> | ||||
|         </activity> | ||||
|     </application> | ||||
| 
 | ||||
| </manifest> | ||||
|  | @ -1,15 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <configuration> | ||||
|     <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender"> | ||||
|         <tagEncoder> | ||||
|             <pattern>%logger{12}</pattern> | ||||
|         </tagEncoder> | ||||
|         <encoder> | ||||
|             <pattern>[%-20thread] %msg</pattern> | ||||
|         </encoder> | ||||
|     </appender> | ||||
| 
 | ||||
|     <root level="DEBUG"> | ||||
|         <appender-ref ref="logcat" /> | ||||
|     </root> | ||||
| </configuration> | ||||
|  | @ -1,508 +0,0 @@ | |||
| package com.google.zxing.integration.android; | ||||
| 
 | ||||
| /* | ||||
|  * Copyright 2009 ZXing authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.app.AlertDialog; | ||||
| import android.app.Fragment; | ||||
| import android.content.ActivityNotFoundException; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.Intent; | ||||
| import android.content.pm.PackageManager; | ||||
| import android.content.pm.ResolveInfo; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| /** | ||||
|  * <p>A utility class which helps ease integration with Barcode Scanner via {@link Intent}s. This is a simple | ||||
|  * way to invoke barcode scanning and receive the result, without any need to integrate, modify, or learn the | ||||
|  * project's source code.</p> | ||||
|  * | ||||
|  * <h2>Initiating a barcode scan</h2> | ||||
|  * | ||||
|  * <p>To integrate, create an instance of {@code IntentIntegrator} and call {@link #initiateScan()} and wait | ||||
|  * for the result in your app.</p> | ||||
|  * | ||||
|  * <p>It does require that the Barcode Scanner (or work-alike) application is installed. The | ||||
|  * {@link #initiateScan()} method will prompt the user to download the application, if needed.</p> | ||||
|  * | ||||
|  * <p>There are a few steps to using this integration. First, your {@link Activity} must implement | ||||
|  * the method {@link Activity#onActivityResult(int, int, Intent)} and include a line of code like this:</p> | ||||
|  * | ||||
|  * <pre>{@code | ||||
|  * public void onActivityResult(int requestCode, int resultCode, Intent intent) { | ||||
|  *   IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); | ||||
|  *   if (scanResult != null) { | ||||
|  *     // handle scan result
 | ||||
|  *   } | ||||
|  *   // else continue with any other code you need in the method
 | ||||
|  *   ... | ||||
|  * } | ||||
|  * }</pre> | ||||
|  * | ||||
|  * <p>This is where you will handle a scan result.</p> | ||||
|  * | ||||
|  * <p>Second, just call this in response to a user action somewhere to begin the scan process:</p> | ||||
|  * | ||||
|  * <pre>{@code | ||||
|  * IntentIntegrator integrator = new IntentIntegrator(yourActivity); | ||||
|  * integrator.initiateScan(); | ||||
|  * }</pre> | ||||
|  * | ||||
|  * <p>Note that {@link #initiateScan()} returns an {@link AlertDialog} which is non-null if the | ||||
|  * user was prompted to download the application. This lets the calling app potentially manage the dialog. | ||||
|  * In particular, ideally, the app dismisses the dialog if it's still active in its {@link Activity#onPause()} | ||||
|  * method.</p> | ||||
|  * | ||||
|  * <p>You can use {@link #setTitle(String)} to customize the title of this download prompt dialog (or, use | ||||
|  * {@link #setTitleByID(int)} to set the title by string resource ID.) Likewise, the prompt message, and | ||||
|  * yes/no button labels can be changed.</p> | ||||
|  * | ||||
|  * <p>Finally, you can use {@link #addExtra(String, Object)} to add more parameters to the Intent used | ||||
|  * to invoke the scanner. This can be used to set additional options not directly exposed by this | ||||
|  * simplified API.</p> | ||||
|  * | ||||
|  * <p>By default, this will only allow applications that are known to respond to this intent correctly | ||||
|  * do so. The apps that are allowed to response can be set with {@link #setTargetApplications(List)}. | ||||
|  * For example, set to {@link #TARGET_BARCODE_SCANNER_ONLY} to only target the Barcode Scanner app itself.</p> | ||||
|  * | ||||
|  * <h2>Sharing text via barcode</h2> | ||||
|  * | ||||
|  * <p>To share text, encoded as a QR Code on-screen, similarly, see {@link #shareText(CharSequence)}.</p> | ||||
|  * | ||||
|  * <p>Some code, particularly download integration, was contributed from the Anobiit application.</p> | ||||
|  * | ||||
|  * <h2>Enabling experimental barcode formats</h2> | ||||
|  * | ||||
|  * <p>Some formats are not enabled by default even when scanning with {@link #ALL_CODE_TYPES}, such as | ||||
|  * PDF417. Use {@link #initiateScan(Collection)} with | ||||
|  * a collection containing the names of formats to scan for explicitly, like "PDF_417", to use such | ||||
|  * formats.</p> | ||||
|  * | ||||
|  * @author Sean Owen | ||||
|  * @author Fred Lin | ||||
|  * @author Isaac Potoczny-Jones | ||||
|  * @author Brad Drehmer | ||||
|  * @author gcstang | ||||
|  */ | ||||
| public class IntentIntegrator { | ||||
| 
 | ||||
|     public static final int REQUEST_CODE = 0x0000c0de; // Only use bottom 16 bits
 | ||||
|     private static final String TAG = IntentIntegrator.class.getSimpleName(); | ||||
| 
 | ||||
|     public static final String DEFAULT_TITLE = "Install Barcode Scanner?"; | ||||
|     public static final String DEFAULT_MESSAGE = | ||||
|             "This application requires Barcode Scanner. Would you like to install it?"; | ||||
|     public static final String DEFAULT_YES = "Yes"; | ||||
|     public static final String DEFAULT_NO = "No"; | ||||
| 
 | ||||
|     private static final String BS_PACKAGE = "com.google.zxing.client.android"; | ||||
|     private static final String BSPLUS_PACKAGE = "com.srowen.bs.android"; | ||||
| 
 | ||||
|     // supported barcode formats
 | ||||
|     public static final Collection<String> PRODUCT_CODE_TYPES = list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "RSS_14"); | ||||
|     public static final Collection<String> ONE_D_CODE_TYPES = | ||||
|             list("UPC_A", "UPC_E", "EAN_8", "EAN_13", "CODE_39", "CODE_93", "CODE_128", | ||||
|                     "ITF", "RSS_14", "RSS_EXPANDED"); | ||||
|     public static final Collection<String> QR_CODE_TYPES = Collections.singleton("QR_CODE"); | ||||
|     public static final Collection<String> DATA_MATRIX_TYPES = Collections.singleton("DATA_MATRIX"); | ||||
| 
 | ||||
|     public static final Collection<String> ALL_CODE_TYPES = null; | ||||
| 
 | ||||
|     public static final List<String> TARGET_BARCODE_SCANNER_ONLY = Collections.singletonList(BS_PACKAGE); | ||||
|     public static final List<String> TARGET_ALL_KNOWN = list( | ||||
|             BSPLUS_PACKAGE,             // Barcode Scanner+
 | ||||
|             BSPLUS_PACKAGE + ".simple", // Barcode Scanner+ Simple
 | ||||
|             BS_PACKAGE                  // Barcode Scanner
 | ||||
|             // What else supports this intent?
 | ||||
|     ); | ||||
| 
 | ||||
|     private final Activity activity; | ||||
|     private final Fragment fragment; | ||||
| 
 | ||||
|     private String title; | ||||
|     private String message; | ||||
|     private String buttonYes; | ||||
|     private String buttonNo; | ||||
|     private List<String> targetApplications; | ||||
|     private final Map<String,Object> moreExtras = new HashMap<String,Object>(3); | ||||
| 
 | ||||
|     /** | ||||
|      * @param activity {@link Activity} invoking the integration | ||||
|      */ | ||||
|     public IntentIntegrator(Activity activity) { | ||||
|         this.activity = activity; | ||||
|         this.fragment = null; | ||||
|         initializeConfiguration(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param fragment {@link Fragment} invoking the integration. | ||||
|      *  {@link #startActivityForResult(Intent, int)} will be called on the {@link Fragment} instead | ||||
|      *  of an {@link Activity} | ||||
|      */ | ||||
|     public IntentIntegrator(Fragment fragment) { | ||||
|         this.activity = fragment.getActivity(); | ||||
|         this.fragment = fragment; | ||||
|         initializeConfiguration(); | ||||
|     } | ||||
| 
 | ||||
|     private void initializeConfiguration() { | ||||
|         title = DEFAULT_TITLE; | ||||
|         message = DEFAULT_MESSAGE; | ||||
|         buttonYes = DEFAULT_YES; | ||||
|         buttonNo = DEFAULT_NO; | ||||
|         targetApplications = TARGET_ALL_KNOWN; | ||||
|     } | ||||
| 
 | ||||
|     public String getTitle() { | ||||
|         return title; | ||||
|     } | ||||
| 
 | ||||
|     public void setTitle(String title) { | ||||
|         this.title = title; | ||||
|     } | ||||
| 
 | ||||
|     public void setTitleByID(int titleID) { | ||||
|         title = activity.getString(titleID); | ||||
|     } | ||||
| 
 | ||||
|     public String getMessage() { | ||||
|         return message; | ||||
|     } | ||||
| 
 | ||||
|     public void setMessage(String message) { | ||||
|         this.message = message; | ||||
|     } | ||||
| 
 | ||||
|     public void setMessageByID(int messageID) { | ||||
|         message = activity.getString(messageID); | ||||
|     } | ||||
| 
 | ||||
|     public String getButtonYes() { | ||||
|         return buttonYes; | ||||
|     } | ||||
| 
 | ||||
|     public void setButtonYes(String buttonYes) { | ||||
|         this.buttonYes = buttonYes; | ||||
|     } | ||||
| 
 | ||||
|     public void setButtonYesByID(int buttonYesID) { | ||||
|         buttonYes = activity.getString(buttonYesID); | ||||
|     } | ||||
| 
 | ||||
|     public String getButtonNo() { | ||||
|         return buttonNo; | ||||
|     } | ||||
| 
 | ||||
|     public void setButtonNo(String buttonNo) { | ||||
|         this.buttonNo = buttonNo; | ||||
|     } | ||||
| 
 | ||||
|     public void setButtonNoByID(int buttonNoID) { | ||||
|         buttonNo = activity.getString(buttonNoID); | ||||
|     } | ||||
| 
 | ||||
|     public Collection<String> getTargetApplications() { | ||||
|         return targetApplications; | ||||
|     } | ||||
| 
 | ||||
|     public final void setTargetApplications(List<String> targetApplications) { | ||||
|         if (targetApplications.isEmpty()) { | ||||
|             throw new IllegalArgumentException("No target applications"); | ||||
|         } | ||||
|         this.targetApplications = targetApplications; | ||||
|     } | ||||
| 
 | ||||
|     public void setSingleTargetApplication(String targetApplication) { | ||||
|         this.targetApplications = Collections.singletonList(targetApplication); | ||||
|     } | ||||
| 
 | ||||
|     public Map<String,?> getMoreExtras() { | ||||
|         return moreExtras; | ||||
|     } | ||||
| 
 | ||||
|     public final void addExtra(String key, Object value) { | ||||
|         moreExtras.put(key, value); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initiates a scan for all known barcode types with the default camera. | ||||
|      * | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise. | ||||
|      */ | ||||
|     public final AlertDialog initiateScan() { | ||||
|         return initiateScan(ALL_CODE_TYPES, -1); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initiates a scan for all known barcode types with the specified camera. | ||||
|      * | ||||
|      * @param cameraId camera ID of the camera to use. A negative value means "no preference". | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise. | ||||
|      */ | ||||
|     public final AlertDialog initiateScan(int cameraId) { | ||||
|         return initiateScan(ALL_CODE_TYPES, cameraId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initiates a scan, using the default camera, only for a certain set of barcode types, given as strings corresponding | ||||
|      * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants | ||||
|      * like {@link #PRODUCT_CODE_TYPES} for example. | ||||
|      * | ||||
|      * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise. | ||||
|      */ | ||||
|     public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats) { | ||||
|         return initiateScan(desiredBarcodeFormats, -1); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Initiates a scan, using the specified camera, only for a certain set of barcode types, given as strings corresponding | ||||
|      * to their names in ZXing's {@code BarcodeFormat} class like "UPC_A". You can supply constants | ||||
|      * like {@link #PRODUCT_CODE_TYPES} for example. | ||||
|      * | ||||
|      * @param desiredBarcodeFormats names of {@code BarcodeFormat}s to scan for | ||||
|      * @param cameraId camera ID of the camera to use. A negative value means "no preference". | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise | ||||
|      */ | ||||
|     public final AlertDialog initiateScan(Collection<String> desiredBarcodeFormats, int cameraId) { | ||||
|         Intent intentScan = new Intent(BS_PACKAGE + ".SCAN"); | ||||
|         intentScan.addCategory(Intent.CATEGORY_DEFAULT); | ||||
| 
 | ||||
|         // check which types of codes to scan for
 | ||||
|         if (desiredBarcodeFormats != null) { | ||||
|             // set the desired barcode types
 | ||||
|             StringBuilder joinedByComma = new StringBuilder(); | ||||
|             for (String format : desiredBarcodeFormats) { | ||||
|                 if (joinedByComma.length() > 0) { | ||||
|                     joinedByComma.append(','); | ||||
|                 } | ||||
|                 joinedByComma.append(format); | ||||
|             } | ||||
|             intentScan.putExtra("SCAN_FORMATS", joinedByComma.toString()); | ||||
|         } | ||||
| 
 | ||||
|         // check requested camera ID
 | ||||
|         if (cameraId >= 0) { | ||||
|             intentScan.putExtra("SCAN_CAMERA_ID", cameraId); | ||||
|         } | ||||
| 
 | ||||
|         String targetAppPackage = findTargetAppPackage(intentScan); | ||||
|         if (targetAppPackage == null) { | ||||
|             return showDownloadDialog(); | ||||
|         } | ||||
|         intentScan.setPackage(targetAppPackage); | ||||
|         intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||||
|         intentScan.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | ||||
|         attachMoreExtras(intentScan); | ||||
|         startActivityForResult(intentScan, REQUEST_CODE); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Start an activity. This method is defined to allow different methods of activity starting for | ||||
|      * newer versions of Android and for compatibility library. | ||||
|      * | ||||
|      * @param intent Intent to start. | ||||
|      * @param code Request code for the activity | ||||
|      * @see Activity#startActivityForResult(Intent, int) | ||||
|      * @see Fragment#startActivityForResult(Intent, int) | ||||
|      */ | ||||
|     protected void startActivityForResult(Intent intent, int code) { | ||||
|         if (fragment == null) { | ||||
|             activity.startActivityForResult(intent, code); | ||||
|         } else { | ||||
|             fragment.startActivityForResult(intent, code); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private String findTargetAppPackage(Intent intent) { | ||||
|         PackageManager pm = activity.getPackageManager(); | ||||
|         List<ResolveInfo> availableApps = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); | ||||
|         if (availableApps != null) { | ||||
|             for (String targetApp : targetApplications) { | ||||
|                 if (contains(availableApps, targetApp)) { | ||||
|                     return targetApp; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private static boolean contains(Iterable<ResolveInfo> availableApps, String targetApp) { | ||||
|         for (ResolveInfo availableApp : availableApps) { | ||||
|             String packageName = availableApp.activityInfo.packageName; | ||||
|             if (targetApp.equals(packageName)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     private AlertDialog showDownloadDialog() { | ||||
|         AlertDialog.Builder downloadDialog = new AlertDialog.Builder(activity); | ||||
|         downloadDialog.setTitle(title); | ||||
|         downloadDialog.setMessage(message); | ||||
|         downloadDialog.setPositiveButton(buttonYes, new DialogInterface.OnClickListener() { | ||||
|             @Override | ||||
|             public void onClick(DialogInterface dialogInterface, int i) { | ||||
|                 String packageName; | ||||
|                 if (targetApplications.contains(BS_PACKAGE)) { | ||||
|                     // Prefer to suggest download of BS if it's anywhere in the list
 | ||||
|                     packageName = BS_PACKAGE; | ||||
|                 } else { | ||||
|                     // Otherwise, first option:
 | ||||
|                     packageName = targetApplications.get(0); | ||||
|                 } | ||||
|                 Uri uri = Uri.parse("market://details?id=" + packageName); | ||||
|                 Intent intent = new Intent(Intent.ACTION_VIEW, uri); | ||||
|                 try { | ||||
|                     if (fragment == null) { | ||||
|                         activity.startActivity(intent); | ||||
|                     } else { | ||||
|                         fragment.startActivity(intent); | ||||
|                     } | ||||
|                 } catch (ActivityNotFoundException anfe) { | ||||
|                     // Hmm, market is not installed
 | ||||
|                     Log.w(TAG, "Google Play is not installed; cannot install " + packageName); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         downloadDialog.setNegativeButton(buttonNo, null); | ||||
|         downloadDialog.setCancelable(true); | ||||
|         return downloadDialog.show(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * <p>Call this from your {@link Activity}'s | ||||
|      * {@link Activity#onActivityResult(int, int, Intent)} method.</p> | ||||
|      * | ||||
|      * @param requestCode request code from {@code onActivityResult()} | ||||
|      * @param resultCode result code from {@code onActivityResult()} | ||||
|      * @param intent {@link Intent} from {@code onActivityResult()} | ||||
|      * @return null if the event handled here was not related to this class, or | ||||
|      *  else an {@link IntentResult} containing the result of the scan. If the user cancelled scanning, | ||||
|      *  the fields will be null. | ||||
|      */ | ||||
|     public static IntentResult parseActivityResult(int requestCode, int resultCode, Intent intent) { | ||||
|         if (requestCode == REQUEST_CODE) { | ||||
|             if (resultCode == Activity.RESULT_OK) { | ||||
|                 String contents = intent.getStringExtra("SCAN_RESULT"); | ||||
|                 String formatName = intent.getStringExtra("SCAN_RESULT_FORMAT"); | ||||
|                 byte[] rawBytes = intent.getByteArrayExtra("SCAN_RESULT_BYTES"); | ||||
|                 int intentOrientation = intent.getIntExtra("SCAN_RESULT_ORIENTATION", Integer.MIN_VALUE); | ||||
|                 Integer orientation = intentOrientation == Integer.MIN_VALUE ? null : intentOrientation; | ||||
|                 String errorCorrectionLevel = intent.getStringExtra("SCAN_RESULT_ERROR_CORRECTION_LEVEL"); | ||||
|                 return new IntentResult(contents, | ||||
|                         formatName, | ||||
|                         rawBytes, | ||||
|                         orientation, | ||||
|                         errorCorrectionLevel); | ||||
|             } | ||||
|             return new IntentResult(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Defaults to type "TEXT_TYPE". | ||||
|      * | ||||
|      * @param text the text string to encode as a barcode | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise | ||||
|      * @see #shareText(CharSequence, CharSequence) | ||||
|      */ | ||||
|     public final AlertDialog shareText(CharSequence text) { | ||||
|         return shareText(text, "TEXT_TYPE"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Shares the given text by encoding it as a barcode, such that another user can | ||||
|      * scan the text off the screen of the device. | ||||
|      * | ||||
|      * @param text the text string to encode as a barcode | ||||
|      * @param type type of data to encode. See {@code com.google.zxing.client.android.Contents.Type} constants. | ||||
|      * @return the {@link AlertDialog} that was shown to the user prompting them to download the app | ||||
|      *   if a prompt was needed, or null otherwise | ||||
|      */ | ||||
|     public final AlertDialog shareText(CharSequence text, CharSequence type) { | ||||
|         Intent intent = new Intent(); | ||||
|         intent.addCategory(Intent.CATEGORY_DEFAULT); | ||||
|         intent.setAction(BS_PACKAGE + ".ENCODE"); | ||||
|         intent.putExtra("ENCODE_TYPE", type); | ||||
|         intent.putExtra("ENCODE_DATA", text); | ||||
|         String targetAppPackage = findTargetAppPackage(intent); | ||||
|         if (targetAppPackage == null) { | ||||
|             return showDownloadDialog(); | ||||
|         } | ||||
|         intent.setPackage(targetAppPackage); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||||
|         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | ||||
|         attachMoreExtras(intent); | ||||
|         if (fragment == null) { | ||||
|             activity.startActivity(intent); | ||||
|         } else { | ||||
|             fragment.startActivity(intent); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     private static List<String> list(String... values) { | ||||
|         return Collections.unmodifiableList(Arrays.asList(values)); | ||||
|     } | ||||
| 
 | ||||
|     private void attachMoreExtras(Intent intent) { | ||||
|         for (Map.Entry<String,Object> entry : moreExtras.entrySet()) { | ||||
|             String key = entry.getKey(); | ||||
|             Object value = entry.getValue(); | ||||
|             // Kind of hacky
 | ||||
|             if (value instanceof Integer) { | ||||
|                 intent.putExtra(key, (Integer) value); | ||||
|             } else if (value instanceof Long) { | ||||
|                 intent.putExtra(key, (Long) value); | ||||
|             } else if (value instanceof Boolean) { | ||||
|                 intent.putExtra(key, (Boolean) value); | ||||
|             } else if (value instanceof Double) { | ||||
|                 intent.putExtra(key, (Double) value); | ||||
|             } else if (value instanceof Float) { | ||||
|                 intent.putExtra(key, (Float) value); | ||||
|             } else if (value instanceof Bundle) { | ||||
|                 intent.putExtra(key, (Bundle) value); | ||||
|             } else { | ||||
|                 intent.putExtra(key, value.toString()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | @ -1,94 +0,0 @@ | |||
| /* | ||||
|  * Copyright 2009 ZXing authors | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package com.google.zxing.integration.android; | ||||
| 
 | ||||
| /** | ||||
|  * <p>Encapsulates the result of a barcode scan invoked through {@link IntentIntegrator}.</p> | ||||
|  * | ||||
|  * @author Sean Owen | ||||
|  */ | ||||
| public final class IntentResult { | ||||
| 
 | ||||
|     private final String contents; | ||||
|     private final String formatName; | ||||
|     private final byte[] rawBytes; | ||||
|     private final Integer orientation; | ||||
|     private final String errorCorrectionLevel; | ||||
| 
 | ||||
|     IntentResult() { | ||||
|         this(null, null, null, null, null); | ||||
|     } | ||||
| 
 | ||||
|     IntentResult(String contents, | ||||
|                  String formatName, | ||||
|                  byte[] rawBytes, | ||||
|                  Integer orientation, | ||||
|                  String errorCorrectionLevel) { | ||||
|         this.contents = contents; | ||||
|         this.formatName = formatName; | ||||
|         this.rawBytes = rawBytes; | ||||
|         this.orientation = orientation; | ||||
|         this.errorCorrectionLevel = errorCorrectionLevel; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return raw content of barcode | ||||
|      */ | ||||
|     public String getContents() { | ||||
|         return contents; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return name of format, like "QR_CODE", "UPC_A". See {@code BarcodeFormat} for more format names. | ||||
|      */ | ||||
|     public String getFormatName() { | ||||
|         return formatName; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return raw bytes of the barcode content, if applicable, or null otherwise | ||||
|      */ | ||||
|     public byte[] getRawBytes() { | ||||
|         return rawBytes; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return rotation of the image, in degrees, which resulted in a successful scan. May be null. | ||||
|      */ | ||||
|     public Integer getOrientation() { | ||||
|         return orientation; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return name of the error correction level used in the barcode, if applicable | ||||
|      */ | ||||
|     public String getErrorCorrectionLevel() { | ||||
|         return errorCorrectionLevel; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         int rawBytesLength = rawBytes == null ? 0 : rawBytes.length; | ||||
|         return "Format: " + formatName + '\n' + | ||||
|                 "Contents: " + contents + '\n' + | ||||
|                 "Raw bytes: (" + rawBytesLength + " bytes)\n" + | ||||
|                 "Orientation: " + orientation + '\n' + | ||||
|                 "EC level: " + errorCorrectionLevel + '\n'; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
|  | @ -1,109 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| import android.content.res.Configuration; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceActivity; | ||||
| import android.support.annotation.LayoutRes; | ||||
| import android.support.annotation.Nullable; | ||||
| import android.support.v7.app.ActionBar; | ||||
| import android.support.v7.app.AppCompatDelegate; | ||||
| import android.support.v7.widget.Toolbar; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| /** | ||||
|  * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls | ||||
|  * to be used with AppCompat. | ||||
|  */ | ||||
| public abstract class AppCompatPreferenceActivity extends PreferenceActivity { | ||||
| 
 | ||||
|     private AppCompatDelegate mDelegate; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         getDelegate().installViewFactory(); | ||||
|         getDelegate().onCreate(savedInstanceState); | ||||
|         super.onCreate(savedInstanceState); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostCreate(Bundle savedInstanceState) { | ||||
|         super.onPostCreate(savedInstanceState); | ||||
|         getDelegate().onPostCreate(savedInstanceState); | ||||
|     } | ||||
| 
 | ||||
|     public ActionBar getSupportActionBar() { | ||||
|         return getDelegate().getSupportActionBar(); | ||||
|     } | ||||
| 
 | ||||
|     public void setSupportActionBar(@Nullable Toolbar toolbar) { | ||||
|         getDelegate().setSupportActionBar(toolbar); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MenuInflater getMenuInflater() { | ||||
|         return getDelegate().getMenuInflater(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContentView(@LayoutRes int layoutResID) { | ||||
|         getDelegate().setContentView(layoutResID); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContentView(View view) { | ||||
|         getDelegate().setContentView(view); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContentView(View view, ViewGroup.LayoutParams params) { | ||||
|         getDelegate().setContentView(view, params); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void addContentView(View view, ViewGroup.LayoutParams params) { | ||||
|         getDelegate().addContentView(view, params); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostResume() { | ||||
|         super.onPostResume(); | ||||
|         getDelegate().onPostResume(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onTitleChanged(CharSequence title, int color) { | ||||
|         super.onTitleChanged(title, color); | ||||
|         getDelegate().setTitle(title); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onConfigurationChanged(Configuration newConfig) { | ||||
|         super.onConfigurationChanged(newConfig); | ||||
|         getDelegate().onConfigurationChanged(newConfig); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStop() { | ||||
|         super.onStop(); | ||||
|         getDelegate().onStop(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onDestroy() { | ||||
|         super.onDestroy(); | ||||
|         getDelegate().onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     public void invalidateOptionsMenu() { | ||||
|         getDelegate().invalidateOptionsMenu(); | ||||
|     } | ||||
| 
 | ||||
|     private AppCompatDelegate getDelegate() { | ||||
|         if (mDelegate == null) { | ||||
|             mDelegate = AppCompatDelegate.create(this, null); | ||||
|         } | ||||
|         return mDelegate; | ||||
|     } | ||||
| } | ||||
|  | @ -1,252 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| 
 | ||||
| import android.content.Intent; | ||||
| import android.content.SharedPreferences; | ||||
| import android.graphics.Color; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Bundle; | ||||
| import android.preference.Preference; | ||||
| import android.preference.PreferenceManager; | ||||
| import android.support.v7.app.AppCompatActivity; | ||||
| import android.util.Base64; | ||||
| import android.util.Log; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.widget.Button; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| import ch.qos.logback.classic.android.BasicLogcatConfigurator; | ||||
| import com.google.protobuf.InvalidProtocolBufferException; | ||||
| import com.google.protobuf.Message; | ||||
| import com.google.zxing.integration.android.IntentIntegrator; | ||||
| import com.google.zxing.integration.android.IntentResult; | ||||
| import meerkat.crypto.DigitalSignatureGenerator; | ||||
| import meerkat.crypto.concrete.ECDSADeterministicSignature; | ||||
| import meerkat.pollingstation.PollingStationScanner; | ||||
| import meerkat.pollingstation.ScannerClientAPI; | ||||
| import meerkat.protobuf.PollingStation; | ||||
| 
 | ||||
| import java.math.BigInteger; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import static com.meerkat.laura.fakescannerapp.R.xml.preferences; | ||||
| 
 | ||||
| 
 | ||||
| public class MainActivity extends AppCompatActivity implements View.OnClickListener,  SharedPreferences.OnSharedPreferenceChangeListener { | ||||
| 
 | ||||
|     static { | ||||
|         BasicLogcatConfigurator.configureDefaultContext(); | ||||
|     } | ||||
| 
 | ||||
|     final public static String SCANNER_NAME = "AndroidScanner"; | ||||
|     SharedPreferences sharedPref; | ||||
| 
 | ||||
|     Button scanBtn; | ||||
|     TextView formatTxt, contentTxt, responseTxt, serverStatus; | ||||
|     PollingStationScanner.ScannerClient scannerClient; | ||||
|     DigitalSignatureGenerator signer; | ||||
| 
 | ||||
|     class AsyncScanConnect extends AsyncTask<Void, Void, Boolean> { | ||||
|         PollingStation.ConnectionServerData serverData; | ||||
| 
 | ||||
|         public AsyncScanConnect(PollingStation.ConnectionServerData connectionServerData) { | ||||
|             this.serverData = connectionServerData; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPreExecute() { | ||||
|             super.onPreExecute(); | ||||
|             serverStatus.setTextColor(Color.BLUE); | ||||
|             serverStatus.setText("Connecting to " + serverData.getServerUrl()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected Boolean doInBackground(Void... foo) { | ||||
|             try { | ||||
|                 return scannerClient.connect(serverData); | ||||
|             } catch (RuntimeException e) { | ||||
|                 Log.e("MainActivity", "Exception during connect: " + e.toString()); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPostExecute(Boolean res) { | ||||
|             super.onPostExecute(res); | ||||
|             if (res) { | ||||
|                 serverStatus.setTextColor(Color.GREEN); | ||||
|                 serverStatus.setText("Connected to " + serverData.getServerUrl()); | ||||
|             } else { | ||||
|                 serverStatus.setTextColor(Color.RED); | ||||
|                 serverStatus.setText("Connection failed: " + serverData.getServerUrl()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     class AsyncScanSend extends AsyncTask<Void, Void, Boolean> { | ||||
|         Message data; | ||||
| 
 | ||||
|         public AsyncScanSend(Message data) { | ||||
|             this.data = data; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPreExecute() { | ||||
|             super.onPreExecute(); | ||||
|             serverStatus.setTextColor(Color.BLUE); | ||||
|             serverStatus.setText("Sending to server..."); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected Boolean doInBackground(Void... foo) { | ||||
|             try { | ||||
|                 if (data instanceof PollingStation.ScannedBallot) { | ||||
|                     return scannerClient.newScan((PollingStation.ScannedBallot) data); | ||||
|                 } else if (data instanceof PollingStation.ScanError) { | ||||
|                     return scannerClient.reportError((PollingStation.ScanError) data); | ||||
|                 } else { | ||||
|                     Log.e("MainActivity", "Trying to send invalid message to scanner"); | ||||
|                     return false; | ||||
|                 } | ||||
|             } catch (RuntimeException e) { | ||||
|                 Log.e("MainActivity", "Exception during send: " + e.toString()); | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void onPostExecute(Boolean res) { | ||||
|             super.onPostExecute(res); | ||||
|             if (res) { | ||||
|                 serverStatus.setTextColor(Color.GREEN); | ||||
|                 serverStatus.setText("Sent successfully"); | ||||
|                 Log.i("MainActivity", "post submitted to API."); | ||||
|             } else { | ||||
|                 serverStatus.setTextColor(Color.RED); | ||||
|                 serverStatus.setText("Send failed"); | ||||
|                 Log.e("MainActivity", "Unable to submit post to API."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreateOptionsMenu(Menu menu) { | ||||
|         MenuInflater inflater = getMenuInflater(); | ||||
|         inflater.inflate(R.menu.options_menu, menu); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(MenuItem item) { | ||||
|         // Handle item selection
 | ||||
|         switch (item.getItemId()) { | ||||
|             case R.id.action_settings: | ||||
|                 Intent i = new Intent(this, SettingsActivity.class); | ||||
|                 startActivity(i); | ||||
|                 return true; | ||||
|             default: | ||||
|                 return super.onOptionsItemSelected(item); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     void setNewScannerClientAPI(SharedPreferences sharedPref) { | ||||
|         String pscUrlString = sharedPref.getString(SettingsFragment.PSC_URL, ""); | ||||
|         long nonce = 0; | ||||
| 
 | ||||
|         try { | ||||
|             nonce = Long.parseLong(sharedPref.getString(SettingsFragment.PSC_NONCE, "0")); | ||||
|         } catch (NumberFormatException e) { | ||||
|             // Ignore
 | ||||
|         } | ||||
| 
 | ||||
|         if (pscUrlString.isEmpty()) | ||||
|             return; | ||||
| 
 | ||||
|         PollingStation.ConnectionServerData serverData = PollingStation.ConnectionServerData.newBuilder() | ||||
|                 .setServerUrl(pscUrlString) | ||||
|                 .setNonce(nonce) | ||||
|                 .build(); | ||||
| 
 | ||||
|         new AsyncScanConnect(serverData).execute(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         setContentView(R.layout.activity_main); | ||||
| 
 | ||||
|         signer = new ECDSADeterministicSignature(); | ||||
|         signer.generateSigningCertificate(BigInteger.ONE, new Date(System.currentTimeMillis()), | ||||
|                 new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 365), SCANNER_NAME); | ||||
|         scannerClient = new ScannerClientAPI(signer); | ||||
| 
 | ||||
|         PreferenceManager.setDefaultValues(this, preferences, false); | ||||
|         sharedPref = PreferenceManager.getDefaultSharedPreferences(this); | ||||
|         sharedPref.registerOnSharedPreferenceChangeListener(this); | ||||
| 
 | ||||
|         scanBtn = (Button) findViewById(R.id.scan_button); | ||||
|         formatTxt = (TextView) findViewById(R.id.scan_format); | ||||
|         contentTxt = (TextView) findViewById(R.id.scan_content); | ||||
|         responseTxt = (TextView) findViewById(R.id.server_response); | ||||
|         serverStatus = (TextView) findViewById(R.id.serverStatus); | ||||
| 
 | ||||
|         setNewScannerClientAPI(sharedPref); | ||||
|         scanBtn.setOnClickListener(this); | ||||
|     } | ||||
| 
 | ||||
|     public void onClick(View v) { | ||||
|         if (v.getId() == R.id.scan_button) { | ||||
|             IntentIntegrator scanIntegrator = new IntentIntegrator(this); | ||||
|             formatTxt.setText("Initiating scan..."); | ||||
|             scanIntegrator.initiateScan(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void onActivityResult(int requestCode, int resultCode, Intent intent) { | ||||
|         IntentResult scanningResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); | ||||
|         if (scanningResult != null) { | ||||
|             String scanContent = scanningResult.getContents(); | ||||
|             byte[] rawBytes = scanningResult.getRawBytes(); | ||||
|             String scanFormat = scanningResult.getFormatName(); | ||||
|             formatTxt.setText("FORMAT: " + scanFormat); | ||||
|             contentTxt.setText("CONTENT: " + scanContent + "sending data to server..."); | ||||
| //            sendPost(rawBytes);
 | ||||
|             sendPost(scanContent); | ||||
|         } else { | ||||
|             Toast toast = Toast.makeText(getApplicationContext(), | ||||
|                     "No scan data received!", Toast.LENGTH_SHORT); | ||||
|             toast.show(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //    public void sendPost(byte[] body) {
 | ||||
|     public void sendPost(String scanContent) { | ||||
| 
 | ||||
|         try { | ||||
|             PollingStation.ScannedBallot scannedBallot = PollingStation.ScannedBallot.parseFrom(Base64.decode(scanContent.getBytes(), Base64.DEFAULT)); | ||||
|             //
 | ||||
|             //        PollingStation.ScannedData scannedData = PollingStation.ScannedData.newBuilder()
 | ||||
|             //                .setChannel(ByteString.copyFrom(body))
 | ||||
|             //                .build();
 | ||||
| 
 | ||||
|             new AsyncScanSend(scannedBallot).execute(); | ||||
|         } catch (InvalidProtocolBufferException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void showResponse(String response) { | ||||
|         responseTxt.setText(response); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key){ | ||||
|         setNewScannerClientAPI(sharedPreferences); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,26 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| import android.app.Activity; | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| /** | ||||
|  * Created by talm on 25/06/17. | ||||
|  */ | ||||
| public class SettingsActivity extends Activity { | ||||
|     SettingsFragment settings; | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
| 
 | ||||
|         settings = new SettingsFragment(); | ||||
|         PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(settings); | ||||
| 
 | ||||
|         // Display the fragment as the main content.
 | ||||
|         getFragmentManager().beginTransaction() | ||||
|                 .replace(android.R.id.content, settings) | ||||
|                 .commit(); | ||||
|     } | ||||
| } | ||||
|  | @ -1,34 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| import android.content.SharedPreferences; | ||||
| import android.os.Bundle; | ||||
| import android.preference.Preference; | ||||
| import android.preference.PreferenceActivity; | ||||
| import android.preference.PreferenceFragment; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener { | ||||
|     public final static String PSC_URL = "psc_url"; | ||||
|     public final static String PSC_NONCE = "psc_nonce"; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         addPreferencesFromResource(R.xml.preferences); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { | ||||
|         Preference pref = findPreference(key); | ||||
|         if (pref == null) | ||||
|             return; | ||||
|         switch(key) { | ||||
|             case SettingsFragment.PSC_URL: | ||||
|                 pref.setSummary(sharedPreferences.getString(key, "")); | ||||
|                 break; | ||||
|             case SettingsFragment.PSC_NONCE: | ||||
|                 pref.setSummary(sharedPreferences.getString(key, "")); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,9 +0,0 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|             android:fillColor="#FF000000" | ||||
|             android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z"/> | ||||
| </vector> | ||||
|  | @ -1,9 +0,0 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|             android:fillColor="#FF000000" | ||||
|             android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z"/> | ||||
| </vector> | ||||
|  | @ -1,9 +0,0 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         android:width="24dp" | ||||
|         android:height="24dp" | ||||
|         android:viewportWidth="24.0" | ||||
|         android:viewportHeight="24.0"> | ||||
|     <path | ||||
|             android:fillColor="#FF000000" | ||||
|             android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z"/> | ||||
| </vector> | ||||
|  | @ -1,58 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:paddingLeft="@dimen/activity_horizontal_margin" | ||||
|     android:paddingRight="@dimen/activity_horizontal_margin" | ||||
|     android:paddingTop="@dimen/activity_vertical_margin" | ||||
|     android:paddingBottom="@dimen/activity_vertical_margin" | ||||
|     tools:context=".MainActivity"> | ||||
| 
 | ||||
|     <TextView android:text="@string/instructions" android:layout_width="wrap_content" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:id="@+id/textView" /> | ||||
| 
 | ||||
|     <Button | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:text="@string/button_txt" | ||||
|         android:id="@+id/scan_button" | ||||
|         android:layout_below="@+id/textView" | ||||
|         android:layout_centerHorizontal="true" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textIsSelectable="true" | ||||
|         android:id="@+id/scan_format" | ||||
|         android:layout_below="@+id/scan_button" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:layout_marginTop="44dp" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textIsSelectable="true" | ||||
|         android:id="@+id/scan_content" | ||||
|         android:layout_below="@+id/scan_format" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:layout_marginTop="32dp" /> | ||||
| 
 | ||||
|     <TextView | ||||
|         android:id="@+id/server_response" | ||||
|         android:layout_below="@+id/scan_content" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:textIsSelectable="true" | ||||
|         android:layout_centerHorizontal="true" | ||||
|         android:layout_marginTop="44dp" /> | ||||
|     <TextView | ||||
|             android:text="Not Connected" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" android:id="@+id/serverStatus" | ||||
|             android:layout_alignParentBottom="true" | ||||
|             android:layout_alignParentRight="true" android:layout_alignParentEnd="true" | ||||
|             android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/> | ||||
| 
 | ||||
| </RelativeLayout> | ||||
|  | @ -1,9 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
| 
 | ||||
|     <!-- Settings, should always be in the overflow --> | ||||
|     <item android:id="@+id/action_settings" | ||||
|         android:title="@string/action_settings" | ||||
|         app:showAsAction="never"/> | ||||
| </menu> | ||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 3.3 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 2.2 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 4.7 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 7.5 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 10 KiB | 
|  | @ -1,6 +0,0 @@ | |||
| <resources> | ||||
|     <!-- Example customization of dimensions originally defined in res/values/dimens.xml | ||||
|          (such as screen margins) for screens with more than 820dp of available width. This | ||||
|          would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> | ||||
|     <dimen name="activity_horizontal_margin">64dp</dimen> | ||||
| </resources> | ||||
|  | @ -1,6 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#3F51B5</color> | ||||
|     <color name="colorPrimaryDark">#303F9F</color> | ||||
|     <color name="colorAccent">#FF4081</color> | ||||
| </resources> | ||||
|  | @ -1,5 +0,0 @@ | |||
| <resources> | ||||
|     <!-- Default screen margins, per the Android Design guidelines. --> | ||||
|     <dimen name="activity_horizontal_margin">16dp</dimen> | ||||
|     <dimen name="activity_vertical_margin">16dp</dimen> | ||||
| </resources> | ||||
|  | @ -1,80 +0,0 @@ | |||
| <resources> | ||||
|     <string name="app_name">FakeScannerApp</string> | ||||
|     <string name="button_txt">SCAN</string> | ||||
|     <string name="instructions">Please, press the button below to scan the vote receipt.</string> | ||||
|     <string name="title_activity_settings">Settings</string> | ||||
| 
 | ||||
|     <!-- Strings related to Settings --> | ||||
|     <string name="action_settings">Settings</string> | ||||
| 
 | ||||
| 
 | ||||
|     <!-- Example General settings --> | ||||
|     <string name="pref_header_general">General</string> | ||||
| 
 | ||||
|     <string name="pref_title_social_recommendations">Enable social recommendations</string> | ||||
|     <string name="pref_description_social_recommendations">Recommendations for people to contact based on your message | ||||
|         history | ||||
|     </string> | ||||
| 
 | ||||
|     <string name="pref_title_psc_url">Scanner Service URL</string> | ||||
|     <string name="pref_default_psc_url">http://192.168.70.10:8880/scan</string> | ||||
| 
 | ||||
|     <string name="pref_title_add_friends_to_messages">Add friends to messages</string> | ||||
|     <string-array name="pref_example_list_titles"> | ||||
|         <item>Always</item> | ||||
|         <item>When possible</item> | ||||
|         <item>Never</item> | ||||
|     </string-array> | ||||
|     <string-array name="pref_example_list_values"> | ||||
|         <item>1</item> | ||||
|         <item>0</item> | ||||
|         <item>-1</item> | ||||
|     </string-array> | ||||
| 
 | ||||
|     <!-- Example settings for Data & Sync --> | ||||
|     <string name="pref_header_data_sync">Data & 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> | ||||
|  | @ -1,11 +0,0 @@ | |||
| <resources> | ||||
| 
 | ||||
|     <!-- Base application theme. --> | ||||
|     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|     </style> | ||||
| 
 | ||||
| </resources> | ||||
|  | @ -1,28 +0,0 @@ | |||
| <PreferenceScreen xmlns:tools="http://schemas.android.com/tools" | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
| 
 | ||||
|     <!-- NOTE: EditTextPreference accepts EditText attributes. --> | ||||
|     <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. --> | ||||
|     <EditTextPreference | ||||
|             android:key="psc_url" | ||||
|             android:title="@string/pref_title_psc_url" | ||||
|             android:defaultValue="@string/pref_default_psc_url" | ||||
|             android:selectAllOnFocus="true" | ||||
|             android:singleLine="true" | ||||
|             android:maxLines="1" | ||||
|         android:inputType="textUri" /> | ||||
| 
 | ||||
|     <EditTextPreference | ||||
|         android:key="psc_nonce" | ||||
|         android:title="Nonce" | ||||
|         android:defaultValue="0" | ||||
|         android:selectAllOnFocus="true" | ||||
|         android:singleLine="true" | ||||
|         android:maxLines="1" | ||||
|         android:inputType="number"/> | ||||
| 
 | ||||
|     <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to | ||||
|          dismiss it. --> | ||||
|     <!-- NOTE: ListPreference's summary should be set to its value by the activity code. --> | ||||
| 
 | ||||
| </PreferenceScreen> | ||||
|  | @ -1,15 +0,0 @@ | |||
| package com.meerkat.laura.fakescannerapp; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import static org.junit.Assert.*; | ||||
| 
 | ||||
| /** | ||||
|  * To work on unit tests, switch the Test Artifact in the Build Variants view. | ||||
|  */ | ||||
| public class ExampleUnitTest { | ||||
|     @Test | ||||
|     public void addition_isCorrect() throws Exception { | ||||
|         assertEquals(4, 2 + 2); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								build.gradle
								
								
								
								
							
							
						
						
									
										52
									
								
								build.gradle
								
								
								
								
							|  | @ -1,56 +1,10 @@ | |||
| apply plugin: com.google.gradle.osdetector.OsDetectorPlugin | ||||
| 
 | ||||
| 
 | ||||
| subprojects { proj -> | ||||
|     proj.afterEvaluate { | ||||
|         // Used to generate initial maven-dir layout | ||||
|         task "create-dirs" { | ||||
|             description = "Create default maven directory structure" | ||||
|             doLast { | ||||
|                 sourceSets*.java.srcDirs*.each { it.mkdirs() } | ||||
|                 sourceSets*.resources.srcDirs*.each { it.mkdirs() } | ||||
|             } | ||||
|         task "create-dirs" { description = "Create default maven directory structure" } << { | ||||
|             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 { | ||||
|   id "us.kirchmeier.capsule" version "1.0.2" | ||||
|   id 'com.google.protobuf' version '0.8.1' | ||||
|   id "us.kirchmeier.capsule" version "1.0.1" | ||||
|   id 'com.google.protobuf' version '0.7.0' | ||||
| } | ||||
| 
 | ||||
| apply plugin: 'java' | ||||
|  | @ -25,11 +25,8 @@ ext { | |||
|          | ||||
|         // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) | ||||
|         // Should be set in ${HOME}/.gradle/gradle.properties | ||||
| 
 | ||||
|         // 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') : "" | ||||
|         nexusUser =  project.hasProperty('nexusUser') ? project.property('nexusUser') : "" | ||||
|         nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" | ||||
| } | ||||
| 
 | ||||
| description = "TODO: Add a description" | ||||
|  | @ -134,19 +131,17 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) { | |||
| 
 | ||||
|         destinationDir = buildDir | ||||
| 
 | ||||
|         def fatMain | ||||
|          | ||||
|         if (this.hasProperty('fatmain')) { | ||||
|             fatMain = fatmain  | ||||
|             appendix = "fat-${fatMain}" | ||||
|         } else { | ||||
|             fatMain = mainClassName | ||||
|             appendix = "fat" | ||||
|         } | ||||
|         def fatMain = hasProperty('fatmain') ? fatmain : mainClassName | ||||
| 
 | ||||
|         applicationClass fatMain | ||||
| 
 | ||||
|         def testJar = this.hasProperty('test') | ||||
|         def testJar = hasProperty('test') | ||||
| 
 | ||||
|         if (hasProperty('fatmain')) { | ||||
|             appendix = "fat-${fatMain}" | ||||
|         } else { | ||||
|             appendix = "fat" | ||||
|         } | ||||
| 
 | ||||
|         if (testJar) { | ||||
|             from sourceSets.test.output | ||||
|  | @ -160,6 +155,21 @@ if (project.hasProperty('mainClassName') && (mainClassName != null)) { | |||
|  *===================================*/ | ||||
| 
 | ||||
| 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 | ||||
|         mavenLocal() | ||||
| 
 | ||||
|  | @ -167,15 +177,13 @@ repositories { | |||
|         mavenCentral() | ||||
| } | ||||
| 
 | ||||
| task "info"  { | ||||
|     doLast { | ||||
| task "info" << { | ||||
|         println "Project: ${project.name}" | ||||
|         println "Description: ${project.description}" | ||||
| println "Description: ${project.description}" | ||||
|         println "--------------------------" | ||||
|         println "GroupId: $groupId" | ||||
|         println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" | ||||
|         println "" | ||||
|     } | ||||
| } | ||||
| info.description 'Print some information about project parameters' | ||||
| 
 | ||||
|  | @ -197,12 +205,12 @@ publishing { | |||
|     } | ||||
|     repositories { | ||||
|         maven { | ||||
|             url publishRepository | ||||
|             url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" | ||||
|             credentials { username  | ||||
|                 password  | ||||
| 
 | ||||
|                 username publishUser | ||||
|                 password publishPassword | ||||
|                 username nexusUser | ||||
|                 password nexusPassword  | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,25 +0,0 @@ | |||
| apply plugin: 'groovy' | ||||
| 
 | ||||
| description = 'A Gradle plugin that detects the OS name and architecture, providing a uniform\ | ||||
|  classifier to be used in the names of native artifacts.' | ||||
| group = 'com.google.gradle' | ||||
| 
 | ||||
| // The major and minor versions are aligned with the Maven plugin's. | ||||
| version = '1.5.0-SNAPSHOT' | ||||
| 
 | ||||
| dependencies { | ||||
|     compile gradleApi(), | ||||
|           localGroovy(), | ||||
|           'kr.motd.maven:os-maven-plugin:1.5.0.Final' | ||||
|     // Newer versions of gradle conflict with the default guava 10. dependency of the maven | ||||
|     // plugin. | ||||
|     compile("com.google.guava:guava:19.0") { | ||||
|         force = true | ||||
|     } | ||||
|     testCompile 'junit:junit:4.12' | ||||
| } | ||||
| 
 | ||||
| repositories { | ||||
|   mavenLocal() | ||||
|   mavenCentral() | ||||
| } | ||||
|  | @ -1 +0,0 @@ | |||
| ../gradlew | ||||
|  | @ -1,25 +0,0 @@ | |||
| /* | ||||
|  * Copyright 2015 Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *     Unless required by applicable law or agreed to in writing, software | ||||
|  *     distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *     See the License for the specific language governing permissions and | ||||
|  *     limitations under the License. | ||||
|  */ | ||||
| package com.google.gradle.osdetector | ||||
| 
 | ||||
| import org.gradle.api.Plugin | ||||
| import org.gradle.api.Project | ||||
| 
 | ||||
| class OsDetectorPlugin implements Plugin<Project> { | ||||
|   void apply(final Project project) { | ||||
|     project.extensions.create('osdetector', OsDetector) | ||||
|   } | ||||
| } | ||||
|  | @ -1,120 +0,0 @@ | |||
| /* | ||||
|  * Copyright 2015 Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  *     Unless required by applicable law or agreed to in writing, software | ||||
|  *     distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *     See the License for the specific language governing permissions and | ||||
|  *     limitations under the License. | ||||
|  */ | ||||
| package com.google.gradle.osdetector; | ||||
| 
 | ||||
| import kr.motd.maven.os.Detector; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Properties; | ||||
| 
 | ||||
| public class OsDetector { | ||||
|   private static final Logger logger = LoggerFactory.getLogger(OsDetector.class.getName()); | ||||
| 
 | ||||
|   private final List<String> classifierWithLikes = new ArrayList<String>(); | ||||
|   private Impl impl; | ||||
| 
 | ||||
|   public String getOs() { | ||||
|     return (String) getImpl().detectedProperties.get(Detector.DETECTED_NAME); | ||||
|   } | ||||
| 
 | ||||
|   public String getArch() { | ||||
|     return (String) getImpl().detectedProperties.get(Detector.DETECTED_ARCH); | ||||
|   } | ||||
| 
 | ||||
|   public String getClassifier() { | ||||
|     return (String) getImpl().detectedProperties.get(Detector.DETECTED_CLASSIFIER); | ||||
|   } | ||||
| 
 | ||||
|   public Release getRelease() { | ||||
|     Impl impl = getImpl(); | ||||
|     Object releaseId = impl.detectedProperties.get(Detector.DETECTED_RELEASE); | ||||
|     if (releaseId == null) { | ||||
|       return null; | ||||
|     } | ||||
|     return new Release(impl); | ||||
|   } | ||||
| 
 | ||||
|   public synchronized void setClassifierWithLikes(List<String> classifierWithLikes) { | ||||
|     if (impl != null) { | ||||
|       throw new IllegalStateException("classifierWithLikes must be set before osdetector is read."); | ||||
|     } | ||||
|     this.classifierWithLikes.clear(); | ||||
|     this.classifierWithLikes.addAll(classifierWithLikes); | ||||
|   } | ||||
| 
 | ||||
|   private synchronized Impl getImpl() { | ||||
|     if (impl == null) { | ||||
|       impl = new Impl(classifierWithLikes); | ||||
|     } | ||||
|     return impl; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Accessor to information about the current OS release. | ||||
|    */ | ||||
|   public static class Release { | ||||
|     private final Impl impl; | ||||
| 
 | ||||
|     private Release(Impl impl) { | ||||
|       this.impl = impl; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the release ID. | ||||
|      */ | ||||
|     public String getId() { | ||||
|       return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the version ID. | ||||
|      */ | ||||
|     public String getVersion() { | ||||
|       return (String) impl.detectedProperties.get(Detector.DETECTED_RELEASE_VERSION); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns {@code true} if this release is a variant of the given base release (for example, | ||||
|      * ubuntu is "like" debian). | ||||
|      */ | ||||
|     public boolean isLike(String baseRelease) { | ||||
|       return impl.detectedProperties.containsKey( | ||||
|           Detector.DETECTED_RELEASE_LIKE_PREFIX + baseRelease); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static class Impl extends Detector { | ||||
|     final Properties detectedProperties = System.getProperties(); | ||||
| 
 | ||||
|     @Override | ||||
|     protected void log(String message) { | ||||
|       logger.info(message); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void logProperty(String name, String value) { | ||||
|       logger.info(name + "=" + value); | ||||
|     } | ||||
| 
 | ||||
|     Impl(List<String> classifierWithLikes) { | ||||
|       detect(detectedProperties, classifierWithLikes); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1 +0,0 @@ | |||
| implementation-class=com.google.gradle.osdetector.OsDetectorPlugin | ||||
|  | @ -1,62 +0,0 @@ | |||
| /* | ||||
|  * Copyright 2015 Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  *     Unless required by applicable law or agreed to in writing, software | ||||
|  *     distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  *     See the License for the specific language governing permissions and | ||||
|  *     limitations under the License. | ||||
|  */ | ||||
| package com.google.gradle.osdetector | ||||
| 
 | ||||
| import org.gradle.testfixtures.ProjectBuilder | ||||
| import org.gradle.api.Project | ||||
| import org.junit.Test | ||||
| import static org.junit.Assert.assertEquals | ||||
| import static org.junit.Assert.assertNotNull | ||||
| import static org.junit.Assert.fail | ||||
| 
 | ||||
| class OsDetectorPluginTest { | ||||
|   @Test | ||||
|   public void pluginAddsExtensionToProject() { | ||||
|     Project project = ProjectBuilder.builder().build() | ||||
|     project.apply plugin: 'com.google.osdetector' | ||||
|     assertNotNull(project.osdetector) | ||||
|     assertNotNull(project.osdetector.os) | ||||
|     assertNotNull(project.osdetector.arch) | ||||
|     assertEquals(project.osdetector.os + '-' + project.osdetector.arch, | ||||
|         project.osdetector.classifier) | ||||
|     System.err.println('classifier=' + project.osdetector.classifier) | ||||
|     if (project.osdetector.os == 'linux') { | ||||
|       assertNotNull(project.osdetector.release.id) | ||||
|       assertNotNull(project.osdetector.release.version) | ||||
|       System.err.println('release.id=' + project.osdetector.release.id) | ||||
|       System.err.println('release.version=' + project.osdetector.release.version) | ||||
|       System.err.println('release.isLike(debian)=' + project.osdetector.release.isLike('debian')) | ||||
|       System.err.println('release.isLike(redhat)=' + project.osdetector.release.isLike('redhat')) | ||||
|     } else if (project.osdetector.release) { | ||||
|       fail("Should be null") | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   public void setClassifierWithLikes() { | ||||
|     Project project = ProjectBuilder.builder().build() | ||||
|     project.apply plugin: 'com.google.osdetector' | ||||
|     project.osdetector.classifierWithLikes = ['debian', 'fedora'] | ||||
|     assertNotNull(project.osdetector.os) | ||||
|     assertNotNull(project.osdetector.arch) | ||||
|     System.err.println('classifier=' + project.osdetector.classifier) | ||||
|     try { | ||||
|       project.osdetector.classifierWithLikes = ['debian'] | ||||
|       fail("Should throw IllegalStateException") | ||||
|     } catch (IllegalStateException expected) { | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,3 +0,0 @@ | |||
| Manifest-Version: 1.0 | ||||
| Main-Class: meerkat.voting.gui.configuration.Converter | ||||
| 
 | ||||
|  | @ -1,214 +0,0 @@ | |||
| 
 | ||||
| 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 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  | @ -1 +0,0 @@ | |||
| ../gradlew | ||||
|  | @ -1,7 +1,7 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import meerkat.protobuf.BulletinBoardApi.BatchChunk; | ||||
| import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; | ||||
| import com.google.protobuf.ByteString; | ||||
| import meerkat.protobuf.BulletinBoardAPI.BatchData; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
|  | @ -11,13 +11,15 @@ import java.util.List; | |||
|  */ | ||||
| public class BatchDataContainer { | ||||
| 
 | ||||
|     public final MultiServerBatchIdentifier batchId; | ||||
|     public final List<BatchChunk> batchChunkList; | ||||
|     public final byte[] signerId; | ||||
|     public final int batchId; | ||||
|     public final List<BatchData> batchDataList; | ||||
|     public final int startPosition; | ||||
| 
 | ||||
|     public BatchDataContainer(MultiServerBatchIdentifier batchId, List<BatchChunk> batchChunkList, int startPosition) { | ||||
|     public BatchDataContainer(byte[] signerId, int batchId, List<BatchData> batchDataList, int startPosition) { | ||||
|         this.signerId = signerId; | ||||
|         this.batchId = batchId; | ||||
|         this.batchChunkList = batchChunkList; | ||||
|         this.batchDataList = batchDataList; | ||||
|         this.startPosition = startPosition; | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,490 +0,0 @@ | |||
| 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)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,29 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 17-Jun-16. | ||||
|  */ | ||||
| public final class CachedClientBatchIdentifier implements BatchIdentifier { | ||||
| 
 | ||||
|     // Per-server identifiers
 | ||||
|     private final BatchIdentifier localIdentifier; | ||||
|     private final BatchIdentifier remoteIdentifier; | ||||
| 
 | ||||
|     public CachedClientBatchIdentifier(BatchIdentifier localIdentifier, BatchIdentifier remoteIdentifier) { | ||||
|         this.localIdentifier = localIdentifier; | ||||
|         this.remoteIdentifier = remoteIdentifier; | ||||
|     } | ||||
| 
 | ||||
|     public BatchIdentifier getLocalIdentifier() { | ||||
|         return localIdentifier; | ||||
|     } | ||||
| 
 | ||||
|     public BatchIdentifier getRemoteIdentifier() { | ||||
|         return remoteIdentifier; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,689 +0,0 @@ | |||
| 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) {} | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,27 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 17-Jun-16. | ||||
|  */ | ||||
| public final class MultiServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier { | ||||
| 
 | ||||
|     // Per-server identifiers
 | ||||
|     private final Iterable<BatchIdentifier> identifiers; | ||||
| 
 | ||||
|     public MultiServerBatchIdentifier(Iterable<BatchIdentifier> identifiers) { | ||||
|         this.identifiers = identifiers; | ||||
|     } | ||||
| 
 | ||||
|     public MultiServerBatchIdentifier(BatchIdentifier[] identifiers) { | ||||
|         this.identifiers = Arrays.asList(identifiers); | ||||
|     } | ||||
| 
 | ||||
|     public Iterable<BatchIdentifier> getIdentifiers() { | ||||
|         return identifiers; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicInteger; | |||
|  */ | ||||
| public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN> implements Runnable, FutureCallback<OUT>{ | ||||
| 
 | ||||
|     protected final List<SingleServerBulletinBoardClient> clients; | ||||
|     private final List<SingleServerBulletinBoardClient> clients; | ||||
| 
 | ||||
|     protected AtomicInteger minServers; // The minimal number of servers the job must be successful on for the job to be completed
 | ||||
| 
 | ||||
|  | @ -74,8 +74,7 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN | |||
|      */ | ||||
|     protected void succeed(OUT result){ | ||||
|         if (returnedResult.compareAndSet(false, true)) { | ||||
|             if (futureCallback != null) | ||||
|                 futureCallback.onSuccess(result); | ||||
|             futureCallback.onSuccess(result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -86,11 +85,18 @@ public abstract class MultiServerWorker<IN, OUT> extends BulletinClientWorker<IN | |||
|      */ | ||||
|     protected void fail(Throwable t){ | ||||
|         if (returnedResult.compareAndSet(false, true)) { | ||||
|             if (futureCallback != null) | ||||
|                 futureCallback.onFailure(t); | ||||
|             futureCallback.onFailure(t); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Used by implementations to get a Single Server Client iterator | ||||
|      * @return the requested iterator | ||||
|      */ | ||||
|     protected Iterator<SingleServerBulletinBoardClient> getClientIterator() { | ||||
|         return clients.iterator(); | ||||
|     } | ||||
| 
 | ||||
|     protected int getClientNumber() { | ||||
|         return clients.size(); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,16 +1,12 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.protobuf.BoolValue; | ||||
| import com.google.protobuf.ByteString; | ||||
| import com.google.protobuf.Int64Value; | ||||
| import meerkat.bulletinboard.workers.singleserver.*; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.crypto.Digest; | ||||
| import meerkat.crypto.concrete.SHA256Digest; | ||||
| import meerkat.protobuf.BulletinBoardApi; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| import meerkat.protobuf.Voting.*; | ||||
| import meerkat.rest.*; | ||||
| import meerkat.util.BulletinBoardUtils; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
|  | @ -32,7 +28,7 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ | |||
| 
 | ||||
|     protected Client client; | ||||
| 
 | ||||
|     protected BulletinBoardDigest digest; | ||||
|     protected Digest digest; | ||||
| 
 | ||||
|     /** | ||||
|      * Stores database locations and initializes the web Client | ||||
|  | @ -47,8 +43,7 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ | |||
|         client.register(ProtobufMessageBodyReader.class); | ||||
|         client.register(ProtobufMessageBodyWriter.class); | ||||
| 
 | ||||
|         // Wrap the Digest into a BatchDigest
 | ||||
|         digest = new GenericBulletinBoardDigest(new SHA256Digest()); | ||||
|         digest = new SHA256Digest(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | @ -63,16 +58,19 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ | |||
|     public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { | ||||
| 
 | ||||
|         WebTarget webTarget; | ||||
|         Response response = null; | ||||
|         Response response; | ||||
| 
 | ||||
|         // Post message to all databases
 | ||||
|         try { | ||||
|             for (String db : meerkatDBs) { | ||||
|                 webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(POST_MESSAGE_PATH); | ||||
|                 response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msg, Constants.MEDIATYPE_PROTOBUF)); | ||||
| 
 | ||||
|                 SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(db, msg, 0); | ||||
| 
 | ||||
|                 worker.call(); | ||||
| 
 | ||||
|                 // Only consider valid responses
 | ||||
|                 if (response.getStatusInfo() == Response.Status.OK | ||||
|                         || response.getStatusInfo() == Response.Status.CREATED) { | ||||
|                     response.readEntity(BoolMsg.class).getValue(); | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { // Occurs only when server replies with valid status but invalid data
 | ||||
|             throw new CommunicationException("Error accessing database: " + e.getMessage()); | ||||
|  | @ -130,196 +128,32 @@ public class SimpleBulletinBoardClient implements BulletinBoardClient{ | |||
|      * @return the list of Bulletin Board messages that are returned from a server | ||||
|      */ | ||||
|     @Override | ||||
|     public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException{ | ||||
|     public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) { | ||||
|         WebTarget webTarget; | ||||
|         Response response; | ||||
|         BulletinBoardMessageList messageList; | ||||
| 
 | ||||
|         // Replace null filter list with blank one.
 | ||||
|         if (filterList == null){ | ||||
|             filterList = MessageFilterList.getDefaultInstance(); | ||||
|             filterList = MessageFilterList.newBuilder().build(); | ||||
|         } | ||||
| 
 | ||||
|         String exceptionString = ""; | ||||
| 
 | ||||
|         for (String db : meerkatDBs) { | ||||
| 
 | ||||
|             try { | ||||
|                 webTarget = client.target(db).path(BULLETIN_BOARD_SERVER_PATH).path(READ_MESSAGES_PATH); | ||||
| 
 | ||||
|                 SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(db, filterList, 0); | ||||
|                 response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(filterList, Constants.MEDIATYPE_PROTOBUF)); | ||||
| 
 | ||||
|                 List<BulletinBoardMessage> result = worker.call(); | ||||
| 
 | ||||
|                 return result; | ||||
| 
 | ||||
|             } 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 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++; | ||||
|                 messageList = response.readEntity(BulletinBoardMessageList.class); | ||||
| 
 | ||||
|                 if (messageList != null){ | ||||
|                     return messageList.getMessageList(); | ||||
|                 } | ||||
| 
 | ||||
|                 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"); | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public void close() { | ||||
|  |  | |||
|  | @ -1,241 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import com.google.protobuf.ByteString; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.util.BulletinBoardUtils; | ||||
| 
 | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Semaphore; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel on 13/04/2016. | ||||
|  * Simple, straightforward implementation of the {@link BulletinBoardSynchronizer} interface | ||||
|  */ | ||||
| public class SimpleBulletinBoardSynchronizer implements BulletinBoardSynchronizer { | ||||
| 
 | ||||
|     private DeletableSubscriptionBulletinBoardClient localClient; | ||||
|     private AsyncBulletinBoardClient remoteClient; | ||||
| 
 | ||||
|     private AtomicBoolean running; | ||||
|     private volatile SyncStatus syncStatus; | ||||
| 
 | ||||
|     private List<FutureCallback<Integer>> messageCountCallbacks; | ||||
|     private List<FutureCallback<SyncStatus>> syncStatusCallbacks; | ||||
| 
 | ||||
|     private static final MessageFilterList EMPTY_FILTER = MessageFilterList.getDefaultInstance(); | ||||
|     private static final int DEFAULT_SLEEP_INTERVAL = 10000; // 10 Seconds
 | ||||
|     private static final int DEFAULT_WAIT_CAP = 300000; // 5 minutes wait before deciding that the sync has failed fatally
 | ||||
| 
 | ||||
|     private final int SLEEP_INTERVAL; | ||||
|     private final int WAIT_CAP; | ||||
| 
 | ||||
|     private Semaphore semaphore; | ||||
| 
 | ||||
|     private class SyncCallback implements FutureCallback<List<BulletinBoardMessage>> { | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(List<BulletinBoardMessage> result) { | ||||
| 
 | ||||
|             // Notify Message Count callbacks if needed
 | ||||
| 
 | ||||
|             if (syncStatus != SyncStatus.SYNCHRONIZED || result.size() > 0) { | ||||
| 
 | ||||
|                 for (FutureCallback<Integer> callback : messageCountCallbacks){ | ||||
|                     callback.onSuccess(result.size()); | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             // Handle upload and status change
 | ||||
| 
 | ||||
|             SyncStatus newStatus = SyncStatus.PENDING; | ||||
| 
 | ||||
|             if (result.size() == 0) { | ||||
|                 newStatus = SyncStatus.SYNCHRONIZED; | ||||
|                 semaphore.release(); | ||||
|             } | ||||
| 
 | ||||
|             else{   // Upload messages
 | ||||
| 
 | ||||
|                 for (BulletinBoardMessage message : result){ | ||||
| 
 | ||||
|                     try { | ||||
| 
 | ||||
|                         if (message.getMsg().getDataTypeCase() == UnsignedBulletinBoardMessage.DataTypeCase.MSGID) { | ||||
| 
 | ||||
|                             // This is a batch message: need to upload batch data as well as the message itself
 | ||||
| 
 | ||||
|                             BulletinBoardMessage completeMsg = localClient.readBatchData(message); | ||||
| 
 | ||||
|                             remoteClient.postMessage(completeMsg); | ||||
| 
 | ||||
|                             localClient.deleteMessage(completeMsg.getEntryNum()); | ||||
| 
 | ||||
| 
 | ||||
|                         } else { | ||||
| 
 | ||||
|                             // This is a regular message: post it
 | ||||
|                             remoteClient.postMessage(message); | ||||
| 
 | ||||
|                             localClient.deleteMessage(message.getEntryNum()); | ||||
| 
 | ||||
|                         } | ||||
| 
 | ||||
|                     } catch (CommunicationException e) { | ||||
|                         // This is an error with the local server
 | ||||
|                         // TODO: log
 | ||||
|                         updateSyncStatus(SyncStatus.SERVER_ERROR); | ||||
|                     } | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|             updateSyncStatus(newStatus); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
| 
 | ||||
|             updateSyncStatus(SyncStatus.SERVER_ERROR); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public SimpleBulletinBoardSynchronizer(int sleepInterval, int waitCap) { | ||||
|         this.syncStatus = SyncStatus.STOPPED; | ||||
|         this.SLEEP_INTERVAL = sleepInterval; | ||||
|         this.WAIT_CAP = waitCap; | ||||
|         this.running = new AtomicBoolean(false); | ||||
|     } | ||||
| 
 | ||||
|     public SimpleBulletinBoardSynchronizer() { | ||||
|         this(DEFAULT_SLEEP_INTERVAL, DEFAULT_WAIT_CAP); | ||||
|     } | ||||
| 
 | ||||
|     private synchronized void updateSyncStatus(SyncStatus newStatus) { | ||||
| 
 | ||||
|         if (!running.get()) { | ||||
| 
 | ||||
|             newStatus = SyncStatus.STOPPED; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         if (newStatus != syncStatus){ | ||||
| 
 | ||||
|             syncStatus = newStatus; | ||||
| 
 | ||||
|             for (FutureCallback<SyncStatus> callback : syncStatusCallbacks){ | ||||
|                 if (callback != null) | ||||
|                     callback.onSuccess(syncStatus); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void init(DeletableSubscriptionBulletinBoardClient localClient, AsyncBulletinBoardClient remoteClient) { | ||||
| 
 | ||||
|         updateSyncStatus(SyncStatus.STOPPED); | ||||
| 
 | ||||
|         this.localClient = localClient; | ||||
|         this.remoteClient = remoteClient; | ||||
| 
 | ||||
|         messageCountCallbacks = new LinkedList<>(); | ||||
|         syncStatusCallbacks = new LinkedList<>(); | ||||
| 
 | ||||
|         semaphore = new Semaphore(0); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public SyncStatus getSyncStatus() { | ||||
|         return syncStatus; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void subscribeToSyncStatus(FutureCallback<SyncStatus> callback) { | ||||
|         syncStatusCallbacks.add(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<BulletinBoardMessage> getRemainingMessages()  throws CommunicationException{ | ||||
|         return localClient.readMessages(EMPTY_FILTER); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void getRemainingMessages(FutureCallback<List<BulletinBoardMessage>> callback) { | ||||
|         localClient.readMessages(EMPTY_FILTER, callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public long getRemainingMessagesCount() throws CommunicationException { | ||||
|         return localClient.readMessages(EMPTY_FILTER).size(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void subscribeToRemainingMessagesCount(FutureCallback<Integer> callback) { | ||||
|         messageCountCallbacks.add(callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void run() { | ||||
| 
 | ||||
|         if (running.compareAndSet(false,true)){ | ||||
| 
 | ||||
|             updateSyncStatus(SyncStatus.PENDING); | ||||
|             SyncCallback callback = new SyncCallback(); | ||||
| 
 | ||||
|             while (syncStatus != SyncStatus.STOPPED) { | ||||
| 
 | ||||
|                 do { | ||||
| 
 | ||||
|                     localClient.readMessages(EMPTY_FILTER, callback); | ||||
| 
 | ||||
|                     try { | ||||
| 
 | ||||
|                         semaphore.tryAcquire(WAIT_CAP, TimeUnit.MILLISECONDS); | ||||
|                         //TODO: log hard error. Too much time trying to upload data.
 | ||||
| 
 | ||||
|                     } catch (InterruptedException ignored) { | ||||
|                         // We expect an interruption when the upload will complete
 | ||||
|                     } | ||||
| 
 | ||||
|                 } while (syncStatus == SyncStatus.PENDING); | ||||
| 
 | ||||
|                 // Database is synced. Wait for new data.
 | ||||
| 
 | ||||
|                 try { | ||||
|                     semaphore.tryAcquire(SLEEP_INTERVAL, TimeUnit.MILLISECONDS); | ||||
|                 } catch (InterruptedException e) { | ||||
|                     //TODO: log (probably nudged)
 | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void nudge() { | ||||
|         semaphore.release(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void stop() { | ||||
| 
 | ||||
|         running.set(false); | ||||
|         updateSyncStatus(SyncStatus.STOPPED); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,42 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.protobuf.Int64Value; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 16-Jun-16. | ||||
|  * Single-server implementation of the BatchIdentifier interface | ||||
|  */ | ||||
| final class SingleServerBatchIdentifier implements AsyncBulletinBoardClient.BatchIdentifier { | ||||
| 
 | ||||
|     private final Int64Value batchId; | ||||
| 
 | ||||
|     private int length; | ||||
| 
 | ||||
|     public SingleServerBatchIdentifier(Int64Value batchId) { | ||||
|         this.batchId = batchId; | ||||
|         length = 0; | ||||
|     } | ||||
| 
 | ||||
|     public SingleServerBatchIdentifier(long batchId) { | ||||
|         this(Int64Value.newBuilder().setValue(batchId).build()); | ||||
|     } | ||||
| 
 | ||||
|     public Int64Value getBatchId() { | ||||
|         return batchId; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Overrides the existing length with the new one only if the new length is longer | ||||
|      * @param newLength | ||||
|      */ | ||||
|     public void setLength(int newLength) { | ||||
|         if (newLength > length) { | ||||
|             length = newLength; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public int getLength() { | ||||
|         return length; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -4,19 +4,17 @@ 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 com.google.protobuf.ByteString; | ||||
| 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.BulletinBoardAPI.*; | ||||
| import meerkat.protobuf.Voting.BulletinBoardClientParams; | ||||
| import meerkat.util.BulletinBoardUtils; | ||||
| 
 | ||||
| import javax.ws.rs.client.Client; | ||||
| import java.lang.Iterable; | ||||
| import javax.ws.rs.NotFoundException; | ||||
| import java.util.Arrays; | ||||
| import java.util.Iterator; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Executors; | ||||
| import java.util.concurrent.TimeUnit; | ||||
|  | @ -31,23 +29,19 @@ import java.util.concurrent.atomic.AtomicInteger; | |||
|  * If the list of servers contains more than one server: the server actually used is the first one | ||||
|  * 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; | ||||
| public class SingleServerBulletinBoardClient extends SimpleBulletinBoardClient implements AsyncBulletinBoardClient { | ||||
| 
 | ||||
|     private final int MAX_RETRIES = 11; | ||||
| 
 | ||||
|     private final ListeningScheduledExecutorService executorService; | ||||
|     private ListeningScheduledExecutorService executorService; | ||||
| 
 | ||||
|     protected BatchDigest batchDigest; | ||||
| 
 | ||||
|     private long lastServerErrorTime; | ||||
| 
 | ||||
|     private final long FAIL_DELAY_IN_MILLISECONDS; | ||||
|     private final long failDelayInMilliseconds; | ||||
| 
 | ||||
|     private final long SUBSCRIPTION_INTERVAL_IN_MILLISECONDS; | ||||
|     private final long subscriptionIntervalInMilliseconds; | ||||
| 
 | ||||
|     /** | ||||
|      * Notify the client that a job has failed | ||||
|  | @ -60,43 +54,6 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private class SynchronousRetry<OUT> { | ||||
| 
 | ||||
|         private final SingleServerWorker<?,OUT> worker; | ||||
| 
 | ||||
|         private String thrown; | ||||
| 
 | ||||
|         public SynchronousRetry(SingleServerWorker<?,OUT> worker) { | ||||
|             this.worker = worker; | ||||
|             this.thrown = "Could not contact server. Errors follow:\n"; | ||||
|         } | ||||
| 
 | ||||
|         OUT run() throws CommunicationException { | ||||
| 
 | ||||
|             do { | ||||
| 
 | ||||
|                 try { | ||||
|                     return worker.call(); | ||||
|                 } catch (Exception e) { | ||||
|                     thrown += e.getCause() + " " + e.getMessage() + "\n"; | ||||
|                 } | ||||
| 
 | ||||
|                 try { | ||||
|                     Thread.sleep(FAIL_DELAY_IN_MILLISECONDS); | ||||
|                 } catch (InterruptedException e) { | ||||
|                     //TODO: log
 | ||||
|                 } | ||||
| 
 | ||||
|                 worker.decMaxRetry(); | ||||
| 
 | ||||
|             } while (worker.isRetry()); | ||||
| 
 | ||||
|             throw new CommunicationException(thrown); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This method adds a worker to the scheduled queue of the threadpool | ||||
|      * If the server is in an accessible state: the job is submitted for immediate handling | ||||
|  | @ -108,7 +65,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
| 
 | ||||
|         long timeSinceLastServerError = System.currentTimeMillis() - lastServerErrorTime; | ||||
| 
 | ||||
|         if (timeSinceLastServerError >= FAIL_DELAY_IN_MILLISECONDS) { | ||||
|         if (timeSinceLastServerError >= failDelayInMilliseconds) { | ||||
| 
 | ||||
|             // Schedule for immediate processing
 | ||||
|             Futures.addCallback(executorService.submit(worker), callback); | ||||
|  | @ -118,7 +75,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|             // Schedule for processing immediately following delay expiry
 | ||||
|             Futures.addCallback(executorService.schedule( | ||||
|                     worker, | ||||
|                     FAIL_DELAY_IN_MILLISECONDS - timeSinceLastServerError, | ||||
|                     failDelayInMilliseconds - timeSinceLastServerError, | ||||
|                     TimeUnit.MILLISECONDS), | ||||
|                     callback); | ||||
| 
 | ||||
|  | @ -141,8 +98,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(T result) { | ||||
|             if (futureCallback != null) | ||||
|                 futureCallback.onSuccess(result); | ||||
|             futureCallback.onSuccess(result); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|  | @ -160,8 +116,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|                 scheduleWorker(worker, this); | ||||
|             } else { | ||||
|                 // No more retries: notify caller about failure
 | ||||
|                 if (futureCallback != null) | ||||
|                     futureCallback.onFailure(t); | ||||
|                 futureCallback.onFailure(t); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|  | @ -173,14 +128,14 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|      * 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> { | ||||
|     class PostBatchDataListCallback implements FutureCallback<Boolean> { | ||||
| 
 | ||||
|         private final FutureCallback<Boolean> callback; | ||||
| 
 | ||||
|         private AtomicInteger batchDataRemaining; | ||||
|         private AtomicBoolean aggregatedResult; | ||||
| 
 | ||||
|         public PostBatchChunkListCallback(int batchDataLength, FutureCallback<Boolean> callback) { | ||||
|         public PostBatchDataListCallback(int batchDataLength, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|             this.callback = callback; | ||||
|             this.batchDataRemaining = new AtomicInteger(batchDataLength); | ||||
|  | @ -196,8 +151,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|             } | ||||
| 
 | ||||
|             if (batchDataRemaining.decrementAndGet() == 0){ | ||||
|                 if (callback != null) | ||||
|                     callback.onSuccess(this.aggregatedResult.get()); | ||||
|                 callback.onSuccess(this.aggregatedResult.get()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  | @ -205,80 +159,110 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|         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 | ||||
|      * This callback ties together the different parts of a CompleteBatch as they arrive from the server | ||||
|      * It assembles a CompleteBatch from the parts and sends it to the user if all parts arrived | ||||
|      * If any part fails to arrive: it invokes the onFailure method | ||||
|      */ | ||||
|     class CompleteMessageReadCallback implements FutureCallback<List<BulletinBoardMessage>>{ | ||||
|     class CompleteBatchReadCallback { | ||||
| 
 | ||||
|         private final FutureCallback<BulletinBoardMessage> callback; | ||||
|         private final FutureCallback<CompleteBatch> callback; | ||||
| 
 | ||||
|         public CompleteMessageReadCallback(FutureCallback<BulletinBoardMessage> callback) { | ||||
|         private List<BatchData> batchDataList; | ||||
|         private BulletinBoardMessage batchMessage; | ||||
| 
 | ||||
|         private AtomicInteger remainingQueries; | ||||
|         private AtomicBoolean failed; | ||||
| 
 | ||||
|         public CompleteBatchReadCallback(FutureCallback<CompleteBatch> callback) { | ||||
| 
 | ||||
|             this.callback = callback; | ||||
| 
 | ||||
|             remainingQueries = new AtomicInteger(2); | ||||
|             failed = new AtomicBoolean(false); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(List<BulletinBoardMessage> result) { | ||||
|             if (result.size() <= 0) { | ||||
|                 onFailure(new CommunicationException("Could not find required message on the server.")); | ||||
|             } else { | ||||
|         protected void combineAndReturn() { | ||||
| 
 | ||||
|                 BulletinBoardMessage msg = result.get(0); | ||||
|             final String[] prefixes = { | ||||
|                     BulletinBoardConstants.BATCH_ID_TAG_PREFIX, | ||||
|                     BulletinBoardConstants.BATCH_TAG}; | ||||
| 
 | ||||
|                 if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) { | ||||
|                     callback.onSuccess(msg); | ||||
|                 } else { | ||||
|             if (remainingQueries.decrementAndGet() == 0){ | ||||
| 
 | ||||
|                     // 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)); | ||||
|                 String batchIdStr = BulletinBoardUtils.findTagWithPrefix(batchMessage, BulletinBoardConstants.BATCH_ID_TAG_PREFIX); | ||||
| 
 | ||||
|                 if (batchIdStr == null){ | ||||
|                     callback.onFailure(new CommunicationException("Server returned invalid message with no Batch ID tag")); | ||||
|                 } | ||||
| 
 | ||||
|                 BeginBatchMessage beginBatchMessage = | ||||
|                         BeginBatchMessage.newBuilder() | ||||
|                                 .setSignerId(batchMessage.getSig(0).getSignerId()) | ||||
|                                 .setBatchId(Integer.parseInt(batchIdStr)) | ||||
|                                 .addAllTag(BulletinBoardUtils.removePrefixTags(batchMessage, Arrays.asList(prefixes))) | ||||
|                                 .build(); | ||||
|                 callback.onSuccess(new CompleteBatch(beginBatchMessage, batchDataList, batchMessage.getSig(0))); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         protected void fail(Throwable t) { | ||||
|             if (failed.compareAndSet(false, true)) { | ||||
|                 callback.onFailure(t); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
|             callback.onFailure(t); | ||||
|         /** | ||||
|          * @return a FutureCallback for the Batch Data List that ties to this object | ||||
|          */ | ||||
|         public FutureCallback<List<BatchData>> asBatchDataListFutureCallback() { | ||||
|             return new FutureCallback<List<BatchData>>() { | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onSuccess(List<BatchData> result) { | ||||
|                     batchDataList = result; | ||||
| 
 | ||||
|                     combineAndReturn(); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(Throwable t) { | ||||
|                     fail(t); | ||||
|                 } | ||||
| 
 | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * @return a FutureCallback for the Bulletin Board Message that ties to this object | ||||
|          */ | ||||
|         public FutureCallback<List<BulletinBoardMessage>> asBulletinBoardMessageListFutureCallback() { | ||||
|             return new FutureCallback<List<BulletinBoardMessage>>() { | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onSuccess(List<BulletinBoardMessage> result) { | ||||
|                     if (result.size() < 1){ | ||||
|                         onFailure(new IllegalArgumentException("Server returned empty message list")); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
|                     batchMessage = result.get(0); | ||||
| 
 | ||||
|                     combineAndReturn(); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(Throwable t) { | ||||
|                     fail(t); | ||||
|                 } | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  | @ -291,13 +275,13 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     class SubscriptionCallback implements FutureCallback<List<BulletinBoardMessage>> { | ||||
| 
 | ||||
|         private SingleServerReadMessagesWorker worker; | ||||
|         private final FutureCallback<List<BulletinBoardMessage>> callback; | ||||
|         private final MessageHandler messageHandler; | ||||
| 
 | ||||
|         private MessageFilterList.Builder filterBuilder; | ||||
| 
 | ||||
|         public SubscriptionCallback(SingleServerReadMessagesWorker worker, FutureCallback<List<BulletinBoardMessage>> callback) { | ||||
|         public SubscriptionCallback(SingleServerReadMessagesWorker worker, MessageHandler messageHandler) { | ||||
|             this.worker = worker; | ||||
|             this.callback = callback; | ||||
|             this.messageHandler = messageHandler; | ||||
|             filterBuilder = worker.getPayload().toBuilder(); | ||||
| 
 | ||||
|         } | ||||
|  | @ -306,31 +290,22 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|         public void onSuccess(List<BulletinBoardMessage> result) { | ||||
| 
 | ||||
|             // Report new messages to user
 | ||||
|             if (callback != null) | ||||
|                 callback.onSuccess(result); | ||||
|             messageHandler.handleNewMessages(result); | ||||
| 
 | ||||
|             // Update filter if needed
 | ||||
|             // Remove last filter from list (MIN_ENTRY one)
 | ||||
|             filterBuilder.removeFilter(filterBuilder.getFilterCount() - 1); | ||||
| 
 | ||||
|             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()); | ||||
| 
 | ||||
|             } | ||||
|             // 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); | ||||
|             worker = new SingleServerReadMessagesWorker(worker.serverAddress, filterBuilder.build(), 1); | ||||
| 
 | ||||
|             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); | ||||
|             // Schedule the worker
 | ||||
|             scheduleWorker(worker, this); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|  | @ -340,31 +315,20 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|             // Notify client about failure
 | ||||
|             fail(); | ||||
| 
 | ||||
|             // Notify caller about failure and terminate subscription
 | ||||
|             if (callback != null) | ||||
|                 callback.onFailure(t); | ||||
|             // Reschedule exact same task
 | ||||
|             scheduleWorker(worker, this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|         executorService = MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(threadPoolSize)); | ||||
| 
 | ||||
|         this.failDelayInMilliseconds = failDelayInMilliseconds; | ||||
|         this.subscriptionIntervalInMilliseconds = subscriptionIntervalInMilliseconds; | ||||
| 
 | ||||
|         // Set server error time to a time sufficiently in the past to make new jobs go through
 | ||||
|         lastServerErrorTime = System.currentTimeMillis() - failDelayInMilliseconds; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | @ -375,289 +339,79 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     @Override | ||||
|     public void init(BulletinBoardClientParams clientParams) { | ||||
| 
 | ||||
|         this.digest = new GenericBulletinBoardDigest(new SHA256Digest()); | ||||
|         // Perform usual setup
 | ||||
|         super.init(clientParams); | ||||
| 
 | ||||
|         // Wrap the Digest into a BatchDigest
 | ||||
|         batchDigest = new GenericBatchDigest(digest); | ||||
| 
 | ||||
|         // Remove all but first DB address
 | ||||
|         this.dbAddress = clientParams.getBulletinBoardAddress(0); | ||||
|         String dbAddress = meerkatDBs.get(0); | ||||
|         meerkatDBs = new LinkedList<>(); | ||||
|         meerkatDBs.add(dbAddress); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Synchronous methods
 | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageID postMessage(BulletinBoardMessage msg) throws CommunicationException { | ||||
| 
 | ||||
|         SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(dbAddress, msg, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<Boolean> retry = new SynchronousRetry<>(worker); | ||||
| 
 | ||||
|         retry.run(); | ||||
| 
 | ||||
|         digest.reset(); | ||||
|         digest.update(msg); | ||||
| 
 | ||||
|         return digest.digestAsMessageID(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public float getRedundancy(MessageID id) throws CommunicationException { | ||||
| 
 | ||||
|         SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<Float> retry = new SynchronousRetry<>(worker); | ||||
| 
 | ||||
|         return retry.run(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<BulletinBoardMessage> readMessages(MessageFilterList filterList) throws CommunicationException { | ||||
| 
 | ||||
|         SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(worker); | ||||
| 
 | ||||
|         return retry.run(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize) throws CommunicationException { | ||||
| 
 | ||||
|         // Begin the batch and obtain identifier
 | ||||
| 
 | ||||
|         BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder() | ||||
|                 .addAllTag(msg.getMsg().getTagList()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         SingleServerBeginBatchWorker beginBatchWorker = new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<Int64Value> beginRetry = new SynchronousRetry<>(beginBatchWorker); | ||||
| 
 | ||||
|         Int64Value identifier = beginRetry.run(); | ||||
| 
 | ||||
|         // Post data chunks
 | ||||
| 
 | ||||
|         List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize); | ||||
| 
 | ||||
|         BatchMessage.Builder builder = BatchMessage.newBuilder().setBatchId(identifier.getValue()); | ||||
| 
 | ||||
|         int position = 0; | ||||
| 
 | ||||
|         for (BatchChunk data : batchChunkList) { | ||||
| 
 | ||||
|             builder.setSerialNum(position).setData(data); | ||||
| 
 | ||||
|             SingleServerPostBatchWorker dataWorker = new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES); | ||||
| 
 | ||||
|             SynchronousRetry<Boolean> dataRetry = new SynchronousRetry<>(dataWorker); | ||||
| 
 | ||||
|             dataRetry.run(); | ||||
| 
 | ||||
|             // Increment position in batch
 | ||||
|             position++; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         // Close batch
 | ||||
| 
 | ||||
|         CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder() | ||||
|                 .setBatchId(identifier.getValue()) | ||||
|                 .addAllSig(msg.getSigList()) | ||||
|                 .setTimestamp(msg.getMsg().getTimestamp()) | ||||
|                 .setBatchLength(position) | ||||
|                 .build(); | ||||
| 
 | ||||
|         SingleServerCloseBatchWorker closeBatchWorker = new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<Boolean> retry = new SynchronousRetry<>(closeBatchWorker); | ||||
| 
 | ||||
|         retry.run(); | ||||
| 
 | ||||
|         // Calculate ID and return
 | ||||
| 
 | ||||
|         digest.reset(); | ||||
|         digest.update(msg); | ||||
| 
 | ||||
|         return digest.digestAsMessageID(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public BulletinBoardMessage readMessage(MessageID msgID) throws CommunicationException { | ||||
| 
 | ||||
|         // Retrieve message (which may be a stub)
 | ||||
| 
 | ||||
|         MessageFilterList filterList = MessageFilterList.newBuilder() | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.MSG_ID) | ||||
|                         .setId(msgID.getID()) | ||||
|                         .build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         SingleServerReadMessagesWorker stubWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<List<BulletinBoardMessage>> retry = new SynchronousRetry<>(stubWorker); | ||||
| 
 | ||||
|         List<BulletinBoardMessage> messages = retry.run(); | ||||
| 
 | ||||
|         if (messages.size() <= 0) { | ||||
|             throw new CommunicationException("Could not find message in database."); | ||||
|         } | ||||
| 
 | ||||
|         BulletinBoardMessage msg = messages.get(0); | ||||
| 
 | ||||
|         if (msg.getMsg().getDataTypeCase() != UnsignedBulletinBoardMessage.DataTypeCase.MSGID) { | ||||
| 
 | ||||
|             // We retrieved a complete message. Return it.
 | ||||
|             return msg; | ||||
| 
 | ||||
|         } else { | ||||
| 
 | ||||
|             // We retrieved a stub. Retrieve data.
 | ||||
|             return readBatchData(msg); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public BulletinBoardMessage readBatchData(BulletinBoardMessage stub) throws CommunicationException, IllegalArgumentException { | ||||
| 
 | ||||
|         BatchQuery batchQuery = BatchQuery.newBuilder() | ||||
|                 .setMsgID(MessageID.newBuilder() | ||||
|                         .setID(stub.getMsg().getMsgId()) | ||||
|                         .build()) | ||||
|                 .setStartPosition(0) | ||||
|                 .build(); | ||||
| 
 | ||||
|         SingleServerReadBatchWorker readBatchWorker = new SingleServerReadBatchWorker(dbAddress, batchQuery, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<List<BatchChunk>> batchRetry = new SynchronousRetry<>(readBatchWorker); | ||||
| 
 | ||||
|         List<BatchChunk> batchChunkList = batchRetry.run(); | ||||
| 
 | ||||
|         return BulletinBoardUtils.gatherBatch(stub, batchChunkList); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public SyncQuery generateSyncQuery(GenerateSyncQueryParams generateSyncQueryParams) throws CommunicationException { | ||||
| 
 | ||||
|         SingleServerGenerateSyncQueryWorker worker = | ||||
|                 new SingleServerGenerateSyncQueryWorker(dbAddress, generateSyncQueryParams, MAX_RETRIES); | ||||
| 
 | ||||
|         SynchronousRetry<SyncQuery> retry = new SynchronousRetry<>(worker); | ||||
| 
 | ||||
|         return retry.run(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Asynchronous methods
 | ||||
| 
 | ||||
|     @Override | ||||
|     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); | ||||
|         SingleServerPostMessageWorker worker = new SingleServerPostMessageWorker(meerkatDBs.get(0), 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(); | ||||
|         batchDigest.reset(); | ||||
|         batchDigest.update(msg.getMsg()); | ||||
|         return batchDigest.digestAsMessageID(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private class PostBatchDataCallback implements FutureCallback<Boolean> { | ||||
| 
 | ||||
|         private final BulletinBoardMessage msg; | ||||
|         private final BatchIdentifier identifier; | ||||
|         private final CompleteBatch completeBatch; | ||||
|         private final FutureCallback<Boolean> callback; | ||||
| 
 | ||||
|         public PostBatchDataCallback(BulletinBoardMessage msg, BatchIdentifier identifier, FutureCallback<Boolean> callback) { | ||||
|             this.msg = msg; | ||||
|             this.identifier = identifier; | ||||
|         public PostBatchDataCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { | ||||
|             this.completeBatch = completeBatch; | ||||
|             this.callback = callback; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(Boolean result) { | ||||
|         public void onSuccess(Boolean msg) { | ||||
|             closeBatch( | ||||
|                     identifier, | ||||
|                     msg.getMsg().getTimestamp(), | ||||
|                     msg.getSigList(), | ||||
|                     completeBatch.getCloseBatchMessage(), | ||||
|                     callback | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
|             if (callback != null) | ||||
|                 callback.onFailure(t); | ||||
|             callback.onFailure(t); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private class ContinueBatchCallback implements FutureCallback<BatchIdentifier> { | ||||
|     private class BeginBatchCallback implements FutureCallback<Boolean> { | ||||
| 
 | ||||
|         private final BulletinBoardMessage msg; | ||||
|         private final int chunkSize; | ||||
|         private final CompleteBatch completeBatch; | ||||
|         private final FutureCallback<Boolean> callback; | ||||
| 
 | ||||
|         public ContinueBatchCallback(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) { | ||||
|             this.msg = msg; | ||||
|             this.chunkSize = chunkSize; | ||||
|         public BeginBatchCallback(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { | ||||
|             this.completeBatch = completeBatch; | ||||
|             this.callback = callback; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(BatchIdentifier identifier) { | ||||
| 
 | ||||
|             List<BatchChunk> batchChunkList = BulletinBoardUtils.breakToBatch(msg, chunkSize); | ||||
|         public void onSuccess(Boolean msg) { | ||||
| 
 | ||||
|             postBatchData( | ||||
|                     identifier, | ||||
|                     batchChunkList, | ||||
|                     completeBatch.getBeginBatchMessage().getSignerId(), | ||||
|                     completeBatch.getBeginBatchMessage().getBatchId(), | ||||
|                     completeBatch.getBatchDataList(), | ||||
|                     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)); | ||||
|                     new PostBatchDataCallback(completeBatch,callback)); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|  | @ -667,53 +421,51 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) { | ||||
|     public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         BeginBatchMessage beginBatchMessage = BeginBatchMessage.newBuilder() | ||||
|                 .addAllTag(tags) | ||||
|                 .build(); | ||||
|         beginBatch( | ||||
|                 completeBatch.getBeginBatchMessage(), | ||||
|                 new BeginBatchCallback(completeBatch, callback) | ||||
|         ); | ||||
| 
 | ||||
|         // Create worker with redundancy 1 and MAX_RETRIES retries
 | ||||
|         SingleServerBeginBatchWorker worker = | ||||
|                 new SingleServerBeginBatchWorker(dbAddress, beginBatchMessage, MAX_RETRIES); | ||||
|         batchDigest.update(completeBatch); | ||||
| 
 | ||||
|         // Submit worker and create callback
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, new BeginBatchCallback(callback))); | ||||
|         return batchDigest.digestAsMessageID(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // Create worker with redundancy 1 and MAX_RETRIES retries
 | ||||
|         SingleServerBeginBatchWorker worker = | ||||
|                 new SingleServerBeginBatchWorker(meerkatDBs.get(0), beginBatchMessage, MAX_RETRIES); | ||||
| 
 | ||||
|         // Submit worker and create callback
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, callback)); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, | ||||
|                           int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException{ | ||||
|     public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, | ||||
|                           int startPosition, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // 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()); | ||||
|         BatchMessage.Builder builder = BatchMessage.newBuilder() | ||||
|                 .setSignerId(signerId) | ||||
|                 .setBatchId(batchId); | ||||
| 
 | ||||
|         // Create a unified callback to aggregate successful posts
 | ||||
| 
 | ||||
|         PostBatchChunkListCallback listCallback = new PostBatchChunkListCallback(batchChunkList.size(), callback); | ||||
|         PostBatchDataListCallback listCallback = new PostBatchDataListCallback(batchDataList.size(), callback); | ||||
| 
 | ||||
|         // Iterate through data list
 | ||||
| 
 | ||||
|         BatchMessage.Builder builder = BatchMessage.newBuilder() | ||||
|                 .setBatchId(identifier.getBatchId().getValue()); | ||||
| 
 | ||||
|         for (BatchChunk data : batchChunkList) { | ||||
|         for (BatchData data : batchDataList) { | ||||
|             builder.setSerialNum(startPosition).setData(data); | ||||
| 
 | ||||
|             // Create worker with redundancy 1 and MAX_RETRIES retries
 | ||||
|             SingleServerPostBatchWorker worker = | ||||
|                     new SingleServerPostBatchWorker(dbAddress, builder.build(), MAX_RETRIES); | ||||
|                     new SingleServerPostBatchWorker(meerkatDBs.get(0), builder.build(), MAX_RETRIES); | ||||
| 
 | ||||
|             // Create worker with redundancy 1 and MAX_RETRIES retries
 | ||||
|             scheduleWorker(worker, new RetryCallback<>(worker, listCallback)); | ||||
|  | @ -725,33 +477,33 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) | ||||
|         throws IllegalArgumentException { | ||||
|     public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         postBatchData(batchIdentifier, batchChunkList, 0, callback); | ||||
|         postBatchData(signerId, batchId, batchDataList, 0, callback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closeBatch(BatchIdentifier batchIdentifier, Timestamp timestamp, Iterable<Crypto.Signature> signatures, FutureCallback<Boolean> callback) | ||||
|             throws IllegalArgumentException { | ||||
|     public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, | ||||
|                               int startPosition, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         if (!(batchIdentifier instanceof SingleServerBatchIdentifier)){ | ||||
|             throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class."); | ||||
|         } | ||||
|         postBatchData(ByteString.copyFrom(signerId), batchId, batchDataList, startPosition, callback); | ||||
| 
 | ||||
|         SingleServerBatchIdentifier identifier = (SingleServerBatchIdentifier) batchIdentifier; | ||||
|     } | ||||
| 
 | ||||
|         CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder() | ||||
|                 .setBatchId(identifier.getBatchId().getValue()) | ||||
|                 .setBatchLength(identifier.getLength()) | ||||
|                 .setTimestamp(timestamp) | ||||
|                 .addAllSig(signatures) | ||||
|                 .build(); | ||||
|     @Override | ||||
|     public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         postBatchData(signerId, batchId, batchDataList, 0, callback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // Create worker with redundancy 1 and MAX_RETRIES retries
 | ||||
|         SingleServerCloseBatchWorker worker = | ||||
|                 new SingleServerCloseBatchWorker(dbAddress, closeBatchMessage, MAX_RETRIES); | ||||
|                 new SingleServerCloseBatchWorker(meerkatDBs.get(0), closeBatchMessage, MAX_RETRIES); | ||||
| 
 | ||||
|         // Submit worker and create callback
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, callback)); | ||||
|  | @ -762,7 +514,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     public void getRedundancy(MessageID id, FutureCallback<Float> callback) { | ||||
| 
 | ||||
|         // Create worker with no retries
 | ||||
|         SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(dbAddress, id, 1); | ||||
|         SingleServerGetRedundancyWorker worker = new SingleServerGetRedundancyWorker(meerkatDBs.get(0), id, 1); | ||||
| 
 | ||||
|         // Submit job and create callback
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, callback)); | ||||
|  | @ -773,7 +525,7 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     public void readMessages(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) { | ||||
| 
 | ||||
|         // Create job with no retries
 | ||||
|         SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterList, 1); | ||||
|         SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1); | ||||
| 
 | ||||
|         // Submit job and create callback
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, callback)); | ||||
|  | @ -781,62 +533,42 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void readMessage(MessageID msgID, FutureCallback<BulletinBoardMessage> callback) { | ||||
|     public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { | ||||
| 
 | ||||
|         // Create job with MAX retries for retrieval of the Bulletin Board Message (which may be a stub)
 | ||||
|         // Create job with no retries for retrieval of the Bulletin Board Message that defines the batch
 | ||||
| 
 | ||||
|         MessageFilterList filterList = MessageFilterList.newBuilder() | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.MSG_ID) | ||||
|                         .setId(msgID.getID()) | ||||
|                         .setType(FilterType.TAG) | ||||
|                         .setTag(BulletinBoardConstants.BATCH_TAG) | ||||
|                         .build()) | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.TAG) | ||||
|                         .setTag(BulletinBoardConstants.BATCH_ID_TAG_PREFIX + batchSpecificationMessage.getBatchId()) | ||||
|                         .build()) | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.SIGNER_ID) | ||||
|                         .setId(batchSpecificationMessage.getSignerId()) | ||||
|                         .build()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         BatchQuery batchQuery = BatchQuery.newBuilder() | ||||
|                 .setMsgID(msgID) | ||||
|                 .setStartPosition(0) | ||||
|                 .build(); | ||||
|         SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterList, 1); | ||||
| 
 | ||||
|         SingleServerReadMessagesWorker messageWorker = new SingleServerReadMessagesWorker(dbAddress, filterList, MAX_RETRIES); | ||||
|         // Create job with no retries for retrieval of the Batch Data List
 | ||||
|         SingleServerReadBatchWorker batchWorker = new SingleServerReadBatchWorker(meerkatDBs.get(0), batchSpecificationMessage, 1); | ||||
| 
 | ||||
|         // Create callback that will combine the two worker products
 | ||||
|         CompleteBatchReadCallback completeBatchReadCallback = new CompleteBatchReadCallback(callback); | ||||
| 
 | ||||
|         // Submit jobs with wrapped callbacks
 | ||||
|         scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, new CompleteMessageReadCallback(callback))); | ||||
|         scheduleWorker(messageWorker, new RetryCallback<>(messageWorker, completeBatchReadCallback.asBulletinBoardMessageListFutureCallback())); | ||||
|         scheduleWorker(batchWorker, new RetryCallback<>(batchWorker, completeBatchReadCallback.asBatchDataListFutureCallback())); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void readBatchData(BulletinBoardMessage stub, FutureCallback<BulletinBoardMessage> callback) throws IllegalArgumentException{ | ||||
|     public void subscribe(MessageFilterList filterList, MessageHandler messageHandler) { | ||||
| 
 | ||||
|         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(); | ||||
|  | @ -851,24 +583,22 @@ public class SingleServerBulletinBoardClient implements SubscriptionBulletinBoar | |||
|         } | ||||
|         filterListBuilder.addFilter(MessageFilter.newBuilder() | ||||
|                 .setType(FilterType.MIN_ENTRY) | ||||
|                 .setEntry(startEntry) | ||||
|                 .setEntry(0) | ||||
|                 .build()); | ||||
| 
 | ||||
|         // Create job with no retries
 | ||||
|         SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(dbAddress, filterListBuilder.build(), MAX_RETRIES); | ||||
|         SingleServerReadMessagesWorker worker = new SingleServerReadMessagesWorker(meerkatDBs.get(0), filterListBuilder.build(), 1); | ||||
| 
 | ||||
|         // Submit job and create callback that retries on failure and handles repeated subscription
 | ||||
|         scheduleWorker(worker, new RetryCallback<>(worker, new SubscriptionCallback(worker, callback))); | ||||
|     } | ||||
|         // Submit job and create callback
 | ||||
|         scheduleWorker(worker, new SubscriptionCallback(worker, messageHandler)); | ||||
| 
 | ||||
|     @Override | ||||
|     public void subscribe(MessageFilterList filterList, FutureCallback<List<BulletinBoardMessage>> callback) { | ||||
|         subscribe(filterList, 0, callback); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void close() { | ||||
| 
 | ||||
|         super.close(); | ||||
| 
 | ||||
|         executorService.shutdown(); | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import com.google.protobuf.ByteString; | ||||
| 
 | ||||
| import com.google.protobuf.Timestamp; | ||||
| import meerkat.bulletinboard.workers.multiserver.*; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.Crypto.Signature; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| import meerkat.protobuf.Voting.*; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
|  | @ -30,33 +30,18 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
|     // Per-server clients
 | ||||
|     private List<SingleServerBulletinBoardClient> clients; | ||||
| 
 | ||||
|     private BulletinBoardDigest batchDigest; | ||||
|     private BatchDigest batchDigest; | ||||
| 
 | ||||
|     private final static int POST_MESSAGE_RETRY_NUM = 3; | ||||
|     private final static int READ_MESSAGES_RETRY_NUM = 1; | ||||
|     private final static int 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 static final int SERVER_THREADPOOL_SIZE = 5; | ||||
|     private static final long FAIL_DELAY = 5000; | ||||
|     private static final long SUBSCRIPTION_INTERVAL = 10000; | ||||
| 
 | ||||
|     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 the required minimum redundancy. | ||||
|  | @ -68,9 +53,9 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
| 
 | ||||
|         super.init(clientParams); | ||||
| 
 | ||||
|         batchDigest = new GenericBulletinBoardDigest(digest); | ||||
|         batchDigest = new GenericBatchDigest(digest); | ||||
| 
 | ||||
|         minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * (float) clientParams.getBulletinBoardAddressCount()); | ||||
|         minAbsoluteRedundancy = (int) (clientParams.getMinRedundancy() * clientParams.getBulletinBoardAddressCount()); | ||||
| 
 | ||||
|         executorService = Executors.newFixedThreadPool(JOBS_THREAD_NUM); | ||||
| 
 | ||||
|  | @ -114,28 +99,28 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public MessageID postAsBatch(BulletinBoardMessage msg, int chunkSize, FutureCallback<Boolean> callback) { | ||||
|     public MessageID postBatch(CompleteBatch completeBatch, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // Create job
 | ||||
|         MultiServerPostBatchWorker worker = | ||||
|                 new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, msg, chunkSize, POST_MESSAGE_RETRY_NUM, callback); | ||||
|                 new MultiServerPostBatchWorker(clients, minAbsoluteRedundancy, completeBatch, POST_MESSAGE_RETRY_NUM, callback); | ||||
| 
 | ||||
|         // Submit job
 | ||||
|         executorService.submit(worker); | ||||
| 
 | ||||
|         // Calculate the correct message ID and return it
 | ||||
|         batchDigest.reset(); | ||||
|         batchDigest.update(msg); | ||||
|         batchDigest.update(completeBatch); | ||||
|         return batchDigest.digestAsMessageID(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void beginBatch(Iterable<String> tags, FutureCallback<BatchIdentifier> callback) { | ||||
|     public void beginBatch(BeginBatchMessage beginBatchMessage, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // Create job
 | ||||
|         MultiServerBeginBatchWorker worker = | ||||
|                 new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, tags, POST_MESSAGE_RETRY_NUM, callback); | ||||
|                 new MultiServerBeginBatchWorker(clients, minAbsoluteRedundancy, beginBatchMessage, POST_MESSAGE_RETRY_NUM, callback); | ||||
| 
 | ||||
|         // Submit job
 | ||||
|         executorService.submit(worker); | ||||
|  | @ -143,18 +128,10 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, | ||||
|                               int startPosition, FutureCallback<Boolean> callback) throws IllegalArgumentException { | ||||
|     public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, | ||||
|                               int startPosition, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // 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); | ||||
|         BatchDataContainer batchDataContainer = new BatchDataContainer(signerId, batchId, batchDataList, startPosition); | ||||
| 
 | ||||
|         // Create job
 | ||||
|         MultiServerPostBatchDataWorker worker = | ||||
|  | @ -166,26 +143,33 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postBatchData(BatchIdentifier batchIdentifier, List<BatchChunk> batchChunkList, FutureCallback<Boolean> callback) | ||||
|             throws IllegalArgumentException { | ||||
|     public void postBatchData(byte[] signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         postBatchData(batchIdentifier, batchChunkList, 0, callback); | ||||
|         postBatchData(signerId, batchId, batchDataList, 0, callback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closeBatch(BatchIdentifier payload, Timestamp timestamp, Iterable<Signature> signatures, FutureCallback<Boolean> callback) | ||||
|         throws IllegalArgumentException{ | ||||
|     public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, | ||||
|                               int startPosition, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         if (!(payload instanceof MultiServerBatchIdentifier)) { | ||||
|             throw new IllegalArgumentException("Error: batch identifier supplied was not created by this class."); | ||||
|         } | ||||
|         postBatchData(signerId.toByteArray(), batchId, batchDataList, startPosition, callback); | ||||
| 
 | ||||
|         MultiServerBatchIdentifier identifier = (MultiServerBatchIdentifier) payload; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void postBatchData(ByteString signerId, int batchId, List<BatchData> batchDataList, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         postBatchData(signerId, batchId, batchDataList, 0, callback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void closeBatch(CloseBatchMessage closeBatchMessage, FutureCallback<Boolean> callback) { | ||||
| 
 | ||||
|         // Create job
 | ||||
|         MultiServerCloseBatchWorker worker = | ||||
|                 new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, identifier, timestamp, signatures, POST_MESSAGE_RETRY_NUM, callback); | ||||
|                 new MultiServerCloseBatchWorker(clients, minAbsoluteRedundancy, closeBatchMessage, POST_MESSAGE_RETRY_NUM, callback); | ||||
| 
 | ||||
|         // Submit job
 | ||||
|         executorService.submit(worker); | ||||
|  | @ -228,40 +212,20 @@ public class ThreadedBulletinBoardClient extends SimpleBulletinBoardClient imple | |||
|     } | ||||
| 
 | ||||
|     @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"); | ||||
|         } | ||||
|     public void readBatch(BatchSpecificationMessage batchSpecificationMessage, FutureCallback<CompleteBatch> callback) { | ||||
| 
 | ||||
|         // Create job
 | ||||
|         MultiServerReadBatchDataWorker worker = | ||||
|                 new MultiServerReadBatchDataWorker(clients, minAbsoluteRedundancy, stub, READ_MESSAGES_RETRY_NUM, callback); | ||||
|         MultiServerReadBatchWorker worker = | ||||
|                 new MultiServerReadBatchWorker(clients, minAbsoluteRedundancy, batchSpecificationMessage, 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")); | ||||
|     public void subscribe(MessageFilterList filterList, MessageHandler messageHandler) { | ||||
|         // TODO: Implement
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  |  | |||
|  | @ -1,276 +0,0 @@ | |||
| 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,96 +1,28 @@ | |||
| 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 meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage; | ||||
| 
 | ||||
| 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 class MultiServerBeginBatchWorker extends MultiServerGenericPostWorker<BeginBatchMessage> { | ||||
| 
 | ||||
|     public MultiServerBeginBatchWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                        int minServers, Iterable<String> payload, int maxRetry, | ||||
|                                        FutureCallback<BatchIdentifier> futureCallback) { | ||||
|                                        int minServers, BeginBatchMessage payload, int maxRetry, | ||||
|                                        FutureCallback<Boolean> 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); | ||||
|     protected void doPost(SingleServerBulletinBoardClient client, BeginBatchMessage payload) { | ||||
|         client.beginBatch(payload, this); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onFailure(Throwable t) { | ||||
|         fail(t); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void run() { | ||||
| 
 | ||||
|         int clientNum = 0; | ||||
| 
 | ||||
|         for (SingleServerBulletinBoardClient client : clients){ | ||||
| 
 | ||||
|             client.beginBatch(payload, new BeginBatchCallback(clientNum)); | ||||
| 
 | ||||
|             clientNum++; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,83 +1,27 @@ | |||
| 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 meerkat.protobuf.BulletinBoardAPI.CloseBatchMessage; | ||||
| 
 | ||||
| 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 class MultiServerCloseBatchWorker extends MultiServerGenericPostWorker<CloseBatchMessage> { | ||||
| 
 | ||||
|     public MultiServerCloseBatchWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                        int minServers, MultiServerBatchIdentifier payload, Timestamp timestamp, Iterable<Crypto.Signature> signatures, | ||||
|                                        int maxRetry, FutureCallback<Boolean> futureCallback) { | ||||
|                                        int minServers, CloseBatchMessage payload, 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); | ||||
|         } | ||||
|     protected void doPost(SingleServerBulletinBoardClient client, CloseBatchMessage payload) { | ||||
|         client.closeBatch(payload, this); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,9 +35,14 @@ public abstract class MultiServerGenericPostWorker<T> extends MultiServerWorker< | |||
|     public void run() { | ||||
| 
 | ||||
|         // Iterate through servers
 | ||||
|         for (SingleServerBulletinBoardClient client : clients) { | ||||
| 
 | ||||
|         Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator(); | ||||
| 
 | ||||
|         while (clientIterator.hasNext()) { | ||||
| 
 | ||||
|             // Send request to Server
 | ||||
|             SingleServerBulletinBoardClient client = clientIterator.next(); | ||||
| 
 | ||||
|             doPost(client, payload); | ||||
| 
 | ||||
|         } | ||||
|  |  | |||
|  | @ -14,9 +14,7 @@ import java.util.List; | |||
|  */ | ||||
| public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerWorker<IN, OUT>{ | ||||
| 
 | ||||
|     private Iterator<SingleServerBulletinBoardClient> clientIterator; | ||||
| 
 | ||||
|     private String errorString; | ||||
|     private final Iterator<SingleServerBulletinBoardClient> clientIterator; | ||||
| 
 | ||||
|     public MultiServerGenericReadWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                         int minServers, IN payload, int maxRetry, | ||||
|  | @ -24,8 +22,7 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW | |||
| 
 | ||||
|         super(clients, true, minServers, payload, maxRetry, futureCallback); // Shuffle clients on creation to balance load
 | ||||
| 
 | ||||
|         clientIterator = clients.iterator(); | ||||
|         errorString = ""; | ||||
|         clientIterator = getClientIterator(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | @ -49,7 +46,7 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW | |||
|             doRead(payload, client); | ||||
| 
 | ||||
|         } else { | ||||
|             fail(new CommunicationException("Could not contact any server. Errors follow:\n" + errorString)); | ||||
|             fail(new CommunicationException("Could not contact any server")); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  | @ -61,8 +58,6 @@ public abstract class MultiServerGenericReadWorker<IN, OUT> extends MultiServerW | |||
| 
 | ||||
|     @Override | ||||
|     public void onFailure(Throwable t) { | ||||
|         //TODO: log
 | ||||
|         errorString += t.getCause() + " " + t.getMessage() + "\n"; | ||||
|         run(); // Retry with next server
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import com.google.common.util.concurrent.FutureCallback; | |||
| import meerkat.bulletinboard.MultiServerWorker; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| 
 | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
|  | @ -36,8 +36,13 @@ public class MultiServerGetRedundancyWorker extends MultiServerWorker<MessageID, | |||
|      */ | ||||
|     public void run(){ | ||||
| 
 | ||||
|         Iterator<SingleServerBulletinBoardClient> clientIterator = getClientIterator(); | ||||
| 
 | ||||
|         // Iterate through clients
 | ||||
|         for (SingleServerBulletinBoardClient client : clients) { | ||||
| 
 | ||||
|         while (clientIterator.hasNext()) { | ||||
| 
 | ||||
|             SingleServerBulletinBoardClient client = clientIterator.next(); | ||||
| 
 | ||||
|             // Send request to client
 | ||||
|             client.getRedundancy(payload,this); | ||||
|  |  | |||
|  | @ -1,18 +1,15 @@ | |||
| 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 class MultiServerPostBatchDataWorker extends MultiServerGenericPostWorker<BatchDataContainer> { | ||||
| 
 | ||||
|     public MultiServerPostBatchDataWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                           int minServers, BatchDataContainer payload, int maxRetry, | ||||
|  | @ -23,50 +20,9 @@ public class MultiServerPostBatchDataWorker extends MultiServerWorker<BatchDataC | |||
|     } | ||||
| 
 | ||||
|     @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(); | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     protected void doPost(SingleServerBulletinBoardClient client, BatchDataContainer payload) { | ||||
|         client.postBatchData(payload.signerId, payload.batchId, payload.batchDataList, payload.startPosition, this); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onSuccess(Boolean result) { | ||||
|         if (minServers.decrementAndGet() <= 0){ | ||||
|             succeed(result); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onFailure(Throwable t) { | ||||
|         if (maxFailedServers.decrementAndGet() <= 0){ | ||||
|             fail(t); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -1,33 +1,27 @@ | |||
| package meerkat.bulletinboard.workers.multiserver; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.CompleteBatch; | ||||
| 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 class MultiServerPostBatchWorker extends MultiServerGenericPostWorker<CompleteBatch> { | ||||
| 
 | ||||
|     public MultiServerPostBatchWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                       int minServers, BulletinBoardMessage payload, int chunkSize, int maxRetry, | ||||
|                                       int minServers, CompleteBatch payload, 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); | ||||
| 
 | ||||
|     protected void doPost(SingleServerBulletinBoardClient client, CompleteBatch payload) { | ||||
|         client.postBatch(payload, this); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver; | |||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,29 +0,0 @@ | |||
| package meerkat.bulletinboard.workers.multiserver; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 27-Dec-15. | ||||
|  */ | ||||
| public class MultiServerReadBatchDataWorker extends MultiServerGenericReadWorker<BulletinBoardMessage, BulletinBoardMessage> { | ||||
| 
 | ||||
|     public MultiServerReadBatchDataWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                           int minServers, BulletinBoardMessage payload, int maxRetry, | ||||
|                                           FutureCallback<BulletinBoardMessage> futureCallback) { | ||||
| 
 | ||||
|         super(clients, minServers, payload, maxRetry, futureCallback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void doRead(BulletinBoardMessage payload, SingleServerBulletinBoardClient client) { | ||||
|         client.readBatchData(payload, this); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,30 @@ | |||
| package meerkat.bulletinboard.workers.multiserver; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.CompleteBatch; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.protobuf.BulletinBoardAPI.BatchSpecificationMessage; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 27-Dec-15. | ||||
|  */ | ||||
| public class MultiServerReadBatchWorker extends MultiServerGenericReadWorker<BatchSpecificationMessage, CompleteBatch> { | ||||
| 
 | ||||
|     public MultiServerReadBatchWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                       int minServers, BatchSpecificationMessage payload, int maxRetry, | ||||
|                                       FutureCallback<CompleteBatch> futureCallback) { | ||||
| 
 | ||||
|         super(clients, minServers, payload, maxRetry, futureCallback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void doRead(BatchSpecificationMessage payload, SingleServerBulletinBoardClient client) { | ||||
|         client.readBatch(payload, this); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -1,30 +0,0 @@ | |||
| package meerkat.bulletinboard.workers.multiserver; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage; | ||||
| import meerkat.protobuf.BulletinBoardApi.MessageID; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 27-Dec-15. | ||||
|  */ | ||||
| public class MultiServerReadMessageWorker extends MultiServerGenericReadWorker<MessageID, BulletinBoardMessage> { | ||||
| 
 | ||||
|     public MultiServerReadMessageWorker(List<SingleServerBulletinBoardClient> clients, | ||||
|                                         int minServers, MessageID payload, int maxRetry, | ||||
|                                         FutureCallback<BulletinBoardMessage> futureCallback) { | ||||
| 
 | ||||
|         super(clients, minServers, payload, maxRetry, futureCallback); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void doRead(MessageID payload, SingleServerBulletinBoardClient client) { | ||||
|         client.readMessage(payload, this); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -2,7 +2,7 @@ package meerkat.bulletinboard.workers.multiserver; | |||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import meerkat.bulletinboard.SingleServerBulletinBoardClient; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,53 +1,17 @@ | |||
| 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 meerkat.protobuf.BulletinBoardAPI.BeginBatchMessage; | ||||
| 
 | ||||
| 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 class SingleServerBeginBatchWorker extends SingleServerGenericPostWorker<BeginBatchMessage> { | ||||
| 
 | ||||
|     public SingleServerBeginBatchWorker(String serverAddress, BeginBatchMessage payload, int maxRetry) { | ||||
|         super(serverAddress, payload, maxRetry); | ||||
|         super(serverAddress, BEGIN_BATCH_PATH, payload, maxRetry); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Int64Value call() throws Exception { | ||||
|         Client client = clientLocal.get(); | ||||
| 
 | ||||
|         WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(BEGIN_BATCH_PATH); | ||||
|         Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post( | ||||
|                 Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF)); | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             Int64Value result =  response.readEntity(Int64Value.class); | ||||
|             return result; | ||||
| 
 | ||||
|         } catch (ProcessingException | IllegalStateException e) { | ||||
| 
 | ||||
|             // Post to this server failed
 | ||||
|             throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage()); | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|             throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage()); | ||||
|         } | ||||
|         finally { | ||||
|             response.close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import meerkat.protobuf.BulletinBoardApi.CloseBatchMessage; | ||||
| 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 | ||||
|  * Tries to contact server once and perform a close batch operation | ||||
|  */ | ||||
| public class SingleServerCloseBatchWorker extends SingleServerGenericPostWorker<CloseBatchMessage> { | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,55 +0,0 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import com.google.protobuf.Int64Value; | ||||
| import meerkat.bulletinboard.SingleServerWorker; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.BulletinBoardApi.SyncQuery; | ||||
| import meerkat.protobuf.BulletinBoardApi.GenerateSyncQueryParams; | ||||
| import meerkat.rest.Constants; | ||||
| 
 | ||||
| import javax.ws.rs.ProcessingException; | ||||
| import javax.ws.rs.client.Client; | ||||
| import javax.ws.rs.client.Entity; | ||||
| import javax.ws.rs.client.WebTarget; | ||||
| import javax.ws.rs.core.Response; | ||||
| 
 | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH; | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.GENERATE_SYNC_QUERY_PATH; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 27-Dec-15. | ||||
|  * Tries to contact server once and perform a Sync Query Generation operation | ||||
|  */ | ||||
| public class SingleServerGenerateSyncQueryWorker extends SingleServerWorker<GenerateSyncQueryParams,SyncQuery> { | ||||
| 
 | ||||
|     public SingleServerGenerateSyncQueryWorker(String serverAddress, GenerateSyncQueryParams payload, int maxRetry) { | ||||
|         super(serverAddress, payload, maxRetry); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public SyncQuery call() throws Exception { | ||||
| 
 | ||||
|         Client client = clientLocal.get(); | ||||
| 
 | ||||
|         WebTarget webTarget = client.target(serverAddress).path(BULLETIN_BOARD_SERVER_PATH).path(GENERATE_SYNC_QUERY_PATH); | ||||
| 
 | ||||
|         Response response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(payload, Constants.MEDIATYPE_PROTOBUF)); | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             SyncQuery result = response.readEntity(SyncQuery.class); | ||||
|             return result; | ||||
| 
 | ||||
|         } catch (ProcessingException | IllegalStateException e) { | ||||
| 
 | ||||
|             // Post to this server failed
 | ||||
|             throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage()); | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|             throw new CommunicationException("Could not contact the server. Original error: " + e.getMessage()); | ||||
|         } | ||||
|         finally { | ||||
|             response.close(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -1,9 +1,8 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import com.google.protobuf.BoolValue; | ||||
| import meerkat.bulletinboard.SingleServerWorker; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.Comm.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.BoolMsg; | ||||
| import meerkat.rest.Constants; | ||||
| 
 | ||||
| import javax.ws.rs.ProcessingException; | ||||
|  | @ -44,8 +43,8 @@ public class SingleServerGenericPostWorker<T> extends SingleServerWorker<T, Bool | |||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             // If a BoolValue entity is returned: the post was successful
 | ||||
|             response.readEntity(BoolValue.class); | ||||
|             // If a BoolMsg entity is returned: the post was successful
 | ||||
|             response.readEntity(BoolMsg.class); | ||||
|             return Boolean.TRUE; | ||||
| 
 | ||||
|         } catch (ProcessingException | IllegalStateException e) { | ||||
|  |  | |||
|  | @ -2,18 +2,15 @@ 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.*; | ||||
| 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 static meerkat.bulletinboard.BulletinBoardConstants.BULLETIN_BOARD_SERVER_PATH; | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.READ_MESSAGES_PATH; | ||||
| 
 | ||||
|  | @ -48,19 +45,17 @@ public class SingleServerGetRedundancyWorker extends SingleServerWorker<MessageI | |||
| 
 | ||||
|         // 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; | ||||
|         response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).post(Entity.entity(msgFilterList, Constants.MEDIATYPE_PROTOBUF)); | ||||
| 
 | ||||
|         // Retrieve answer
 | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BulletinBoardMessage.class); | ||||
|             // If a BulletinBoardMessageList is returned: the read was successful
 | ||||
|             BulletinBoardMessageList msgList = response.readEntity(BulletinBoardMessageList.class); | ||||
| 
 | ||||
|             if (inputStream.asList().size() > 0){ | ||||
|             if (msgList.getMessageList().size() > 0){ | ||||
|                 // Message exists in the server
 | ||||
|                 return 1.0f; | ||||
|             } | ||||
|  | @ -69,15 +64,14 @@ public class SingleServerGetRedundancyWorker extends SingleServerWorker<MessageI | |||
|                 return 0.0f; | ||||
|             } | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|         } catch (ProcessingException | IllegalStateException e) { | ||||
| 
 | ||||
|             // Read failed
 | ||||
|             throw new CommunicationException("Server access failed"); | ||||
| 
 | ||||
|         } finally { | ||||
|             try { | ||||
|                 inputStream.close(); | ||||
|             } catch (IOException ignored) {} | ||||
|         } | ||||
|         finally { | ||||
|             response.close(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import meerkat.protobuf.BulletinBoardApi.BatchMessage; | ||||
| import meerkat.protobuf.BulletinBoardAPI.BatchMessage; | ||||
| 
 | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.POST_BATCH_PATH; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import meerkat.protobuf.BulletinBoardApi.BulletinBoardMessage; | ||||
| import meerkat.protobuf.BulletinBoardAPI.BulletinBoardMessage; | ||||
| 
 | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.POST_MESSAGE_PATH; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,59 +0,0 @@ | |||
| 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(); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -1,28 +1,35 @@ | |||
| package meerkat.bulletinboard.workers.singleserver; | ||||
| 
 | ||||
| import meerkat.bulletinboard.CompleteBatch; | ||||
| import meerkat.bulletinboard.SingleServerWorker; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.comm.MessageInputStream; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| 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.GenericType; | ||||
| 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; | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.READ_BATCH_PATH; | ||||
| 
 | ||||
| import static meerkat.bulletinboard.BulletinBoardConstants.BATCH_ID_TAG_PREFIX; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 27-Dec-15. | ||||
|  */ | ||||
| public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, List<BatchChunk>> { | ||||
| public class SingleServerReadBatchWorker extends SingleServerWorker<BatchSpecificationMessage, List<BatchData>> { | ||||
| 
 | ||||
|     public SingleServerReadBatchWorker(String serverAddress, BatchQuery payload, int maxRetry) { | ||||
|     public SingleServerReadBatchWorker(String serverAddress, BatchSpecificationMessage payload, int maxRetry) { | ||||
|         super(serverAddress, payload, maxRetry); | ||||
|     } | ||||
| 
 | ||||
|  | @ -32,7 +39,7 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, | |||
|      * @return the complete batch as read from the server | ||||
|      * @throws CommunicationException if the server's response is invalid | ||||
|      */ | ||||
|     public List<BatchChunk> call() throws CommunicationException{ | ||||
|     public List<BatchData> call() throws CommunicationException{ | ||||
| 
 | ||||
|         Client client = clientLocal.get(); | ||||
| 
 | ||||
|  | @ -43,11 +50,11 @@ public class SingleServerReadBatchWorker extends SingleServerWorker<BatchQuery, | |||
|         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; | ||||
|         MessageInputStream<BatchData> inputStream = null; | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchChunk.class); | ||||
|             inputStream = MessageInputStream.MessageInputStreamFactory.createMessageInputStream(in, BatchData.class); | ||||
| 
 | ||||
|             return inputStream.asList(); | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,10 +3,10 @@ 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.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; | ||||
|  |  | |||
|  | @ -1,17 +1,25 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import com.google.protobuf.ByteString; | ||||
| import com.google.protobuf.*; | ||||
| import com.google.protobuf.Timestamp; | ||||
| import meerkat.bulletinboard.AsyncBulletinBoardClient; | ||||
| import meerkat.bulletinboard.CompleteBatch; | ||||
| import meerkat.bulletinboard.GenericBatchDigitalSignature; | ||||
| import meerkat.bulletinboard.ThreadedBulletinBoardClient; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.crypto.concrete.ECDSASignature; | ||||
| import meerkat.crypto.concrete.SHA256Digest; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.protobuf.BulletinBoardAPI.*; | ||||
| import meerkat.protobuf.Crypto; | ||||
| 
 | ||||
| import meerkat.protobuf.Voting.*; | ||||
| import meerkat.util.BulletinBoardMessageComparator; | ||||
| import meerkat.util.BulletinBoardMessageGenerator; | ||||
| import meerkat.util.BulletinBoardUtils; | ||||
| import meerkat.bulletinboard.AsyncBulletinBoardClient.BatchIdentifier; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| 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.io.IOException; | ||||
| import java.io.InputStream; | ||||
|  | @ -20,18 +28,14 @@ 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 { | ||||
| public class ThreadedBulletinBoardClientIntegrationTest { | ||||
| 
 | ||||
|     // Signature resources
 | ||||
| 
 | ||||
|     private BulletinBoardSignature signers[]; | ||||
|     private GenericBatchDigitalSignature signers[]; | ||||
|     private ByteString[] signerIDs; | ||||
| 
 | ||||
|     private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; | ||||
|  | @ -43,34 +47,37 @@ public class GenericBulletinBoardClientTester { | |||
|     private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; | ||||
|     private static String CERT3_PEM_EXAMPLE = "/certs/enduser-certs/user3.crt"; | ||||
| 
 | ||||
|     // 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); | ||||
| 
 | ||||
|     // Client and callbacks
 | ||||
| 
 | ||||
|     private AsyncBulletinBoardClient bulletinBoardClient; | ||||
| 
 | ||||
|     private PostCallback postCallback; | ||||
|     private PostCallback failPostCallback = new PostCallback(true,false); | ||||
| 
 | ||||
|     private RedundancyCallback redundancyCallback; | ||||
|     private ReadCallback readCallback; | ||||
|     private ReadBatchCallback readBatchCallback; | ||||
| 
 | ||||
|     // 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){ | ||||
|     public ThreadedBulletinBoardClientIntegrationTest(){ | ||||
| 
 | ||||
|         this.bulletinBoardClient = bulletinBoardClient; | ||||
| 
 | ||||
|         signers = new GenericBulletinBoardSignature[2]; | ||||
|         signers = new GenericBatchDigitalSignature[2]; | ||||
|         signerIDs = new ByteString[signers.length]; | ||||
|         signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); | ||||
|         signers[1] = new GenericBulletinBoardSignature(new ECDSASignature()); | ||||
|         signers[0] = new GenericBatchDigitalSignature(new ECDSASignature()); | ||||
|         signers[1] = new GenericBatchDigitalSignature(new ECDSASignature()); | ||||
| 
 | ||||
|         InputStream keyStream = getClass().getResourceAsStream(KEYFILE_EXAMPLE); | ||||
|         char[] password = KEYFILE_PASSWORD1.toCharArray(); | ||||
|  | @ -112,10 +119,6 @@ public class GenericBulletinBoardClientTester { | |||
|             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
 | ||||
|  | @ -146,21 +149,16 @@ public class GenericBulletinBoardClientTester { | |||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(Boolean msg) { | ||||
| 
 | ||||
|             System.err.println("Post operation completed"); | ||||
| 
 | ||||
|             jobSemaphore.release(); | ||||
|             //TODO: Change Assert mechanism to exception one
 | ||||
|             if (isAssert) { | ||||
|                 if (assertValue && !msg) { | ||||
|                     genericHandleFailure(new AssertionError("Post operation failed")); | ||||
|                 } else if (!assertValue && msg){ | ||||
|                     genericHandleFailure(new AssertionError("Post operation succeeded unexpectedly")); | ||||
|                 if (assertValue) { | ||||
|                     assertThat("Post operation failed", msg, is(Boolean.TRUE)); | ||||
|                 } else { | ||||
|                     jobSemaphore.release(); | ||||
|                     assertThat("Post operation succeeded unexpectedly", msg, is(Boolean.FALSE)); | ||||
|                 } | ||||
|             } else { | ||||
|                 jobSemaphore.release(); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|  | @ -223,24 +221,21 @@ public class GenericBulletinBoardClientTester { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private class ReadBatchCallback implements FutureCallback<BulletinBoardMessage>{ | ||||
|     private class ReadBatchCallback implements FutureCallback<CompleteBatch> { | ||||
| 
 | ||||
|         private BulletinBoardMessage expectedMsg; | ||||
|         private CompleteBatch expectedBatch; | ||||
| 
 | ||||
|         public ReadBatchCallback(BulletinBoardMessage expectedMsg) { | ||||
|             this.expectedMsg = expectedMsg; | ||||
|         public ReadBatchCallback(CompleteBatch expectedBatch) { | ||||
|             this.expectedBatch = expectedBatch; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(BulletinBoardMessage msg) { | ||||
|         public void onSuccess(CompleteBatch batch) { | ||||
| 
 | ||||
|             BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator(); | ||||
|             System.err.println(batch); | ||||
|             jobSemaphore.release(); | ||||
| 
 | ||||
|             if (msgComparator.compare(msg, expectedMsg) != 0) { | ||||
|                 genericHandleFailure(new AssertionError("Batch read returned different message.\nExpected:" + expectedMsg + "\nRecieved:" + msg + "\n")); | ||||
|             } else { | ||||
|                 jobSemaphore.release(); | ||||
|             } | ||||
|             assertThat("Batch returned is incorrect", batch, is(equalTo(expectedBatch))); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|  | @ -250,15 +245,79 @@ public class GenericBulletinBoardClientTester { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Randomness generators
 | ||||
| 
 | ||||
|     private byte randomByte(){ | ||||
|         return (byte) random.nextInt(); | ||||
|     } | ||||
| 
 | ||||
|     private byte[] randomByteArray(int length) { | ||||
| 
 | ||||
|         byte[] randomBytes = new byte[length]; | ||||
| 
 | ||||
|         for (int i = 0; i < length ; i++){ | ||||
|             randomBytes[i] = randomByte(); | ||||
|         } | ||||
| 
 | ||||
|         return randomBytes; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private CompleteBatch createRandomBatch(int signer, int batchId, int length) throws SignatureException { | ||||
| 
 | ||||
|         CompleteBatch completeBatch = new CompleteBatch(); | ||||
| 
 | ||||
|         // Create data
 | ||||
| 
 | ||||
|         completeBatch.setBeginBatchMessage(BeginBatchMessage.newBuilder() | ||||
|                 .setSignerId(signerIDs[signer]) | ||||
|                 .setBatchId(batchId) | ||||
|                 .addTag("Test") | ||||
|                 .build()); | ||||
| 
 | ||||
|         for (int i = 0 ; i < length ; i++){ | ||||
| 
 | ||||
|             BatchData batchData = BatchData.newBuilder() | ||||
|                     .setData(ByteString.copyFrom(randomByteArray(i))) | ||||
|                     .build(); | ||||
| 
 | ||||
|             completeBatch.appendBatchData(batchData); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         completeBatch.setTimestamp(Timestamp.newBuilder() | ||||
|                 .setSeconds(Math.abs(90)) | ||||
|                 .setNanos(50) | ||||
|                 .build()); | ||||
| 
 | ||||
|         signers[signer].updateContent(completeBatch); | ||||
| 
 | ||||
|         completeBatch.setSignature(signers[signer].sign()); | ||||
| 
 | ||||
|         return completeBatch; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Test methods
 | ||||
| 
 | ||||
|     /** | ||||
|      * Takes care of initializing the client and the test resources | ||||
|      */ | ||||
|     @Before | ||||
|     public void init(){ | ||||
| 
 | ||||
|         bulletinBoardClient = new ThreadedBulletinBoardClient(); | ||||
| 
 | ||||
|         random = new Random(0); // We use insecure randomness in tests for repeatability
 | ||||
| 
 | ||||
|         List<String> testDB = new LinkedList<>(); | ||||
|         testDB.add(BASE_URL); | ||||
| 
 | ||||
|         bulletinBoardClient.init(BulletinBoardClientParams.newBuilder() | ||||
|             .addAllBulletinBoardAddress(testDB) | ||||
|             .setMinRedundancy((float) 1.0) | ||||
|             .build()); | ||||
| 
 | ||||
|         postCallback = new PostCallback(); | ||||
|         redundancyCallback = new RedundancyCallback((float) 1.0); | ||||
| 
 | ||||
|  | @ -271,16 +330,13 @@ public class GenericBulletinBoardClientTester { | |||
|      * Closes the client and makes sure the test fails when an exception occurred in a separate thread | ||||
|      */ | ||||
| 
 | ||||
|     @After | ||||
|     public void close() { | ||||
| 
 | ||||
|         bulletinBoardClient.close(); | ||||
| 
 | ||||
|         if (thrown.size() > 0) { | ||||
| 
 | ||||
|             for (Throwable t : thrown){ | ||||
|                 System.err.println(t.getMessage()); | ||||
|             } | ||||
| 
 | ||||
|             assert false; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
|  | @ -288,7 +344,8 @@ public class GenericBulletinBoardClientTester { | |||
|     /** | ||||
|      * Tests the standard post, redundancy and read methods | ||||
|      */ | ||||
|     public void testPost() { | ||||
|     @Test | ||||
|     public void postTest() { | ||||
| 
 | ||||
|         byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4}; | ||||
|         byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14}; | ||||
|  | @ -364,69 +421,68 @@ public class GenericBulletinBoardClientTester { | |||
| 
 | ||||
|     /** | ||||
|      * Tests posting a batch by parts | ||||
|      * Also tests not being able to post to a closed batch | ||||
|      * @throws CommunicationException, SignatureException, InterruptedException | ||||
|      */ | ||||
|     @Test | ||||
|     public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { | ||||
| 
 | ||||
|         final int SIGNER = 1; | ||||
|         final int BATCH_ID = 100; | ||||
|         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); | ||||
|         CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH); | ||||
| 
 | ||||
|         // 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); | ||||
|             } | ||||
| 
 | ||||
|         }); | ||||
|         bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), postCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|         digest.reset(); | ||||
|         digest.update(msg); | ||||
|         // Post data
 | ||||
| 
 | ||||
|         bulletinBoardClient.readMessage(digest.digestAsMessageID(), new ReadBatchCallback(msg)); | ||||
|         bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), postCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|         // Close batch
 | ||||
| 
 | ||||
|         CloseBatchMessage closeBatchMessage = CloseBatchMessage.newBuilder() | ||||
|                 .setBatchId(BATCH_ID) | ||||
|                 .setBatchLength(BATCH_LENGTH) | ||||
|                 .setTimestamp(Timestamp.newBuilder() | ||||
|                         .setSeconds(50) | ||||
|                         .setNanos(80) | ||||
|                         .build()) | ||||
|                 .setSig(completeBatch.getSignature()) | ||||
|                 .build(); | ||||
| 
 | ||||
|         bulletinBoardClient.closeBatch(closeBatchMessage, postCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|         // Attempt to open batch again
 | ||||
| 
 | ||||
|         bulletinBoardClient.beginBatch(completeBatch.getBeginBatchMessage(), failPostCallback); | ||||
| 
 | ||||
|         // Attempt to add batch data
 | ||||
| 
 | ||||
|         bulletinBoardClient.postBatchData(signerIDs[SIGNER], BATCH_ID, completeBatch.getBatchDataList(), failPostCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(2); | ||||
| 
 | ||||
|         // Read batch data
 | ||||
| 
 | ||||
|         BatchSpecificationMessage batchSpecificationMessage = | ||||
|                 BatchSpecificationMessage.newBuilder() | ||||
|                         .setSignerId(signerIDs[SIGNER]) | ||||
|                         .setBatchId(BATCH_ID) | ||||
|                         .setStartPosition(0) | ||||
|                         .build(); | ||||
| 
 | ||||
|         readBatchCallback = new ReadBatchCallback(completeBatch); | ||||
| 
 | ||||
|         bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|  | @ -434,61 +490,64 @@ public class GenericBulletinBoardClientTester { | |||
| 
 | ||||
|     /** | ||||
|      * Posts a complete batch message | ||||
|      * Checks reading of the message in two parts | ||||
|      * Checks reading od the message | ||||
|      * @throws CommunicationException, SignatureException, InterruptedException | ||||
|      */ | ||||
|     @Test | ||||
|     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); | ||||
|         final int SIGNER = 0; | ||||
|         final int BATCH_ID = 101; | ||||
|         final int BATCH_LENGTH = 50; | ||||
| 
 | ||||
|         // Post batch
 | ||||
| 
 | ||||
|         MessageID msgID = bulletinBoardClient.postAsBatch(msg, CHUNK_SIZE, postCallback); | ||||
|         CompleteBatch completeBatch = createRandomBatch(SIGNER, BATCH_ID, BATCH_LENGTH); | ||||
| 
 | ||||
|         bulletinBoardClient.postBatch(completeBatch,postCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|         // Read batch
 | ||||
| 
 | ||||
|         MessageFilterList filterList = MessageFilterList.newBuilder() | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.MSG_ID) | ||||
|                         .setId(msgID.getID()) | ||||
|                         .build()) | ||||
|                 .build(); | ||||
|         BatchSpecificationMessage batchSpecificationMessage = | ||||
|                 BatchSpecificationMessage.newBuilder() | ||||
|                         .setSignerId(signerIDs[SIGNER]) | ||||
|                         .setBatchId(BATCH_ID) | ||||
|                         .setStartPosition(0) | ||||
|                         .build(); | ||||
| 
 | ||||
|         bulletinBoardClient.readMessages(filterList, new FutureCallback<List<BulletinBoardMessage>>() { | ||||
|         readBatchCallback = new ReadBatchCallback(completeBatch); | ||||
| 
 | ||||
|             @Override | ||||
|             public void onSuccess(List<BulletinBoardMessage> msgList) { | ||||
|         bulletinBoardClient.readBatch(batchSpecificationMessage, readBatchCallback); | ||||
| 
 | ||||
|                 if (msgList.size() != 1) { | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|                     genericHandleFailure(new AssertionError("Wrong number of stubs returned. Expected: 1; Found: " + msgList.size())); | ||||
|     } | ||||
| 
 | ||||
|                 } else { | ||||
|     /** | ||||
|      * Tests that an unopened batch cannot be closed | ||||
|      * @throws CommunicationException, InterruptedException | ||||
|      */ | ||||
|     @Test | ||||
|     public void testInvalidBatchClose() throws CommunicationException, InterruptedException { | ||||
| 
 | ||||
|                     BulletinBoardMessage retrievedMsg = msgList.get(0); | ||||
|                     bulletinBoardClient.readBatchData(retrievedMsg, new ReadBatchCallback(msg)); | ||||
|         final int NON_EXISTENT_BATCH_ID = 999; | ||||
| 
 | ||||
|                 } | ||||
|         CloseBatchMessage closeBatchMessage = | ||||
|                 CloseBatchMessage.newBuilder() | ||||
|                         .setBatchId(NON_EXISTENT_BATCH_ID) | ||||
|                         .setBatchLength(1) | ||||
|                         .setSig(Crypto.Signature.getDefaultInstance()) | ||||
|                         .setTimestamp(Timestamp.newBuilder() | ||||
|                                 .setSeconds(9) | ||||
|                                 .setNanos(12) | ||||
|                                 .build()) | ||||
|                         .build(); | ||||
| 
 | ||||
|             } | ||||
|         // Try to close the (unopened) batch;
 | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(Throwable t) { | ||||
|                 genericHandleFailure(t); | ||||
|             } | ||||
| 
 | ||||
|         }); | ||||
|         bulletinBoardClient.closeBatch(closeBatchMessage, failPostCallback); | ||||
| 
 | ||||
|         jobSemaphore.acquire(); | ||||
| 
 | ||||
|  | @ -1,318 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.FutureCallback; | ||||
| import com.google.protobuf.*; | ||||
| import com.google.protobuf.Timestamp; | ||||
| 
 | ||||
| import static meerkat.bulletinboard.BulletinBoardSynchronizer.SyncStatus; | ||||
| 
 | ||||
| import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; | ||||
| import meerkat.bulletinboard.sqlserver.H2QueryProvider; | ||||
| 
 | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.crypto.concrete.ECDSASignature; | ||||
| import meerkat.protobuf.BulletinBoardApi.*; | ||||
| import meerkat.util.BulletinBoardMessageComparator; | ||||
| import meerkat.util.BulletinBoardMessageGenerator; | ||||
| import org.junit.*; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.security.*; | ||||
| import java.security.cert.CertificateException; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.Random; | ||||
| import java.util.concurrent.Semaphore; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| 
 | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.junit.Assert.fail; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel on 6/1/2016. | ||||
|  */ | ||||
| public class BulletinBoardSynchronizerTest { | ||||
| 
 | ||||
|     private static final String REMOTE_SERVER_ADDRESS = "remoteDB"; | ||||
|     private static final String LOCAL_SERVER_ADDRESS = "localDB"; | ||||
|     private static int testCount; | ||||
| 
 | ||||
|     private static final int THREAD_NUM = 3; | ||||
|     private static final int SUBSCRIPTION_INTERVAL = 1000; | ||||
| 
 | ||||
|     private static final int SYNC_SLEEP_INTERVAL = 500; | ||||
|     private static final int SYNC_WAIT_CAP = 1000; | ||||
| 
 | ||||
|     private DeletableSubscriptionBulletinBoardClient localClient; | ||||
|     private AsyncBulletinBoardClient remoteClient; | ||||
| 
 | ||||
|     private BulletinBoardSynchronizer synchronizer; | ||||
| 
 | ||||
|     private static BulletinBoardMessageGenerator messageGenerator; | ||||
|     private static BulletinBoardMessageComparator messageComparator; | ||||
| 
 | ||||
|     private static String KEYFILE_EXAMPLE = "/certs/enduser-certs/user1-key-with-password-secret.p12"; | ||||
|     private static String KEYFILE_PASSWORD1 = "secret"; | ||||
|     private static String CERT1_PEM_EXAMPLE = "/certs/enduser-certs/user1.crt"; | ||||
| 
 | ||||
|     private static BulletinBoardSignature[] signers; | ||||
|     private static ByteString[] signerIDs; | ||||
| 
 | ||||
|     private Semaphore semaphore; | ||||
|     private List<Throwable> thrown; | ||||
| 
 | ||||
|     @BeforeClass | ||||
|     public static void build() { | ||||
| 
 | ||||
|         messageGenerator = new BulletinBoardMessageGenerator(new Random(0)); | ||||
|         messageComparator = new BulletinBoardMessageComparator(); | ||||
| 
 | ||||
|         signers = new BulletinBoardSignature[1]; | ||||
|         signerIDs = new ByteString[1]; | ||||
| 
 | ||||
|         signers[0] = new GenericBulletinBoardSignature(new ECDSASignature()); | ||||
|         signerIDs[0] = signers[0].getSignerID(); | ||||
| 
 | ||||
|         InputStream keyStream = BulletinBoardSynchronizerTest.class.getResourceAsStream(KEYFILE_EXAMPLE); | ||||
|         char[] password = KEYFILE_PASSWORD1.toCharArray(); | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             KeyStore.Builder keyStoreBuilder = signers[0].getPKCS12KeyStoreBuilder(keyStream, password); | ||||
| 
 | ||||
|             signers[0].loadSigningCertificate(keyStoreBuilder); | ||||
| 
 | ||||
|             signers[0].loadVerificationCertificates(BulletinBoardSynchronizerTest.class.getResourceAsStream(CERT1_PEM_EXAMPLE)); | ||||
| 
 | ||||
|         } catch (IOException e) { | ||||
|             System.err.println("Failed reading from signature file " + e.getMessage()); | ||||
|             fail("Failed reading from signature file " + e.getMessage()); | ||||
|         } catch (CertificateException e) { | ||||
|             System.err.println("Failed reading certificate " + e.getMessage()); | ||||
|             fail("Failed reading certificate " + e.getMessage()); | ||||
|         } catch (KeyStoreException e) { | ||||
|             System.err.println("Failed reading keystore " + e.getMessage()); | ||||
|             fail("Failed reading keystore " + e.getMessage()); | ||||
|         } catch (NoSuchAlgorithmException e) { | ||||
|             System.err.println("Couldn't find signing algorithm " + e.getMessage()); | ||||
|             fail("Couldn't find signing algorithm " + e.getMessage()); | ||||
|         } catch (UnrecoverableKeyException e) { | ||||
|             System.err.println("Couldn't find signing key " + e.getMessage()); | ||||
|             fail("Couldn't find signing key " + e.getMessage()); | ||||
|         } | ||||
| 
 | ||||
|         signerIDs[0] = signers[0].getSignerID(); | ||||
| 
 | ||||
|         testCount = 0; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Before | ||||
|     public void init() throws CommunicationException { | ||||
| 
 | ||||
|         DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_SERVER_ADDRESS + testCount)); | ||||
|         remoteServer.init(); | ||||
| 
 | ||||
|         remoteClient = new LocalBulletinBoardClient( | ||||
|                 remoteServer, | ||||
|                 THREAD_NUM, | ||||
|                 SUBSCRIPTION_INTERVAL); | ||||
| 
 | ||||
|         DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_SERVER_ADDRESS + testCount)); | ||||
|         localServer.init(); | ||||
| 
 | ||||
|         localClient = new LocalBulletinBoardClient( | ||||
|                 localServer, | ||||
|                 THREAD_NUM, | ||||
|                 SUBSCRIPTION_INTERVAL); | ||||
| 
 | ||||
|         synchronizer = new SimpleBulletinBoardSynchronizer(SYNC_SLEEP_INTERVAL, SYNC_WAIT_CAP); | ||||
|         synchronizer.init(localClient, remoteClient); | ||||
| 
 | ||||
|         semaphore = new Semaphore(0); | ||||
|         thrown = new LinkedList<>(); | ||||
| 
 | ||||
|         testCount++; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private class SyncStatusCallback implements FutureCallback<SyncStatus> { | ||||
| 
 | ||||
|         private final SyncStatus statusToWaitFor; | ||||
|         private AtomicBoolean stillWaiting; | ||||
| 
 | ||||
|         public SyncStatusCallback(SyncStatus statusToWaitFor) { | ||||
|             this.statusToWaitFor = statusToWaitFor; | ||||
|             stillWaiting = new AtomicBoolean(true); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(SyncStatus result) { | ||||
| 
 | ||||
|             if (result == statusToWaitFor && stillWaiting.compareAndSet(true, false)){ | ||||
|                 semaphore.release(); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
|             thrown.add(t); | ||||
|             if (stillWaiting.compareAndSet(true,false)) { | ||||
|                 semaphore.release(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private class MessageCountCallback implements FutureCallback<Integer> { | ||||
| 
 | ||||
|         private int[] expectedCounts; | ||||
|         private int currentIteration; | ||||
| 
 | ||||
|         public MessageCountCallback(int[] expectedCounts) { | ||||
|             this.expectedCounts = expectedCounts; | ||||
|             this.currentIteration = 0; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onSuccess(Integer result) { | ||||
| 
 | ||||
|             if (currentIteration < expectedCounts.length){ | ||||
|                 if (result != expectedCounts[currentIteration]){ | ||||
|                     onFailure(new AssertionError("Wrong message count. Expected " + expectedCounts[currentIteration] + " but received " + result)); | ||||
|                     currentIteration = expectedCounts.length; | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             currentIteration++; | ||||
| 
 | ||||
|             if (currentIteration == expectedCounts.length) | ||||
|                 semaphore.release(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
|             thrown.add(t); | ||||
|             semaphore.release(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSync() throws SignatureException, CommunicationException, InterruptedException { | ||||
| 
 | ||||
|         Timestamp timestamp = Timestamp.newBuilder() | ||||
|                 .setSeconds(15252162) | ||||
|                 .setNanos(85914) | ||||
|                 .build(); | ||||
| 
 | ||||
|         BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10); | ||||
| 
 | ||||
|         MessageID msgID = localClient.postMessage(msg); | ||||
| 
 | ||||
|         timestamp = Timestamp.newBuilder() | ||||
|                 .setSeconds(51511653) | ||||
|                 .setNanos(3625) | ||||
|                 .build(); | ||||
| 
 | ||||
|         BulletinBoardMessage batchMessage = messageGenerator.generateRandomMessage(signers,timestamp,  100, 10); | ||||
| 
 | ||||
|         MessageID batchMsgID = localClient.postAsBatch(batchMessage, 10); | ||||
| 
 | ||||
|         BulletinBoardMessage test = localClient.readMessage(batchMsgID); | ||||
| 
 | ||||
|         BulletinBoardMessage stub = localClient.readMessages(MessageFilterList.newBuilder() | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.MSG_ID) | ||||
|                         .setId(batchMsgID.getID()) | ||||
|                         .build()) | ||||
|                 .build()).get(0); | ||||
| 
 | ||||
|         BulletinBoardMessage test2 = localClient.readBatchData(stub); | ||||
| 
 | ||||
|         synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SYNCHRONIZED)); | ||||
| 
 | ||||
|         int[] expectedCounts = {2,0}; | ||||
|         synchronizer.subscribeToRemainingMessagesCount(new MessageCountCallback(expectedCounts)); | ||||
| 
 | ||||
|         Thread syncThread = new Thread(synchronizer); | ||||
|         syncThread.start(); | ||||
| 
 | ||||
|         if (!semaphore.tryAcquire(2, 4000, TimeUnit.MILLISECONDS)) { | ||||
|             thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync.")); | ||||
|         } | ||||
| 
 | ||||
|         synchronizer.stop(); | ||||
|         syncThread.join(); | ||||
| 
 | ||||
|         if (thrown.size() > 0) { | ||||
|             for (Throwable t : thrown) | ||||
|                 System.err.println(t.getMessage()); | ||||
|             assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false); | ||||
|         } | ||||
| 
 | ||||
|         List<BulletinBoardMessage> msgList = remoteClient.readMessages(MessageFilterList.newBuilder() | ||||
|                 .addFilter(MessageFilter.newBuilder() | ||||
|                         .setType(FilterType.MSG_ID) | ||||
|                         .setId(msgID.getID()) | ||||
|                         .build()) | ||||
|                 .build()); | ||||
| 
 | ||||
|         assertThat("Wrong number of messages returned.", msgList.size() == 1); | ||||
|         assertThat("Returned message is not equal to original one", messageComparator.compare(msgList.get(0),msg) == 0); | ||||
| 
 | ||||
|         BulletinBoardMessage returnedBatchMsg = remoteClient.readMessage(batchMsgID); | ||||
| 
 | ||||
|         assertThat("Returned batch does not equal original one.", messageComparator.compare(returnedBatchMsg, batchMessage) == 0); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testServerError() throws SignatureException, CommunicationException, InterruptedException { | ||||
| 
 | ||||
|         Timestamp timestamp = Timestamp.newBuilder() | ||||
|                 .setSeconds(945736256) | ||||
|                 .setNanos(276788) | ||||
|                 .build(); | ||||
| 
 | ||||
|         BulletinBoardMessage msg = messageGenerator.generateRandomMessage(signers, timestamp, 10, 10); | ||||
| 
 | ||||
|         remoteClient.close(); | ||||
| 
 | ||||
|         synchronizer.subscribeToSyncStatus(new SyncStatusCallback(SyncStatus.SERVER_ERROR)); | ||||
| 
 | ||||
|         localClient.postMessage(msg); | ||||
| 
 | ||||
|         Thread thread = new Thread(synchronizer); | ||||
| 
 | ||||
|         thread.start(); | ||||
| 
 | ||||
|         if (!semaphore.tryAcquire(4000, TimeUnit.MILLISECONDS)) { | ||||
|             thrown.add(new TimeoutException("Timeout occurred while waiting for synchronizer to sync.")); | ||||
|         } | ||||
| 
 | ||||
|         synchronizer.stop(); | ||||
|         thread.join(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @After | ||||
|     public void close() { | ||||
| 
 | ||||
|         if (thrown.size() > 0) { | ||||
|             for (Throwable t : thrown) { | ||||
|                 System.err.println(t.getMessage()); | ||||
|             } | ||||
|             assertThat("Exception thrown by Synchronizer: " + thrown.get(0).getMessage(), false); | ||||
|         } | ||||
| 
 | ||||
|         synchronizer.stop(); | ||||
|         localClient.close(); | ||||
|         remoteClient.close(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,111 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import meerkat.bulletinboard.sqlserver.BulletinBoardSQLServer; | ||||
| import meerkat.bulletinboard.sqlserver.H2QueryProvider; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.security.SignatureException; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel on 6/27/2016. | ||||
|  */ | ||||
| public class CachedBulletinBoardClientTest { | ||||
| 
 | ||||
|     private static final int THREAD_NUM = 3; | ||||
| 
 | ||||
|     private static final String LOCAL_DB_NAME = "localDB"; | ||||
|     private static final String REMOTE_DB_NAME = "remoteDB"; | ||||
|     private static final String QUEUE_DB_NAME = "queueDB"; | ||||
| 
 | ||||
|     private static final int SUBSRCIPTION_DELAY = 500; | ||||
|     private static final int SYNC_DELAY = 500; | ||||
| 
 | ||||
|     // Testers
 | ||||
|     private CachedBulletinBoardClient cachedClient; | ||||
|     private GenericBulletinBoardClientTester clientTest; | ||||
|     private GenericSubscriptionClientTester subscriptionTester; | ||||
| 
 | ||||
|     public CachedBulletinBoardClientTest() throws CommunicationException { | ||||
| 
 | ||||
|         DeletableBulletinBoardServer localServer = new BulletinBoardSQLServer(new H2QueryProvider(LOCAL_DB_NAME)); | ||||
|         localServer.init(); | ||||
|         LocalBulletinBoardClient localClient = new LocalBulletinBoardClient(localServer, THREAD_NUM, SUBSRCIPTION_DELAY); | ||||
| 
 | ||||
|         DeletableBulletinBoardServer remoteServer = new BulletinBoardSQLServer(new H2QueryProvider(REMOTE_DB_NAME)); | ||||
|         remoteServer.init(); | ||||
|         LocalBulletinBoardClient remoteClient = new LocalBulletinBoardClient(remoteServer, THREAD_NUM, SUBSRCIPTION_DELAY); | ||||
| 
 | ||||
|         DeletableBulletinBoardServer queueServer = new BulletinBoardSQLServer(new H2QueryProvider(QUEUE_DB_NAME)); | ||||
|         queueServer.init(); | ||||
|         LocalBulletinBoardClient queueClient = new LocalBulletinBoardClient(queueServer, THREAD_NUM, SUBSRCIPTION_DELAY); | ||||
| 
 | ||||
|         List<SubscriptionBulletinBoardClient> clientList = new LinkedList<>(); | ||||
|         clientList.add(remoteClient); | ||||
| 
 | ||||
|         BulletinBoardSubscriber subscriber = new ThreadedBulletinBoardSubscriber(clientList, localClient); | ||||
| 
 | ||||
|         cachedClient = new CachedBulletinBoardClient(localClient, remoteClient, subscriber, queueClient, SYNC_DELAY, SYNC_DELAY); | ||||
|         subscriptionTester = new GenericSubscriptionClientTester(cachedClient); | ||||
|         clientTest = new GenericBulletinBoardClientTester(cachedClient, 87351); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Test methods
 | ||||
| 
 | ||||
|     /** | ||||
|      * Takes care of initializing the client and the test resources | ||||
|      */ | ||||
|     @Before | ||||
|     public void init(){ | ||||
| 
 | ||||
|         clientTest.init(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Closes the client and makes sure the test fails when an exception occurred in a separate thread | ||||
|      */ | ||||
| 
 | ||||
|     @After | ||||
|     public void close() { | ||||
| 
 | ||||
|         cachedClient.close(); | ||||
|         clientTest.close(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPost() { | ||||
| 
 | ||||
|         clientTest.testPost(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { | ||||
| 
 | ||||
|         clientTest.testBatchPost(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { | ||||
| 
 | ||||
|         clientTest.testCompleteBatchPost(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSubscription() throws SignatureException, CommunicationException { | ||||
| 
 | ||||
| //        subscriptionTester.init();
 | ||||
| //        subscriptionTester.subscriptionTest();
 | ||||
| //        subscriptionTester.close();
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,226 +0,0 @@ | |||
| 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()); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,90 +0,0 @@ | |||
| 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(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,104 +0,0 @@ | |||
| package meerkat.bulletinboard; | ||||
| 
 | ||||
| import com.google.common.util.concurrent.ListeningExecutorService; | ||||
| import com.google.common.util.concurrent.MoreExecutors; | ||||
| import meerkat.comm.CommunicationException; | ||||
| import meerkat.protobuf.Voting.BulletinBoardClientParams; | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import java.security.SignatureException; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Executors; | ||||
| 
 | ||||
| /** | ||||
|  * Created by Arbel Deutsch Peled on 05-Dec-15. | ||||
|  */ | ||||
| public class SingleServerBulletinBoardClientIntegrationTest { | ||||
| 
 | ||||
|     // Server data
 | ||||
| 
 | ||||
|     private static final String PROP_GETTY_URL = "gretty.httpBaseURI"; | ||||
|     private static final String DEFAULT_BASE_URL = "http://localhost:8081"; | ||||
|     private static final String BASE_URL =  System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL); | ||||
| 
 | ||||
|     private static final int THREAD_NUM = 3; | ||||
|     private static final long FAIL_DELAY = 3000; | ||||
|     private static final long SUBSCRIPTION_INTERVAL = 500; | ||||
| 
 | ||||
|     // Testers
 | ||||
|     private GenericBulletinBoardClientTester clientTest; | ||||
|     private GenericSubscriptionClientTester subscriptionTester; | ||||
| 
 | ||||
|     public SingleServerBulletinBoardClientIntegrationTest(){ | ||||
| 
 | ||||
|         SingleServerBulletinBoardClient client = new SingleServerBulletinBoardClient(THREAD_NUM, FAIL_DELAY, SUBSCRIPTION_INTERVAL); | ||||
| 
 | ||||
|         List<String> testDB = new LinkedList<>(); | ||||
|         testDB.add(BASE_URL); | ||||
| 
 | ||||
|         client.init(BulletinBoardClientParams.newBuilder() | ||||
|                 .addAllBulletinBoardAddress(testDB) | ||||
|                 .setMinRedundancy((float) 1.0) | ||||
|                 .build()); | ||||
| 
 | ||||
|         clientTest = new GenericBulletinBoardClientTester(client, 981541); | ||||
|         subscriptionTester = new GenericSubscriptionClientTester(client); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // Test methods
 | ||||
| 
 | ||||
|     /** | ||||
|      * Takes care of initializing the client and the test resources | ||||
|      */ | ||||
|     @Before | ||||
|     public void init(){ | ||||
| 
 | ||||
|         clientTest.init(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Closes the client and makes sure the test fails when an exception occurred in a separate thread | ||||
|      */ | ||||
| 
 | ||||
|     @After | ||||
|     public void close() { | ||||
| 
 | ||||
|         clientTest.close(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testPost() { | ||||
| 
 | ||||
|         clientTest.testPost(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testBatchPost() throws CommunicationException, SignatureException, InterruptedException { | ||||
| 
 | ||||
|         clientTest.testBatchPost(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testCompleteBatchPost() throws CommunicationException, SignatureException, InterruptedException { | ||||
| 
 | ||||
|         clientTest.testCompleteBatchPost(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void testSubscription() throws SignatureException, CommunicationException { | ||||
| 
 | ||||
|         subscriptionTester.init(); | ||||
|         subscriptionTester.subscriptionTest(); | ||||
|         subscriptionTester.close(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,89 +0,0 @@ | |||
| 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(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,57 +0,0 @@ | |||
| { | ||||
|   "$schema": "./node_modules/@angular/cli/lib/config/schema.json", | ||||
|   "project": { | ||||
|     "name": "bulletin-board-server-frontend" | ||||
|   }, | ||||
|   "apps": [ | ||||
|     { | ||||
|       "root": "src", | ||||
|       "outDir": "dist", | ||||
|       "assets": [ | ||||
|         "assets", | ||||
|         "favicon.ico" | ||||
|       ], | ||||
|       "index": "index.html", | ||||
|       "main": "main.ts", | ||||
|       "polyfills": "polyfills.ts", | ||||
|       "test": "test.ts", | ||||
|       "tsconfig": "tsconfig.app.json", | ||||
|       "testTsconfig": "tsconfig.spec.json", | ||||
|       "prefix": "app", | ||||
|       "styles": [ | ||||
|         "styles.css" | ||||
|       ], | ||||
|       "scripts": [], | ||||
|       "environmentSource": "environments/environment.ts", | ||||
|       "environments": { | ||||
|         "dev": "environments/environment.ts", | ||||
|         "prod": "environments/environment.prod.ts" | ||||
|       } | ||||
|     } | ||||
|   ], | ||||
|   "e2e": { | ||||
|     "protractor": { | ||||
|       "config": "./protractor.conf.js" | ||||
|     } | ||||
|   }, | ||||
|   "lint": [ | ||||
|     { | ||||
|       "project": "src/tsconfig.app.json" | ||||
|     }, | ||||
|     { | ||||
|       "project": "src/tsconfig.spec.json" | ||||
|     }, | ||||
|     { | ||||
|       "project": "e2e/tsconfig.e2e.json" | ||||
|     } | ||||
|   ], | ||||
|   "test": { | ||||
|     "karma": { | ||||
|       "config": "./karma.conf.js" | ||||
|     } | ||||
|   }, | ||||
|   "defaults": { | ||||
|     "styleExt": "css", | ||||
|     "component": {} | ||||
|   } | ||||
| } | ||||
|  | @ -1,13 +0,0 @@ | |||
| # Editor configuration, see http://editorconfig.org | ||||
| root = true | ||||
| 
 | ||||
| [*] | ||||
| charset = utf-8 | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
| 
 | ||||
| [*.md] | ||||
| max_line_length = off | ||||
| trim_trailing_whitespace = false | ||||
|  | @ -1,42 +0,0 @@ | |||
| # See http://help.github.com/ignore-files/ for more about ignoring files. | ||||
| 
 | ||||
| # compiled output | ||||
| /dist | ||||
| /tmp | ||||
| /out-tsc | ||||
| 
 | ||||
| # dependencies | ||||
| /node_modules | ||||
| 
 | ||||
| # IDEs and editors | ||||
| /.idea | ||||
| .project | ||||
| .classpath | ||||
| .c9/ | ||||
| *.launch | ||||
| .settings/ | ||||
| *.sublime-workspace | ||||
| 
 | ||||
| # IDE - VSCode | ||||
| .vscode/* | ||||
| !.vscode/settings.json | ||||
| !.vscode/tasks.json | ||||
| !.vscode/launch.json | ||||
| !.vscode/extensions.json | ||||
| 
 | ||||
| # misc | ||||
| /.sass-cache | ||||
| /connect.lock | ||||
| /coverage | ||||
| /libpeerconnection.log | ||||
| npm-debug.log | ||||
| testem.log | ||||
| /typings | ||||
| 
 | ||||
| # e2e | ||||
| /e2e/*.js | ||||
| /e2e/*.map | ||||
| 
 | ||||
| # System Files | ||||
| .DS_Store | ||||
| Thumbs.db | ||||
|  | @ -1,28 +0,0 @@ | |||
| # BulletinBoardServerFrontend | ||||
| 
 | ||||
| This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 1.2.0. | ||||
| 
 | ||||
| ## Development server | ||||
| 
 | ||||
| Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. | ||||
| 
 | ||||
| ## Code scaffolding | ||||
| 
 | ||||
| Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|module`. | ||||
| 
 | ||||
| ## Build | ||||
| 
 | ||||
| Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. | ||||
| 
 | ||||
| ## Running unit tests | ||||
| 
 | ||||
| Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). | ||||
| 
 | ||||
| ## Running end-to-end tests | ||||
| 
 | ||||
| Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). | ||||
| Before running the tests make sure you are serving the app via `ng serve`. | ||||
| 
 | ||||
| ## Further help | ||||
| 
 | ||||
| To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). | ||||
|  | @ -1,60 +0,0 @@ | |||
| plugins { | ||||
|   id "com.moowork.node" version "1.2.0" | ||||
| } | ||||
| 
 | ||||
| node { | ||||
|   download = true; | ||||
| } | ||||
| 
 | ||||
| ext { | ||||
|   bundlejs = "src/app/bundle.js" | ||||
|   bundlets = "src/app/bundle.d.ts" | ||||
| } | ||||
| 
 | ||||
| //task installAngularCli(type: NpmTask) { | ||||
| //  args = ['install', '@angular/cli'] | ||||
| //} | ||||
| //npm_install.dependsOn(installAngularCli); | ||||
| 
 | ||||
| task printProtos { | ||||
|   doLast { | ||||
|     println getProtoFiles().join(" ") | ||||
|   } | ||||
| } | ||||
| printProtos.description = "List all the .proto files we can find." | ||||
| 
 | ||||
| 
 | ||||
| def getProtoFiles() { | ||||
|   def protoFiles = [] | ||||
| 
 | ||||
|   rootProject.subprojects { proj -> | ||||
|     if (proj.hasProperty('sourceSets') && proj.sourceSets.hasProperty('main') && | ||||
|       proj.sourceSets.main.hasProperty('proto')) { | ||||
|       protoFiles = protoFiles + proj.sourceSets.main.proto.getFiles(); | ||||
|     } | ||||
|   } | ||||
|   return protoFiles; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| task generateProtobufBundle(type: NpmTask) { | ||||
|   doFirst { | ||||
|     // We need do this in doFirst since getProtoFiles won't find all subprojects | ||||
|     // until after configuration is complete | ||||
|     def protoFiles = getProtoFiles() | ||||
|     generateProtobufBundle.args = ['run', 'protoc', '--' ] + protoFiles; | ||||
|   } | ||||
| 
 | ||||
|   dependsOn npm_install | ||||
| } | ||||
| generateProtobufBundle.description = "Generate the bundle.js/bundle.d.ts files containing compiled protobufs" | ||||
| 
 | ||||
| 
 | ||||
| npm_run_build.dependsOn(npm_install) | ||||
| npm_run_build.dependsOn(generateProtobufBundle) | ||||
| npm_start.dependsOn(generateProtobufBundle) | ||||
| 
 | ||||
| 
 | ||||
| // It would be nice if this worked, but it seems to get stuck: | ||||
| //npm_start.dependsOn(project(':bulletin-board-server').jettyStart); | ||||
| 
 | ||||
|  | @ -1,14 +0,0 @@ | |||
| import { BulletinBoardServerFrontendPage } from './app.po'; | ||||
| 
 | ||||
| describe('bulletin-board-server-frontend App', () => { | ||||
|   let page: BulletinBoardServerFrontendPage; | ||||
| 
 | ||||
|   beforeEach(() => { | ||||
|     page = new BulletinBoardServerFrontendPage(); | ||||
|   }); | ||||
| 
 | ||||
|   it('should display welcome message', () => { | ||||
|     page.navigateTo(); | ||||
|     expect(page.getParagraphText()).toEqual('Welcome to app!!'); | ||||
|   }); | ||||
| }); | ||||
|  | @ -1,11 +0,0 @@ | |||
| import { browser, by, element } from 'protractor'; | ||||
| 
 | ||||
| export class BulletinBoardServerFrontendPage { | ||||
|   navigateTo() { | ||||
|     return browser.get('/'); | ||||
|   } | ||||
| 
 | ||||
|   getParagraphText() { | ||||
|     return element(by.css('app-root h1')).getText(); | ||||
|   } | ||||
| } | ||||
|  | @ -1,13 +0,0 @@ | |||
| { | ||||
|   "extends": "../tsconfig.json", | ||||
|   "compilerOptions": { | ||||
|     "outDir": "../out-tsc/e2e", | ||||
|     "module": "commonjs", | ||||
|     "target": "es5", | ||||
|     "types": [ | ||||
|       "jasmine", | ||||
|       "jasminewd2", | ||||
|       "node" | ||||
|     ] | ||||
|   } | ||||
| } | ||||
|  | @ -1,32 +0,0 @@ | |||
| #!/usr/bin/env node
 | ||||
| const { execFileSync } = require('child_process'); | ||||
| const  path  = require('path'); | ||||
| 
 | ||||
| const isWin = (process.platform === 'win32'); | ||||
| 
 | ||||
| const bundlejs = 'src/app/bundle.js' | ||||
| const bundlets = 'src/app/bundle.d.ts' | ||||
| 
 | ||||
| const npm = isWin ? 'npm.cmd' : 'npm'; | ||||
| 
 | ||||
| 
 | ||||
| var args = process.argv.slice(2).map(function(x) { | ||||
|   return path.normalize(x); | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
| var pbjs_args = [ | ||||
|   '-t', 'static-module', '-w', 'commonjs', '-o', bundlejs | ||||
| ].concat(args); | ||||
| 
 | ||||
| console.log("Running pbjs " + pbjs_args.join(" ")); | ||||
| var out = execFileSync(npm, ['run', 'pbjs', '--'].concat(pbjs_args)); | ||||
| console.log("" + out); | ||||
| 
 | ||||
| var pbts_args = [ | ||||
|   '-o', bundlets, bundlejs | ||||
| ]; | ||||
| 
 | ||||
| console.log("Running pbts " + pbts_args.join(" ")); | ||||
| out = execFileSync(npm, ['run', 'pbts', '--'].concat(pbts_args)); | ||||
| console.log("" + out); | ||||
|  | @ -1 +0,0 @@ | |||
| ../gradlew | ||||
|  | @ -1,33 +0,0 @@ | |||
| // Karma configuration file, see link for more information
 | ||||
| // https://karma-runner.github.io/0.13/config/configuration-file.html
 | ||||
| 
 | ||||
| module.exports = function (config) { | ||||
|   config.set({ | ||||
|     basePath: '', | ||||
|     frameworks: ['jasmine', '@angular/cli'], | ||||
|     plugins: [ | ||||
|       require('karma-jasmine'), | ||||
|       require('karma-chrome-launcher'), | ||||
|       require('karma-jasmine-html-reporter'), | ||||
|       require('karma-coverage-istanbul-reporter'), | ||||
|       require('@angular/cli/plugins/karma') | ||||
|     ], | ||||
|     client:{ | ||||
|       clearContext: false // leave Jasmine Spec Runner output visible in browser
 | ||||
|     }, | ||||
|     coverageIstanbulReporter: { | ||||
|       reports: [ 'html', 'lcovonly' ], | ||||
|       fixWebpackSourcePaths: true | ||||
|     }, | ||||
|     angularCli: { | ||||
|       environment: 'dev' | ||||
|     }, | ||||
|     reporters: ['progress', 'kjhtml'], | ||||
|     port: 9876, | ||||
|     colors: true, | ||||
|     logLevel: config.LOG_INFO, | ||||
|     autoWatch: true, | ||||
|     browsers: ['Chrome'], | ||||
|     singleRun: false | ||||
|   }); | ||||
| }; | ||||
|  | @ -1,53 +0,0 @@ | |||
| { | ||||
|   "name": "bulletin-board-server-frontend", | ||||
|   "version": "0.0.0", | ||||
|   "license": "MIT", | ||||
|   "scripts": { | ||||
|     "ng": "ng", | ||||
|     "start": "ng serve --proxy-config proxy.conf.json", | ||||
|     "build": "ng build", | ||||
|     "test": "ng test", | ||||
|     "lint": "ng e2e", | ||||
|     "pbts": "pbts", | ||||
|     "pbjs": "pbjs", | ||||
|     "protoc" : "node ./generate-protobuf.js" | ||||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@angular/animations": "^4.0.0", | ||||
|     "@angular/common": "^4.0.0", | ||||
|     "@angular/compiler": "^4.0.0", | ||||
|     "@angular/core": "^4.0.0", | ||||
|     "@angular/forms": "^4.0.0", | ||||
|     "@angular/http": "^4.0.0", | ||||
|     "@angular/platform-browser": "^4.0.0", | ||||
|     "@angular/platform-browser-dynamic": "^4.0.0", | ||||
|     "@angular/router": "^4.0.0", | ||||
|     "@types/long": "^3.0.31", | ||||
|     "core-js": "^2.4.1", | ||||
|     "protobufjs": "^6.8.0", | ||||
|     "rxjs": "^5.1.0", | ||||
|     "zone.js": "^0.8.4" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@angular/cli": "^1.2.0", | ||||
|     "@angular/compiler-cli": "^4.0.0", | ||||
|     "@angular/language-service": "^4.0.0", | ||||
|     "@types/jasmine": "~2.5.53", | ||||
|     "@types/jasminewd2": "~2.0.2", | ||||
|     "@types/node": "~6.0.60", | ||||
|     "codelyzer": "~3.0.1", | ||||
|     "jasmine-core": "~2.6.2", | ||||
|     "jasmine-spec-reporter": "~4.1.0", | ||||
|     "karma": "~1.7.0", | ||||
|     "karma-chrome-launcher": "~2.1.1", | ||||
|     "karma-cli": "~1.0.1", | ||||
|     "karma-coverage-istanbul-reporter": "^1.2.1", | ||||
|     "karma-jasmine": "~1.1.0", | ||||
|     "karma-jasmine-html-reporter": "^0.2.2", | ||||
|     "protractor": "~5.1.2", | ||||
|     "ts-node": "~3.0.4", | ||||
|     "tslint": "~5.3.2", | ||||
|     "typescript": "~2.3.3" | ||||
|   } | ||||
| } | ||||
|  | @ -1,28 +0,0 @@ | |||
| // Protractor configuration file, see link for more information
 | ||||
| // https://github.com/angular/protractor/blob/master/lib/config.ts
 | ||||
| 
 | ||||
| const { SpecReporter } = require('jasmine-spec-reporter'); | ||||
| 
 | ||||
| exports.config = { | ||||
|   allScriptsTimeout: 11000, | ||||
|   specs: [ | ||||
|     './e2e/**/*.e2e-spec.ts' | ||||
|   ], | ||||
|   capabilities: { | ||||
|     'browserName': 'chrome' | ||||
|   }, | ||||
|   directConnect: true, | ||||
|   baseUrl: 'http://localhost:4200/', | ||||
|   framework: 'jasmine', | ||||
|   jasmineNodeOpts: { | ||||
|     showColors: true, | ||||
|     defaultTimeoutInterval: 30000, | ||||
|     print: function() {} | ||||
|   }, | ||||
|   onPrepare() { | ||||
|     require('ts-node').register({ | ||||
|       project: 'e2e/tsconfig.e2e.json' | ||||
|     }); | ||||
|     jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); | ||||
|   } | ||||
| }; | ||||
|  | @ -1,6 +0,0 @@ | |||
| { | ||||
|   "/bbserver" : { | ||||
|     "target" : "http://localhost:8081/", | ||||
|     "secure" : false | ||||
|   } | ||||
| } | ||||
|  | @ -1,12 +0,0 @@ | |||
| <!--The content below is only a placeholder and can be replaced.--> | ||||
| <div style="text-align:center"> | ||||
|   <h1> | ||||
|     Welcome to {{title}}!! | ||||
|   </h1> | ||||
| 
 | ||||
|   <h3>Current status: {{status}}</h3> | ||||
|   <img width="300" src="assets/meerkat-logo.svg"> | ||||
|   <hr> | ||||
|   {{errorMessage}} | ||||
| </div> | ||||
| 
 | ||||
|  | @ -1,32 +0,0 @@ | |||
| import { TestBed, async } from '@angular/core/testing'; | ||||
| 
 | ||||
| import { AppComponent } from './app.component'; | ||||
| 
 | ||||
| describe('AppComponent', () => { | ||||
|   beforeEach(async(() => { | ||||
|     TestBed.configureTestingModule({ | ||||
|       declarations: [ | ||||
|         AppComponent | ||||
|       ], | ||||
|     }).compileComponents(); | ||||
|   })); | ||||
| 
 | ||||
|   it('should create the app', async(() => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     const app = fixture.debugElement.componentInstance; | ||||
|     expect(app).toBeTruthy(); | ||||
|   })); | ||||
| 
 | ||||
|   it(`should have as title 'app'`, async(() => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     const app = fixture.debugElement.componentInstance; | ||||
|     expect(app.title).toEqual('app'); | ||||
|   })); | ||||
| 
 | ||||
|   it('should render title in a h1 tag', async(() => { | ||||
|     const fixture = TestBed.createComponent(AppComponent); | ||||
|     fixture.detectChanges(); | ||||
|     const compiled = fixture.debugElement.nativeElement; | ||||
|     expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!!'); | ||||
|   })); | ||||
| }); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
		Reference in New Issue