Compare commits

..
85 Commits
Author SHA1 Message Date
NotAlexNoyle 0cb4a1dc54 Exclude CODEOWNERS from language statistics 2026-10-27 08:25:16 -04:00
NotAlexNoyle 9ea469dbd7 Add CODEOWNERS (one staff approval) 2026-10-27 08:25:15 -04:00
NotAlexNoyle 011bc26a1a Update token syntax 2026-07-30 14:19:30 -04:00
NotAlexNoyle c697099eeb 1.0.2 - Suppress vote requests over 256 bytes 2026-04-06 03:01:07 -04:00
NotAlexNoyle 4a90995589 Use live TrueOG Network config file 2026-03-09 07:24:50 -04:00
NotAlexNoyle 6a98e18a38 Use TrueOG Network live config 2026-02-23 06:30:48 -05:00
NotAlexNoyle fd859d839d Fix jar 2025-12-18 13:15:59 -05:00
NotAlexNoyle 6085ffbcd3 Update shadow, remove testbenches 2025-11-20 07:17:09 -05:00
NotAlexNoyle 53bb8c938b Standardize gradle build 2025-09-03 12:15:21 -04:00
NotAlexNoyle b771bcb095 Fix all gradle deprecations 2025-08-27 11:44:38 -04:00
NotAlexNoyle fdb1b65159 Update gradle to 8.14.2, add auto formatting 2025-06-29 18:04:49 -04:00
NotAlexNoyle bf0de0a879 Update gradle to 8.14.2 2025-06-18 18:01:01 -04:00
NotAlexNoyle 6bdb2edb66 Update gradle 2025-06-11 15:08:10 -04:00
NotAlexNoyle 4a89704de6 Clean up eclipse files 2025-06-11 14:38:13 -04:00
NotAlexNoyle 999c9cd849 Fix build 2024-12-07 02:52:48 -05:00
NotAlexNoyle f41f0e6fef Remove println 2024-09-14 05:00:21 +00:00
NotAlexNoyle a3a73d9466 Fix typo 2024-09-07 18:02:59 +00:00
NotAlexNoyle ed1e368aae 1.19.4 Release 2024-09-07 17:58:46 +00:00
Stampede 4df84efdcd Version bump 2023-07-21 11:24:32 -05:00
Stampede 95fc2368a5 1.20.1 updates 2023-07-21 11:13:31 -05:00
Andrew Steinborn 9d69da2dab Demote errors in the fabric port 2023-05-13 23:04:41 -04:00
Andrew Steinborn 65007e248e Add basic Folia support
Untested, but should fix #321 since this is the only component of the plugin that explicitly demands the server thread
2023-05-13 23:03:49 -04:00
Andrew Steinborn f1f564d67a Merge pull request #322 from DrexHD/feature/fabric
Add fabric support
2023-05-13 22:57:03 -04:00
Drex f80de12ae2 Implement requested changes 2023-05-12 00:36:38 +02:00
Andrew Steinborn acb19a520a Merge pull request #309 from SeineEloquenz/bad_votes_warning
Make unprocessable votes a warning
2023-05-10 01:17:54 -04:00
Drex 9c27071213 Add fabric support 2023-05-07 17:11:39 +02:00
Alexander Linder 0f6020818d Make unprocessable votes a warning 2022-09-05 18:11:58 +02:00
Andrew Steinborn fcf6c93404 Merge pull request #296 from JOO200/feat/velocity3
Update to velocity 3, Make Proxy Events cancellable
2022-04-11 22:36:49 -04:00
JOO200 3d0f255ab4 BungeeCord: VotifierEvent is not Async anymore 2022-04-09 10:09:55 +02:00
JOO200 ede5cae333 Update to Velocity 3.1.0, make Events on Proxy cancellable 2022-04-09 10:05:13 +02:00
Andrew Steinborn b8f9cb937a Merge pull request #295 from JOO200/feat/update
Updated NuVotifier and Dependencies
2022-04-08 22:53:17 -04:00
JOO200 1fae11c0ff Fixed wrong plugin informations for sponge 2022-04-01 08:27:53 +02:00
JOO200 41eb6327e6 Cleanup tests 2022-03-30 09:47:46 +02:00
JOO200 c723fc7eac Set Source compatibility to Java 8 2022-03-30 09:42:33 +02:00
JOO200 7812823367 Fix wrong version in nuvotifier-bungee, added universal back 2022-03-30 09:29:01 +02:00
JOO200 2d5c2c78d2 Cleanup build script 2022-03-30 09:28:13 +02:00
JOO200 6e64890c04 Updated NuVotifier and Dependencies
* Updated Gradle to version 7.4.0
* Updated Java to version 17
* Moved to Gradle build scripts
* Fixed publishing maven artifacts
* Removed universal file (better depend on the specific implementations)
2022-03-30 09:27:55 +02:00
Andrew Steinborn d269e9da6b Back to 3.0.0-SNAPSHOT 2021-07-06 03:55:08 -04:00
Andrew Steinborn 5466876f41 NuVotifier 2.7.3 2021-07-06 03:49:40 -04:00
Andrew Steinborn 4003006fa4 Insert additional delay if no votes were evicted yet 2021-07-02 21:20:35 -04:00
Andrew Steinborn 13d0cb1d57 Merge pull request #256 from A248/gradle-7
Update to Gradle 7
2021-06-18 10:08:52 +00:00
Andrew Steinborn 15ad7d5abc Fix broken exception logging 2021-06-18 06:06:32 -04:00
A248 8966fbf3d8 Update to Gradle 7 with updates to shadowJar, blossom, and spotbugs 2021-06-11 12:08:35 -04:00
Andrew Steinborn 616d25683f Prepare for Velocity 3.0.0 support
To so do, drop Velocity 1.0.x support.
2021-05-23 23:10:59 -04:00
Andrew Steinborn 669c8a769e Await termination of event loop groups on shutdown.
May fix #241
2021-05-22 12:57:09 -04:00
Andrew Steinborn 7b10eca263 Back to 3.0.0-SNAPSHOT 2021-01-26 13:27:46 -05:00
Andrew Steinborn d22f4cd58b NuVotifier 2.7.2 2021-01-26 13:23:46 -05:00
Andrew Steinborn 8ba7e87067 Some extra logging 2021-01-06 13:27:28 -05:00
Andrew Steinborn 5776acaf34 Merge pull request #221 from Janmm14/relocate-gson
Relocate gson
2021-01-06 12:52:56 -05:00
Andrew Steinborn 90f6ed8b55 Merge pull request #222 from Eufranio/master
Prevent NPE when the fallbackServer is null
2021-01-06 12:47:13 -05:00
Eufranio 616bd4ad01 Prevent NPE when the fallbackServer is null 2021-01-06 14:44:44 -03:00
Janmm14 448baf0329 Relocate gson 2021-01-06 16:45:30 +01:00
Andrew Steinborn 13fe852bcd Update dev network to 1.16.2 2020-09-03 15:52:17 -04:00
Andrew Steinborn c996e36b97 Don't re-register the raw data channel if it's already registered. Fixes #206 2020-08-14 14:36:01 -04:00
Andrew Steinborn ad9e6f5fe8 Do proper version replacement in Sponge/Velocity plugins using blossom 2020-07-13 13:22:24 -04:00
Andrew Steinborn 53eb9c8fec Update test network to 1.16.1 2020-07-07 22:18:49 -04:00
Andrew Steinborn 6dbda167d3 We support Bukkit 1.7.10 too. 2020-07-06 02:54:02 -04:00
Andrew Steinborn be9f92d5cd Bump back to 3.0.0-SNAPSHOT 2020-06-25 15:17:52 -04:00
Andrew Steinborn 96bf715fbc Bump to NuVotifier 2.7.1 2020-06-25 15:15:28 -04:00
Andrew Steinborn 628273413b Don't assume all channels will be NioSocketChannels. Fixes #196 2020-06-25 15:06:46 -04:00
Andrew Steinborn bbfa9e4aa9 Back to 3.0.0-SNAPSHOT 2020-06-24 22:39:54 -04:00
Andrew Steinborn a34b07f2d1 NuVotifier 2.7.0 2020-06-24 22:27:51 -04:00
Andrew Steinborn 1d90a59d5a Add more ArgsToVote tests. 2020-06-23 23:45:30 -04:00
Andrew Steinborn 2734836e58 Add a Sponge server to the testbed and fix one Sponge-related problem 2020-06-23 19:46:26 -04:00
Andrew Steinborn 768daf099f Make Velocity NuVotifier reload command consistent 2020-06-23 19:12:03 -04:00
Andrew Steinborn b6292667b8 Add Velocity testbed and fix some bugs in the Velocity port 2020-06-23 19:05:14 -04:00
Andrew Steinborn 8519535566 Do some test modernization, add some basic ArgsToVote tests 2020-06-23 18:46:58 -04:00
Andrew Steinborn 08e907678f Add additional debug output for pluginMessage forwarding 2020-06-23 18:25:27 -04:00
Andrew Steinborn 27b93ac045 Update dev environment to 1.15 2020-06-16 08:44:50 -04:00
Andrew Steinborn 97f4c91c24 Integrate SpotBugs and fix all errors. 2020-06-06 02:13:29 -04:00
Andrew Steinborn e919c845f7 Attempt to fix vote cache TTL
Instead of trying to expose this data by API, it is now internal to the vote caches.

The only substantial API change was the introduction of a copy constructor to the Vote object.
2020-06-04 00:06:09 -04:00
Andrew Steinborn 1fb35276f7 Support native reloading on BungeeCord, Sponge, and Velocity
/nvreload will remain supported to provide a consistent reload command for documentation, but it is now mostly only relevant for BungeeCord only.
2020-06-03 23:49:02 -04:00
Andrew Steinborn f5f596f21c Document that the Sponge event is fired sync now. 2020-06-03 23:37:41 -04:00
Andrew Steinborn 88523db213 Update issue templates 2020-05-30 23:53:06 -04:00
Andrew Steinborn 3599b0fa1b Restore compatibility with plugins that create Vote objects manually 2020-05-30 18:09:49 -04:00
Andrew Steinborn 950b543e98 Allow using up to 3 worker threads for NuVotifier 2020-05-14 01:38:24 -04:00
Andrew Steinborn e66927dd1a Add epoll transport support 2020-05-14 01:36:40 -04:00
Andrew Steinborn 98d4f69ffb Throttle errors and do general Netty optimizations
Closes #170
2020-05-14 00:58:18 -04:00
Andrew Steinborn ad44c76309 Fix build issues on Windows by just copying the file and not using a symbolic link 2020-05-11 14:24:39 -04:00
Andrew Steinborn 58de66699e Upgrade to Gradle 6.4 and resolve all deprecation issues. 2020-05-11 14:07:25 -04:00
Andrew Steinborn b81052327d Remove almost all use of Guava, closes #187
Guava has proven problematic on a number of occasions - it has caused issues with the Sponge port and there are incompatibilities with certain Forge-Bukkit hybrids. As we desire maximum compatibility, it is better simply to drop Guava altogether. Java 8+ already implements much of what was the bread-and-butter of Guava, or makes rolling our own implementation simpler.

The one place where Guava has been retained is in some configuration-loading code specific to Sponge, which can't be changed to use a non-Guava alternative.
2020-05-11 13:28:43 -04:00
Andrew Steinborn 59a3eff421 Clean up a little Netty code. 2020-05-08 16:50:13 -04:00
Andrew Steinborn 411a529e18 Notify users if they don't have plugins available to listen for votifier events on Bukkit. 2020-05-08 16:39:03 -04:00
Andrew Steinborn 4d6946ab2c Fix a typo 2020-05-08 16:29:20 -04:00
Andrew Steinborn d5ae57b28e Bump Netty to 4.1.49.Final
This could resolve (or greatly ameliorate) #170 but unverified.
2020-04-22 11:47:12 -04:00
101 changed files with 3849 additions and 894 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-1
View File
@@ -1 +0,0 @@
custom: https://www.paypal.me/ibj
+17
View File
@@ -7,6 +7,20 @@
# netbeans
/nbproject
# eclipse
api/.classpath
api/.project
api/.settings/
bukkit/.classpath
bukkit/.project
bukkit/.settings/
common/.classpath
common/.project
common/.settings/
universal/.classpath
universal/.project
universal/.settings/
# we use maven!
/build.xml
@@ -47,3 +61,6 @@ debug.txt
# gradle stuff
.gradle/
.gradletasknamecache
# fabric
fabric/run/
Vendored
-37
View File
@@ -1,37 +0,0 @@
pipeline {
agent {
docker { image 'openjdk:12-jdk' }
}
stages {
stage('Build') {
steps {
sh './gradlew build --no-daemon'
}
}
stage('Test') {
steps {
sh './gradlew test --no-daemon'
}
}
stage('Publish') {
when {
branch "master"
}
environment {
SNAPSHOT_REPO = credentials('ibj-nexus-snapshot-repo')
RELEASE_REPO = credentials('ibj-nexus-release-repo')
RAW_UPLOAD_PATH = credentials('ibj-nexus-raw-path')
REPO = credentials('ibj-nexus-access')
PUBLISH = 'true'
LATEST_BUILD = 'true'
}
steps {
sh './gradlew publishVersionedPublicationToIbjRepository'
sh './gradlew publishLatestToIbjBlockRaw'
}
}
}
}
+5 -19
View File
@@ -1,24 +1,10 @@
# NuVotifier
# Votifier-OG
NuVotifier is a secure alternative to using the original Votifier project.
NuVotifier will work in place of Votifier - any vote listener that supports
Votifier will also support NuVotifier.
Votifier-OG is a 1.19.4 fork of NuVotifier, which is a secure alternative to using the original Votifier project.
Votifier-OG will work in place of NuVotifier, and will work in place of Votifier - any vote listener that supports
Votifier will also support NuVotifier and Votifier-OG.
NuVotifier supports many different platforms using the same universal JAR:
+ Bukkit / Spigot / Paper >=1.8
+ Sponge 7
+ BungeeCord / Waterfall
+ Velocity
NuVotifier also adds forwarding and listener test commands not present in the
original version.
[Setup Guide](https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide)
[Troubleshooting Guide](https://github.com/NuVotifier/NuVotifier/wiki/Troubleshooting-Guide)
[Developer Information](https://github.com/NuVotifier/NuVotifier/wiki/Developer-Documentation)
Votifier-OG supports Purpur 1.19.4. It is maintained by [TrueOG Network](https://trueog.net/)
# License
+4
View File
@@ -0,0 +1,4 @@
# nuvotifier-api
Files in this directory have a strong API promise - they really shouldn't
change over time. Feel free to depend against it!
@@ -0,0 +1,233 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vexsoftware.votifier.model;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.Arrays;
import java.util.Base64;
/**
* A model for a vote.
*
* @author Blake Beaupain
*/
public class Vote {
/**
* The name of the vote service.
*/
private String serviceName;
/**
* The username of the voter.
*/
private String username;
/**
* The address of the voter.
*/
private String address;
/**
* The date and time of the vote.
*/
private String timeStamp;
private byte[] additionalData;
@Deprecated
public Vote() {}
public Vote(String serviceName, String username, String address, String timeStamp) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.additionalData = null;
}
public Vote(String serviceName, String username, String address, String timeStamp, byte[] additionalData) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
this.additionalData = additionalData == null ? null : additionalData.clone();
}
public Vote(Vote vote) {
this(
vote.getServiceName(),
vote.getUsername(),
vote.getAddress(),
vote.getTimeStamp(),
vote.getAdditionalData() == null
? null
: vote.getAdditionalData().clone());
}
private static String getTimestamp(JsonElement object) {
try {
return Long.toString(object.getAsLong());
} catch (Exception e) {
return object.getAsString();
}
}
public Vote(JsonObject jsonObject) {
this(
jsonObject.get("serviceName").getAsString(),
jsonObject.get("username").getAsString(),
jsonObject.get("address").getAsString(),
getTimestamp(jsonObject.get("timestamp")));
if (jsonObject.has("additionalData"))
additionalData =
Base64.getDecoder().decode(jsonObject.get("additionalData").getAsString());
}
@Override
public String toString() {
String data;
if (additionalData == null) data = "null";
else data = Base64.getEncoder().encodeToString(additionalData);
return "Vote (from:" + serviceName + " username:" + username
+ " address:" + address + " timeStamp:" + timeStamp
+ " additionalData:" + data + ")";
}
/**
* Sets the serviceName.
*
* @param serviceName The new serviceName
*/
@Deprecated
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
/**
* Gets the serviceName.
*
* @return The serviceName
*/
public String getServiceName() {
return serviceName;
}
/**
* Sets the username.
*
* @param username The new username
*/
@Deprecated
public void setUsername(String username) {
this.username = username;
}
/**
* Gets the username.
*
* @return The username
*/
public String getUsername() {
return username;
}
/**
* Sets the address.
*
* @param address The new address
*/
@Deprecated
public void setAddress(String address) {
this.address = address;
}
/**
* Gets the address.
*
* @return The address
*/
public String getAddress() {
return address;
}
/**
* Sets the time stamp.
*
* @param timeStamp The new time stamp
*/
@Deprecated
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
/**
* Gets the time stamp.
*
* @return The time stamp
*/
public String getTimeStamp() {
return timeStamp;
}
/**
* Returns additional data sent with the vote, if it exists.
*
* @return Additional data sent with the vote
*/
public byte[] getAdditionalData() {
return additionalData == null ? null : additionalData.clone();
}
public JsonObject serialize() {
JsonObject ret = new JsonObject();
ret.addProperty("serviceName", serviceName);
ret.addProperty("username", username);
ret.addProperty("address", address);
ret.addProperty("timestamp", timeStamp);
if (additionalData != null)
ret.addProperty("additionalData", Base64.getEncoder().encodeToString(additionalData));
return ret;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Vote)) return false;
Vote vote = (Vote) o;
if (!serviceName.equals(vote.serviceName)) return false;
if (!username.equals(vote.username)) return false;
if (!address.equals(vote.address)) return false;
if (!timeStamp.equals(vote.timeStamp)) return false;
return Arrays.equals(additionalData, vote.additionalData);
}
@Override
public int hashCode() {
int result = serviceName.hashCode();
result = 31 * result + username.hashCode();
result = 31 * result + address.hashCode();
result = 31 * result + timeStamp.hashCode();
return result;
}
}
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/bash
# Fetch all submodule content.
git submodule update --force --recursive --init
-44
View File
@@ -1,44 +0,0 @@
import org.apache.tools.ant.filters.ReplaceTokens
allprojects {
group 'io.ibj.nuvotifier'
version '3.0.0-SNAPSHOT'
}
subprojects {
apply plugin: 'java'
apply plugin: 'java-library'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven {
url "https://hub.spigotmc.org/nexus/content/repositories/snapshots"
}
maven {
url "https://oss.sonatype.org/content/repositories/snapshots"
}
maven {
url "https://repo.spongepowered.org/maven"
}
maven {
url "https://repo.velocitypowered.com/snapshots/"
}
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2"
testRuntime "org.junit.jupiter:junit-jupiter-engine:5.4.2"
testCompile "org.mockito:mockito-core:2.+"
}
processResources {
filter(ReplaceTokens, tokens: ["app.version": this.project.version])
}
test {
useJUnitPlatform()
}
}
+218
View File
@@ -0,0 +1,218 @@
import org.gradle.kotlin.dsl.provideDelegate
/* ------------------------------ Plugins ------------------------------ */
plugins {
id("java") // Import Java plugin.
id("java-library") // Import Java Library plugin.
id("com.diffplug.spotless") version "8.1.0" // Import Spotless plugin.
id("com.gradleup.shadow") version "8.3.9" // Import Shadow plugin.
id("checkstyle") // Import Checkstyle plugin.
eclipse // Import Eclipse plugin.
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
}
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
/* --------------------------- JDK / Kotlin ---------------------------- */
java {
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
toolchain { // Select Java toolchain.
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
}
}
kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "net.trueog.votifier-og" // Declare bundle identifier.
version = "1.0.2" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
inputs.properties(props) // Indicates to rerun if version changes.
filesMatching("plugin.yml") { expand(props) }
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
}
/* ---------------------------- Repos ---------------------------------- */
allprojects {
repositories {
mavenCentral() // Import the Maven Central Maven Repository.
gradlePluginPortal() // Import the Gradle Plugin Portal Maven Repository.
maven { url = uri("https://repo.purpurmc.org/snapshots") } // Import the PurpurMC Maven Repository.
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let { // TrueOG Bootstrap mavenLocal().
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
mavenLocal()
}
}
?: run {
logger.error("TrueOG Bootstrap not found, defaulting to ~/.m2 for mavenLocal()")
mavenLocal()
}
}
}
/* ---------------------- Java project deps ---------------------------- */
configurations.create("shadeOnly")
dependencies { implementation(project(":nuvotifier-bukkit")) }
apply(from = "eclipse.gradle.kts") // Import eclipse classpath support script.
/* ---------------------- Reproducible jars ---------------------------- */
allprojects {
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
}
/* ----------------------------- Shadow -------------------------------- */
tasks.shadowJar {
exclude("io.github.miniplaceholders.*") // Exclude the MiniPlaceholders package from being shadowed.
isEnableRelocation = true
relocationPrefix = "${project.group}.shadow"
archiveClassifier.set("") // Use empty string instead of null.
}
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
val copyBukkitJarToRoot by tasks.registering(Copy::class)
project(":nuvotifier-bukkit").pluginManager.withPlugin("com.gradleup.shadow") {
val bukkitShadowJarProvider = project(":nuvotifier-bukkit").tasks.named<Jar>("shadowJar")
copyBukkitJarToRoot.configure {
dependsOn(bukkitShadowJarProvider)
from(bukkitShadowJarProvider.flatMap { it.archiveFile })
into(layout.buildDirectory.dir("libs"))
rename { "Votifier-OG-${project.version}.jar" }
}
}
tasks.build {
dependsOn(tasks.spotlessApply, tasks.shadowJar, copyBukkitJarToRoot)
} // Build depends on spotless and shadow.
tasks.shadowJar { finalizedBy(copyBukkitJarToRoot) }
/* --------------------------- Javac opts ------------------------------- */
allprojects {
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters") // Enable reflection for java code.
options.isFork = true // Run javac in its own process.
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
options.encoding = "UTF-8" // Use UTF-8 file encoding.
}
}
/* ----------------------------- Auto Formatting ------------------------ */
spotless {
java {
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
removeUnusedImports() // Remove imports that aren't being called.
}
kotlinGradle {
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
}
}
checkstyle {
toolVersion = "10.18.1" // Declare checkstyle version to use.
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
}
tasks.named("compileJava") {
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
}
tasks.named("spotlessCheck") {
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
}
/* ------------------------------ Eclipse SHIM ------------------------- */
// This can't be put in eclipse.gradle.kts because Gradle is weird.
subprojects {
apply(plugin = "java-library")
apply(plugin = "eclipse")
eclipse.project.name = "${project.name}-${rootProject.name}"
tasks.withType<Jar>().configureEach { archiveBaseName.set("${project.name}-${rootProject.name}") }
}
/* ----------------------- Subproject configuration -------------------- */
subprojects {
java {
sourceCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.GRAAL_VM)
}
}
}
project(":nuvotifier-api") {
dependencies {
implementation("com.google.code.gson:gson:2.10.1")
testImplementation("junit:junit:4.13.2")
}
tasks.test { useJUnit() }
}
project(":nuvotifier-common") {
dependencies {
api(project(":nuvotifier-api"))
implementation(platform("io.netty:netty-bom:4.1.110.Final"))
implementation("io.netty:netty-handler")
implementation("io.netty:netty-transport-native-epoll:4.1.110.Final:linux-x86_64")
implementation("com.google.code.gson:gson:2.10.1")
}
}
project(":nuvotifier-bukkit") {
apply(plugin = "com.gradleup.shadow")
dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT")
implementation(project(":nuvotifier-api"))
implementation(project(":nuvotifier-common"))
}
tasks.named<ProcessResources>("processResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
val props =
mapOf(
"version" to rootProject.version,
"apiVersion" to apiVersion,
"internalVersion" to rootProject.version,
)
inputs.properties(props)
filesMatching("plugin.yml") { expand(props) }
from(rootProject.file("LICENSE")) { into("/") }
}
tasks.named<Jar>("jar") { manifest { attributes("Implementation-Version" to project.version) } }
tasks.named<Jar>("shadowJar") {
archiveBaseName.set("Votifier-OG")
archiveVersion.set(rootProject.version.toString())
archiveClassifier.set("")
}
}
@@ -0,0 +1,56 @@
import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.*
import org.jfrog.gradle.plugin.artifactory.dsl.ArtifactoryPluginConvention
import org.jfrog.gradle.plugin.artifactory.task.ArtifactoryTask
private const val ARTIFACTORY_CONTEXT_URL = "artifactory_contextUrl"
private const val ARTIFACTORY_USER = "artifactory_user"
private const val ARTIFACTORY_PASSWORD = "artifactory_password"
fun Project.applyRootArtifactoryConfig() {
if (!project.hasProperty(ARTIFACTORY_CONTEXT_URL)) ext[ARTIFACTORY_CONTEXT_URL] = "http://localhost"
if (!project.hasProperty(ARTIFACTORY_USER)) ext[ARTIFACTORY_USER] = "guest"
if (!project.hasProperty(ARTIFACTORY_PASSWORD)) ext[ARTIFACTORY_PASSWORD] = ""
apply(plugin = "com.jfrog.artifactory")
configure<ArtifactoryPluginConvention> {
setContextUrl("${project.property(ARTIFACTORY_CONTEXT_URL)}")
clientConfig.publisher.run {
repoKey = when {
"${project.version}".contains("SNAPSHOT") -> "private"
else -> "public"
}
username = "${project.property(ARTIFACTORY_USER)}"
password = "${project.property(ARTIFACTORY_PASSWORD)}"
isMaven = true
isIvy = false
}
}
tasks.named<ArtifactoryTask>("artifactoryPublish") {
isSkip = true
}
}
fun Project.applyCommonArtifactoryConfig() {
configure<PublishingExtension> {
publications {
register<MavenPublication>("maven") {
from(components["java"])
versionMapping {
usage("java-api") {
fromResolutionOf("runtimeClasspath")
}
usage("java-runtime") {
fromResolutionResult()
}
}
}
}
}
tasks.named<ArtifactoryTask>("artifactoryPublish") {
publications("maven")
}
}
+33
View File
@@ -0,0 +1,33 @@
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.repositories
import org.gradle.kotlin.dsl.the
fun Project.applyCommonConfiguration() {
group = rootProject.group
version = rootProject.version
repositories {
mavenCentral()
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
maven {
name = "Fabric"
url = uri("https://maven.fabricmc.net/")
}
}
configurations.all {
resolutionStrategy {
cacheChangingModulesFor(5, "MINUTES")
}
}
plugins.withId("java") {
the<JavaPluginExtension>().toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
}
@@ -0,0 +1,75 @@
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.api.tasks.testing.Test
import org.gradle.external.javadoc.StandardJavadocDocletOptions
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.withType
fun Project.applyCommonJavaConfiguration(sourcesJar: Boolean, javaRelease: Int = 8, banSlf4j: Boolean = true) {
applyCommonConfiguration()
apply(plugin = "eclipse")
apply(plugin = "idea")
tasks
.withType<JavaCompile>()
.matching { it.name == "compileJava" || it.name == "compileTestJava" }
.configureEach {
val disabledLint = listOf(
"processing", "path", "fallthrough", "serial"
)
options.release.set(javaRelease)
options.compilerArgs.addAll(listOf("-Xlint:all") + disabledLint.map { "-Xlint:-$it" })
options.isDeprecation = true
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
"compileOnly"("com.google.code.findbugs:jsr305:${Versions.FINDBUGS}")
"testImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.JUNIT}")
"testImplementation"("org.junit.jupiter:junit-jupiter-params:${Versions.JUNIT}")
"testImplementation"("org.mockito:mockito-core:${Versions.MOCKITO}")
"testImplementation"("org.mockito:mockito-junit-jupiter:${Versions.MOCKITO}")
"testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:${Versions.JUNIT}")
}
// Java 8 turns on doclint which we fail
tasks.withType<Javadoc>().configureEach {
options.encoding = "UTF-8"
(options as StandardJavadocDocletOptions).apply {
addStringOption("Xdoclint:none", "-quiet")
tags(
"apiNote:a:API Note:",
"implSpec:a:Implementation Requirements:",
"implNote:a:Implementation Note:"
)
}
}
configure<JavaPluginExtension> {
disableAutoTargetJvm()
withJavadocJar()
if (sourcesJar) {
withSourcesJar()
}
}
if (banSlf4j) {
configurations["compileClasspath"].apply {
resolutionStrategy.componentSelection {
withModule("org.slf4j:slf4j-api") {
reject("No SLF4J allowed on compile classpath")
}
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
import org.gradle.api.Project
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.the
val Project.ext: ExtraPropertiesExtension
get() = extensions.getByType()
val Project.sourceSets: SourceSetContainer
get() = the<JavaPluginExtension>().sourceSets
@@ -0,0 +1,42 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.Project
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
fun Project.applyPlatformAndCoreConfiguration(javaRelease: Int = 8) {
applyCommonConfiguration()
apply(plugin = "java")
apply(plugin = "maven-publish")
apply(plugin = "com.jfrog.artifactory")
applyCommonJavaConfiguration(
sourcesJar = true,
javaRelease = javaRelease,
banSlf4j = false
)
ext["internalVersion"] = "$version+${rootProject.ext["gitCommitHash"]}"
}
fun Project.applyShadowConfiguration() {
apply(plugin = "com.github.johnrengelman.shadow")
tasks.named<ShadowJar>("shadowJar") {
archiveClassifier.set("dist")
dependencies {
include(project(":nuvotifier-api"))
include(project(":nuvotifier-common"))
exclude("com.google.code.findbugs:jsr305")
}
exclude("GradleStart**")
exclude(".cache")
exclude("LICENSE*")
exclude("META-INF/maven/**")
}
val javaComponent = components["java"] as AdhocComponentWithVariants
// I don't think we want this published (it's the shadow jar)
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
skip()
}
}
+8
View File
@@ -0,0 +1,8 @@
object Versions {
const val JUNIT = "5.8.1"
const val MOCKITO = "4.3.1"
const val FINDBUGS = "3.0.2"
const val NETTYIO = "4.1.72.Final"
const val GSON = "2.9.0"
const val VELOCITY = "3.1.0"
}
-4
View File
@@ -1,4 +0,0 @@
dependencies {
api group: "org.spigotmc", name: "spigot-api", version: "1.8.8-R0.1-SNAPSHOT"
implementation project(":model")
}
+1
View File
@@ -0,0 +1 @@
/bin/
@@ -0,0 +1,44 @@
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import java.util.concurrent.TimeUnit;
import org.bukkit.scheduler.BukkitTask;
class BukkitScheduler implements VotifierScheduler {
private final NuVotifierBukkit plugin;
public BukkitScheduler(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
private int toTicks(int time, TimeUnit unit) {
return (int) (unit.toMillis(time) / 50);
}
@Override
public ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit) {
return new BukkitTaskWrapper(
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, toTicks(delay, unit)));
}
@Override
public ScheduledVotifierTask repeatOnPool(Runnable runnable, int delay, int repeat, TimeUnit unit) {
return new BukkitTaskWrapper(plugin.getServer()
.getScheduler()
.runTaskTimerAsynchronously(plugin, runnable, toTicks(delay, unit), toTicks(repeat, unit)));
}
private static class BukkitTaskWrapper implements ScheduledVotifierTask {
private final BukkitTask task;
private BukkitTaskWrapper(BukkitTask task) {
this.task = task;
}
@Override
public void cancel() {
task.cancel();
}
}
}
@@ -0,0 +1,417 @@
/*
* Copyright (C) 2012 Vex Software LLC
* This file is part of Votifier.
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.cmd.NVReloadCmd;
import com.vexsoftware.votifier.cmd.TestVoteCmd;
import com.vexsoftware.votifier.forwarding.BukkitPluginMessagingForwardingSink;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VotifierEvent;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAKeygen;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSink;
import com.vexsoftware.votifier.util.IOUtil;
import com.vexsoftware.votifier.util.KeyCreator;
import com.vexsoftware.votifier.util.TokenUtil;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.security.Key;
import java.security.KeyPair;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The main Votifier plugin class.
*
* @author Blake Beaupain
* @author Kramer Campbell
*/
public class NuVotifierBukkit extends JavaPlugin implements VoteHandler, VotifierPlugin, ForwardedVoteListener {
/**
* The server bootstrap.
*/
private VotifierServerBootstrap bootstrap;
/**
* The RSA key pair.
*/
private KeyPair keyPair;
/**
* Debug mode flag
*/
private boolean debug;
/**
* Keys used for websites.
*/
private Map<String, Key> tokens = new HashMap<>();
private ForwardingVoteSink forwardingMethod;
private VotifierScheduler scheduler;
private LoggingAdapter pluginLogger;
private boolean isFolia;
private boolean loadAndBind() {
try {
Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
isFolia = true;
getLogger().info("Using Folia; VotifierEvent will be fired asynchronously.");
} catch (ClassNotFoundException e) {
isFolia = false;
}
scheduler = new BukkitScheduler(this);
pluginLogger = new JavaUtilLogger(getLogger());
if (!getDataFolder().exists()) {
if (!getDataFolder().mkdir()) {
throw new RuntimeException("Unable to create the plugin data folder " + getDataFolder());
}
}
// Handle configuration.
File config = new File(getDataFolder(), "config.yml");
/*
* Use IP address from server.properties as a default for
* configurations. Do not use InetAddress.getLocalHost() as it most
* likely will return the main server address instead of the address
* assigned to the server.
*/
String hostAddr = Bukkit.getServer().getIp();
if (hostAddr == null || hostAddr.length() == 0) hostAddr = "0.0.0.0";
/*
* Create configuration file if it does not exists; otherwise, load it
*/
if (!config.exists()) {
try {
// First time run - do some initialization.
getLogger().info("Configuring Votifier for the first time...");
// Initialize the configuration file.
if (!config.createNewFile()) {
throw new IOException("Unable to create the config file at " + config);
}
// Load and manually replace variables in the configuration.
String cfgStr =
new String(IOUtil.readAllBytes(getResource("bukkitConfig.yml")), StandardCharsets.UTF_8);
String token = TokenUtil.newToken();
cfgStr = cfgStr.replace("%default_token%", token).replace("%ip%", hostAddr);
Files.copy(
new ByteArrayInputStream(cfgStr.getBytes(StandardCharsets.UTF_8)),
config.toPath(),
StandardCopyOption.REPLACE_EXISTING);
/*
* Remind hosted server admins to be sure they have the right
* port number.
*/
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Assigning NuVotifier to listen on port 8192. If you are hosting Craftbukkit on a");
getLogger().info("shared server please check with your hosting provider to verify that this port");
getLogger().info("is available for your use. Chances are that your hosting provider will assign");
getLogger().info("a different port, which you need to specify in config.yml");
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Your default NuVotifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "Error creating configuration file", ex);
return false;
}
}
YamlConfiguration cfg;
File rsaDirectory = new File(getDataFolder(), "rsa");
// Load configuration.
cfg = YamlConfiguration.loadConfiguration(config);
/*
* Create RSA directory and keys if it does not exist; otherwise, read
* keys.
*/
try {
if (!rsaDirectory.exists()) {
if (!rsaDirectory.mkdir()) {
throw new RuntimeException("Unable to create the RSA key folder " + rsaDirectory);
}
keyPair = RSAKeygen.generate(2048);
RSAIO.save(rsaDirectory, keyPair);
} else {
keyPair = RSAIO.load(rsaDirectory);
}
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "Error reading configuration file or RSA tokens", ex);
return false;
}
// the quiet flag always runs priority to the debug flag
if (cfg.isBoolean("quiet")) {
debug = !cfg.getBoolean("quiet");
} else {
// otherwise, default to being noisy
debug = cfg.getBoolean("debug", true);
}
// Load Votifier tokens.
ConfigurationSection tokenSection = cfg.getConfigurationSection("tokens");
if (tokenSection != null) {
Map<String, Object> websites = tokenSection.getValues(false);
for (Map.Entry<String, Object> website : websites.entrySet()) {
tokens.put(
website.getKey(),
KeyCreator.createKeyFrom(website.getValue().toString()));
getLogger().info("Loaded token for website: " + website.getKey());
}
} else {
String token = TokenUtil.newToken();
tokenSection = cfg.createSection("tokens");
tokenSection.set("default", token);
tokens.put("default", KeyCreator.createKeyFrom(token));
try {
cfg.save(config);
} catch (IOException e) {
getLogger().log(Level.SEVERE, "Error generating Votifier token", e);
return false;
}
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("No tokens were found in your configuration, so we've generated one for you.");
getLogger().info("Your default Votifier token is " + token + ".");
getLogger().info("You will need to provide this token when you submit your server to a voting");
getLogger().info("list.");
getLogger().info("------------------------------------------------------------------------------");
}
// Initialize the receiver.
final String host = cfg.getString("host", hostAddr);
final int port = cfg.getInt("port", 8192);
if (!debug) getLogger().info("QUIET mode enabled!");
if (port >= 0) {
final boolean disablev1 = cfg.getBoolean("disable-v1-protocol");
if (disablev1) {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Votifier protocol v1 parsing has been disabled. Most voting websites do not");
getLogger().info("currently support the modern Votifier protocol in NuVotifier.");
getLogger().info("------------------------------------------------------------------------------");
}
this.bootstrap = new VotifierServerBootstrap(host, port, this, disablev1);
this.bootstrap.start(error -> {});
} else {
getLogger().info("------------------------------------------------------------------------------");
getLogger().info("Your Votifier port is less than 0, so we assume you do NOT want to start the");
getLogger().info("votifier port server! Votifier will not listen for votes over any port, and");
getLogger().info("will only listen for pluginMessaging forwarded votes!");
getLogger().info("------------------------------------------------------------------------------");
}
ConfigurationSection forwardingConfig = cfg.getConfigurationSection("forwarding");
if (forwardingConfig != null) {
String method = forwardingConfig
.getString("method", "none")
.toLowerCase(); // Default to lower case for case-insensitive searches
if ("none".equals(method)) {
getLogger()
.info("Method none selected for vote forwarding: Votes will not be received from a forwarder.");
} else if ("pluginmessaging".equals(method)) {
String channel = forwardingConfig.getString("pluginMessaging.channel", "NuVotifier");
try {
forwardingMethod = new BukkitPluginMessagingForwardingSink(this, channel, this);
getLogger().info("Receiving votes over PluginMessaging channel '" + channel + "'.");
} catch (RuntimeException e) {
getLogger()
.log(Level.SEVERE, "NuVotifier could not set up PluginMessaging for vote forwarding!", e);
}
} else {
getLogger()
.severe("No vote forwarding method '" + method + "' known. Defaulting to noop implementation.");
}
}
return true;
}
private void halt() {
// Shut down the network handlers.
if (bootstrap != null) {
bootstrap.shutdown();
bootstrap = null;
}
if (forwardingMethod != null) {
forwardingMethod.halt();
forwardingMethod = null;
}
}
@Override
public void onEnable() {
getCommand("nvreload").setExecutor(new NVReloadCmd(this));
getCommand("testvote").setExecutor(new TestVoteCmd(this));
if (!loadAndBind()) {
gracefulExit();
setEnabled(false); // safer to just bomb out
}
}
@Override
public void onDisable() {
halt();
getLogger().info("Votifier disabled.");
}
public boolean reload() {
try {
halt();
} catch (Exception ex) {
getLogger().log(Level.SEVERE, "On halt, an exception was thrown. This may be fine!", ex);
}
if (loadAndBind()) {
getLogger().info("Reload was successful.");
return true;
} else {
try {
halt();
getLogger()
.log(
Level.SEVERE,
"On reload, there was a problem with the configuration. Votifier currently does nothing!");
} catch (Exception ex) {
getLogger()
.log(
Level.SEVERE,
"On reload, there was a problem loading, and we could not re-halt the server. Votifier is in an unstable state!",
ex);
}
return false;
}
}
private void gracefulExit() {
getLogger().log(Level.SEVERE, "Votifier did not initialize properly!");
}
@Override
public LoggingAdapter getPluginLogger() {
return pluginLogger;
}
@Override
public VotifierScheduler getScheduler() {
return scheduler;
}
public boolean isDebug() {
return debug;
}
@Override
public Map<String, Key> getTokens() {
return tokens;
}
@Override
public KeyPair getProtocolV1Key() {
return keyPair;
}
@Override
public void onVoteReceived(final Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress) {
if (debug) {
getLogger()
.info("Got a " + protocolVersion.humanReadable + " vote record from " + remoteAddress + " -> "
+ vote);
}
fireVotifierEvent(vote);
}
@Override
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
if (debug) {
if (alreadyHandledVote) {
getLogger()
.log(
Level.WARNING,
"Vote processed, however an exception " + "occurred with a vote from " + remoteAddress,
throwable);
} else {
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress, throwable);
}
} else if (!alreadyHandledVote) {
getLogger().log(Level.WARNING, "Unable to process vote from " + remoteAddress);
}
}
@Override
public void onForward(final Vote v) {
if (debug) {
getLogger().info("Got a forwarded vote -> " + v);
}
fireVotifierEvent(v);
}
private void fireVotifierEvent(Vote vote) {
if (VotifierEvent.getHandlerList().getRegisteredListeners().length == 0) {
getLogger()
.log(
Level.SEVERE,
"A vote was received, but you don't have any listeners available to listen for it.");
getLogger()
.log(
Level.SEVERE,
"See https://github.com/NuVotifier/NuVotifier/wiki/Setup-Guide#vote-listeners for");
getLogger().log(Level.SEVERE, "a list of listeners you can configure.");
}
if (!isFolia) {
getServer()
.getScheduler()
.runTask(this, () -> getServer().getPluginManager().callEvent(new VotifierEvent(vote)));
} else {
getServer().getScheduler().runTaskAsynchronously(this, () -> getServer()
.getPluginManager()
.callEvent(new VotifierEvent(vote, true)));
}
}
}
@@ -0,0 +1,36 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class NVReloadCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public NVReloadCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.reload")) {
sender.sendMessage(Component.text("Reloading NuVotifier...").color(NamedTextColor.GRAY));
if (plugin.reload()) {
sender.sendMessage(
Component.text("NuVotifier has been reloaded!").color(NamedTextColor.DARK_GREEN));
} else {
sender.sendMessage(
Component.text("Looks like there was a problem reloading NuVotifier, check the console!")
.color(NamedTextColor.DARK_RED));
}
} else {
sender.sendMessage(
Component.text("You do not have permission to do this!").color(NamedTextColor.DARK_RED));
}
return true;
}
}
@@ -0,0 +1,46 @@
package com.vexsoftware.votifier.cmd;
import com.vexsoftware.votifier.NuVotifierBukkit;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.ArgsToVote;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class TestVoteCmd implements CommandExecutor {
private final NuVotifierBukkit plugin;
public TestVoteCmd(NuVotifierBukkit plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender.hasPermission("nuvotifier.testvote")) {
Vote v;
try {
v = ArgsToVote.parse(args);
} catch (IllegalArgumentException e) {
sender.sendMessage(
Component.text("Error while parsing arguments to create test vote: " + e.getMessage())
.color(NamedTextColor.DARK_RED));
sender.sendMessage(Component.text(
"Usage hint: /testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]")
.color(NamedTextColor.GRAY));
return true;
}
plugin.onVoteReceived(v, VotifierSession.ProtocolVersion.TEST, "localhost.test");
sender.sendMessage(
Component.text("Test vote executed: " + v.toString()).color(NamedTextColor.GREEN));
} else {
sender.sendMessage(
Component.text("You do not have permission to do this!").color(NamedTextColor.DARK_RED));
}
return true;
}
}
@@ -0,0 +1,39 @@
package com.vexsoftware.votifier.forwarding;
import com.vexsoftware.votifier.support.forwarding.AbstractPluginMessagingForwardingSink;
import com.vexsoftware.votifier.support.forwarding.ForwardedVoteListener;
import java.util.Objects;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
public class BukkitPluginMessagingForwardingSink extends AbstractPluginMessagingForwardingSink
implements PluginMessageListener {
public BukkitPluginMessagingForwardingSink(Plugin p, String channel, ForwardedVoteListener listener) {
super(listener);
Objects.requireNonNull(channel, "Channel cannot be null."); // Replaced Validate.notNull
this.channel = channel;
Bukkit.getMessenger().registerIncomingPluginChannel(p, channel, this);
this.p = p;
}
private final Plugin p;
private final String channel;
@Override
public void halt() {
Bukkit.getMessenger().unregisterIncomingPluginChannel(p, channel, this);
}
@Override
public void onPluginMessageReceived(String s, Player player, byte[] bytes) {
try {
this.handlePluginMessage(bytes);
} catch (Exception e) {
p.getLogger().log(Level.SEVERE, "There was an unknown error when processing a forwarded vote.", e);
}
}
}
@@ -1,12 +1,11 @@
package com.vexsoftware.votifier.model;
import io.ibj.nuvotifier.model.NuVote;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* {@code VotifierEvent} is a custom Bukkit event class that is sent
* synchronously to CraftBukkit's main thread allowing other plugins to listener
* synchronously to CraftBukkit's main thread allowing other plugins to listen
* for votes.
*
* @author frelling
@@ -20,14 +19,25 @@ public class VotifierEvent extends Event {
/**
* Encapsulated vote record.
*/
private NuVote vote;
private Vote vote;
/**
* Constructs a vote event that encapsulated the given vote record.
*
* @param vote vote record
*/
public VotifierEvent(final NuVote vote) {
public VotifierEvent(final Vote vote) {
this.vote = vote;
}
/**
* Constructs a vote event that encapsulated the given vote record.
*
* @param vote vote record
* @param async whether the event will be fired asynchronously
*/
public VotifierEvent(final Vote vote, final boolean async) {
super(async);
this.vote = vote;
}
@@ -0,0 +1,25 @@
# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses)
# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this.
host: 0.0.0.0
# Port to listen for new votes on
port: 8192
# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this
# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are
# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security.
disable-v1-protocol: false
# All tokens, labeled by the serviceName of each server list.
tokens:
# Default token for all server lists, if another isn't supplied.
default: "^{VOTIFIER_OG_TOKEN}"
# Configuration section for all vote forwarding to NuVotifier
forwarding:
# Sets whether to set up a remote method for fowarding. Supported methods:
# - none - Does not set up a forwarding method.
# - pluginMessaging - Sets up plugin messaging
method: none
pluginMessaging:
channel: nuvotifier:votes
+26
View File
@@ -0,0 +1,26 @@
name: Votifier-OG
main: com.vexsoftware.votifier.NuVotifierBukkit
version: "${version}"
description: A plugin that gets notified when votes are made for the server on toplists.
authors: [Ichbinjoe, blakeman8192, Kramer, tuxed, NotAlexNoyle]
api-version: 1.19
commands:
nvreload:
description: Reloads the NuVotifier configuration
permission: nuvotifier.reload
permission-message: "You do not have permission to run this command."
usage: "/nvreload"
testvote:
description: Sends a test vote to the server
permission: nuvotifier.testvote
permission-message: "You do not have permission to run this command."
usage: "/testvote [username] [serviceName=?] [username=?] [address=?] [localTimestamp=?] [timestamp=?]"
permissions:
nuvotifier.reload:
description: Allows you to reload the NuVotifier plugin
default: op
nuvotifier.testvote:
description: Allows you to send a test vote
default: op
-4
View File
@@ -1,4 +0,0 @@
dependencies {
api group: "net.md-5", name: "bungeecord-api", version: "1.8-SNAPSHOT"
implementation project(":model")
}
@@ -1,23 +0,0 @@
package com.vexsoftware.votifier.bungee.events;
import io.ibj.nuvotifier.model.NuVote;
import net.md_5.bungee.api.plugin.Event;
/**
* {@code VotifierEvent} is a custom BungeeCord event class that is sent
* asynchronously allowing other plugins to listen for votes.
*/
public class VotifierEvent extends Event {
/**
* The vote.
**/
private final NuVote vote;
public VotifierEvent(NuVote vote) {
this.vote = vote;
}
public NuVote getVote() {
return vote;
}
}
+1
View File
@@ -0,0 +1 @@
/bin/
@@ -0,0 +1,14 @@
package com.vexsoftware.votifier;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
public interface VoteHandler {
void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress)
throws Exception;
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
throw new RuntimeException("Unimplemented onError handler");
}
}
@@ -0,0 +1,137 @@
package com.vexsoftware.votifier.net;
import com.vexsoftware.votifier.net.protocol.VoteInboundHandler;
import com.vexsoftware.votifier.net.protocol.VotifierGreetingHandler;
import com.vexsoftware.votifier.net.protocol.VotifierProtocolDifferentiator;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.ProxyForwardingVoteSource;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.epoll.EpollSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class VotifierServerBootstrap {
private static final boolean USE_EPOLL = Epoll.isAvailable();
private final String host;
private final int port;
private final EventLoopGroup bossLoopGroup;
private final EventLoopGroup eventLoopGroup;
private final VotifierPlugin plugin;
private final boolean v1Disable;
private Channel serverChannel;
public VotifierServerBootstrap(String host, int port, VotifierPlugin plugin, boolean v1Disable) {
this.host = host;
this.port = port;
this.plugin = plugin;
this.v1Disable = v1Disable;
if (USE_EPOLL) {
this.bossLoopGroup = new EpollEventLoopGroup(1, createThreadFactory("Votifier epoll boss"));
this.eventLoopGroup = new EpollEventLoopGroup(3, createThreadFactory("Votifier epoll worker"));
plugin.getPluginLogger().info("Using epoll transport to accept votes.");
} else {
this.bossLoopGroup = new NioEventLoopGroup(1, createThreadFactory("Votifier NIO boss"));
this.eventLoopGroup = new NioEventLoopGroup(3, createThreadFactory("Votifier NIO worker"));
plugin.getPluginLogger().info("Using NIO transport to accept votes.");
}
}
private static ThreadFactory createThreadFactory(String name) {
return runnable -> {
FastThreadLocalThread thread = new FastThreadLocalThread(runnable, name);
thread.setDaemon(true);
return thread;
};
}
public void start(Consumer<Throwable> error) {
Objects.requireNonNull(error, "error");
VoteInboundHandler voteInboundHandler = new VoteInboundHandler(plugin);
new ServerBootstrap()
.channel(USE_EPOLL ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.group(bossLoopGroup, eventLoopGroup)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.attr(VotifierSession.KEY).set(new VotifierSession());
channel.attr(VotifierPlugin.KEY).set(plugin);
channel.pipeline().addLast("greetingHandler", VotifierGreetingHandler.INSTANCE);
channel.pipeline()
.addLast(
"protocolDifferentiator",
new VotifierProtocolDifferentiator(false, !v1Disable));
channel.pipeline().addLast("voteHandler", voteInboundHandler);
}
})
.bind(host, port)
.addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
serverChannel = future.channel();
plugin.getPluginLogger()
.info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
error.accept(null);
} else {
SocketAddress socketAddress = future.channel().localAddress();
if (socketAddress == null) {
socketAddress = new InetSocketAddress(host, port);
}
plugin.getPluginLogger()
.error("Votifier was not able to bind to " + socketAddress.toString(), future.cause());
error.accept(future.cause());
}
});
}
private Bootstrap client() {
return new Bootstrap()
.channel(USE_EPOLL ? EpollSocketChannel.class : NioSocketChannel.class)
.group(eventLoopGroup);
}
public ProxyForwardingVoteSource createForwardingSource(
List<ProxyForwardingVoteSource.BackendServer> backendServers, VoteCache voteCache) {
return new ProxyForwardingVoteSource(plugin, this::client, backendServers, voteCache);
}
public void shutdown() {
if (serverChannel != null) {
try {
serverChannel.close().syncUninterruptibly();
} catch (Exception e) {
plugin.getPluginLogger().error("Unable to shutdown server channel", e);
}
}
eventLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
try {
bossLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
eventLoopGroup.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@@ -1,5 +1,6 @@
package io.ibj.nuvotifier.legacyserver;
package com.vexsoftware.votifier.net;
import com.vexsoftware.votifier.util.TokenUtil;
import io.netty.util.AttributeKey;
public class VotifierSession {
@@ -28,8 +29,7 @@ public class VotifierSession {
}
public void completeVote() {
if (hasCompletedVote)
throw new IllegalStateException("Protocol completed vote twice!");
if (hasCompletedVote) throw new IllegalStateException("Protocol completed vote twice!");
hasCompletedVote = true;
}
@@ -45,6 +45,7 @@ public class VotifierSession {
TEST("test");
public final String humanReadable;
ProtocolVersion(String hr) {
this.humanReadable = hr;
}
@@ -1,26 +1,34 @@
package io.ibj.nuvotifier.legacyserver.protocol;
package com.vexsoftware.votifier.net.protocol;
import com.google.gson.JsonObject;
import io.ibj.nuvotifier.legacyserver.GsonInst;
import io.ibj.nuvotifier.legacyserver.VotifierSession;
import io.ibj.nuvotifier.model.NuVote;
import io.ibj.nuvotifier.platform.VoteHandler;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.GsonInst;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.concurrent.atomic.AtomicLong;
public class VoteInboundHandler extends SimpleChannelInboundHandler<NuVote> {
@ChannelHandler.Sharable
public class VoteInboundHandler extends SimpleChannelInboundHandler<Vote> {
private final VoteHandler handler;
private final AtomicLong lastError;
private final AtomicLong errorsSent;
public VoteInboundHandler(VoteHandler handler) {
this.handler = handler;
this.lastError = new AtomicLong();
this.errorsSent = new AtomicLong();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, final NuVote vote) throws Exception {
protected void channelRead0(ChannelHandlerContext ctx, final Vote vote) throws Exception {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
handler.onVote(vote);
handler.onVoteReceived(
vote, session.getVersion(), ctx.channel().remoteAddress().toString());
session.completeVote();
if (session.getVersion() == VotifierSession.ProtocolVersion.ONE) {
@@ -36,7 +44,8 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<NuVote> {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
handler.onError(cause, session.hasCompletedVote(), ctx.channel().remoteAddress().toString());
String remoteAddr = ctx.channel().remoteAddress().toString();
boolean hasCompletedVote = session.hasCompletedVote();
if (session.getVersion() == VotifierSession.ProtocolVersion.TWO) {
JsonObject object = new JsonObject();
@@ -47,5 +56,22 @@ public class VoteInboundHandler extends SimpleChannelInboundHandler<NuVote> {
} else {
ctx.close();
}
if (!willThrottleErrorLogging()) {
handler.onError(cause, hasCompletedVote, remoteAddr);
}
}
private boolean willThrottleErrorLogging() {
long lastErrorAt = this.lastError.get();
long now = System.currentTimeMillis();
if (lastErrorAt + 2000 >= now) {
return this.errorsSent.incrementAndGet() >= 5;
} else {
this.lastError.set(now);
this.errorsSent.set(0);
return false;
}
}
}
@@ -1,17 +1,22 @@
package io.ibj.nuvotifier.legacyserver.protocol;
package com.vexsoftware.votifier.net.protocol;
import io.ibj.nuvotifier.legacyserver.VotifierSession;
import com.vexsoftware.votifier.net.VotifierSession;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.StandardCharsets;
/**
* Handles the Votifier greeting.
*/
@ChannelHandler.Sharable
public class VotifierGreetingHandler extends ChannelInboundHandlerAdapter {
public static final VotifierGreetingHandler INSTANCE = new VotifierGreetingHandler();
private VotifierGreetingHandler() {}
@Override
public void channelActive(ChannelHandlerContext ctx) {
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
@@ -0,0 +1,79 @@
package com.vexsoftware.votifier.net.protocol;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSA;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Decodes original protocol votes.
*/
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
if (!ctx.channel().isActive()) {
buf.skipBytes(buf.readableBytes());
return;
}
if (buf.readableBytes() < 256) {
// The client might have not sent all the data yet, so don't eject the connection.
return;
}
if (buf.readableBytes() > 256) {
// They sent too much data - silently drop the connection.
buf.skipBytes(buf.readableBytes());
ctx.close();
return;
}
byte[] block = ByteBufUtil.getBytes(buf);
buf.skipBytes(buf.readableBytes());
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
try {
block = RSA.decrypt(block, plugin.getProtocolV1Key().getPrivate());
} catch (Exception e) {
if (plugin.isDebug()) {
throw new CorruptedFrameException(
"Could not decrypt data from " + ctx.channel().remoteAddress()
+ ". Make sure the public key on the list is correct.",
e);
} else {
throw new QuietException("Could not decrypt data from "
+ ctx.channel().remoteAddress() + ". Make sure the public key on the list is correct.");
}
}
// Parse the string we received.
String all = new String(block, StandardCharsets.US_ASCII);
String[] split = all.split("\n");
if (split.length < 5) {
throw new QuietException("Not enough fields specified in vote. This is not a NuVotifier issue. Got "
+ split.length + " fields, but needed 5.");
}
if (!split[0].equals("VOTE")) {
throw new QuietException(
"The VOTE opcode was not present. This is not a NuVotifier issue, but a bug with the server list.");
}
// Create the vote.
Vote vote = new Vote(split[1], split[2], split[3], split[4]);
list.add(vote);
// We are done, remove ourselves. Why? Sometimes, we will decode multiple vote messages.
// Netty doesn't like this, so we must remove ourselves from the pipeline. With Protocol 1,
// ending votes is a "fire and forget" operation, so this is safe.
ctx.pipeline().remove(this);
}
}
@@ -1,21 +1,20 @@
package io.ibj.nuvotifier.legacyserver.protocol;
package com.vexsoftware.votifier.net.protocol;
import com.google.gson.JsonObject;
import io.ibj.nuvotifier.legacyserver.GsonInst;
import io.ibj.nuvotifier.legacyserver.VotifierSession;
import io.ibj.nuvotifier.platform.V2TokenProvider;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.util.GsonInst;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.MessageToMessageDecoder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
/**
* Decodes protocol 2 JSON votes.
@@ -23,12 +22,6 @@ import java.util.UUID;
public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
private static final SecureRandom RANDOM = new SecureRandom();
private final V2TokenProvider tokenProvider;
public VotifierProtocol2Decoder(V2TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void decode(ChannelHandlerContext ctx, String s, List<Object> list) throws Exception {
JsonObject voteMessage = GsonInst.gson.fromJson(s, JsonObject.class);
@@ -43,9 +36,17 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
throw new CorruptedFrameException("Challenge is not valid");
}
String serviceName = votePayload.get("serviceName").getAsString();
// Get the key for the given service.
Key key = tokenProvider.getKeyForService(serviceName);
// Verify that we have keys available.
VotifierPlugin plugin = ctx.channel().attr(VotifierPlugin.KEY).get();
Key key = plugin.getTokens().get(votePayload.get("serviceName").getAsString());
if (key == null) {
key = plugin.getTokens().get("default");
if (key == null) {
throw new RuntimeException(
"Unknown service '" + votePayload.get("serviceName").getAsString() + "'");
}
}
// Verify signature.
String sigHash = voteMessage.get("signature").getAsString();
@@ -60,22 +61,19 @@ public class VotifierProtocol2Decoder extends MessageToMessageDecoder<String> {
UUID.fromString(votePayload.get("uuid").getAsString());
}
String username = votePayload.get("username").getAsString();
if (username.length() > 16) {
if (votePayload.get("username").getAsString().length() > 16) {
throw new CorruptedFrameException("Username too long");
}
String address = votePayload.get("address").getAsString();
long timestamp = votePayload.get("timestamp").getAsLong();
// Create the vote.
Vote vote = new Vote(serviceName, username, address, Long.toString(timestamp));
Vote vote = new Vote(votePayload);
list.add(vote);
ctx.pipeline().remove(this);
}
private boolean hmacEqual(byte[] sig, byte[] message, Key key) throws NoSuchAlgorithmException, InvalidKeyException {
private boolean hmacEqual(byte[] sig, byte[] message, Key key)
throws NoSuchAlgorithmException, InvalidKeyException {
// See https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
// This randomizes the byte order to make timing attacks more difficult.
Mac mac = Mac.getInstance("HmacSHA256");
@@ -1,16 +1,13 @@
package io.ibj.nuvotifier.legacyserver.protocol;
package com.vexsoftware.votifier.net.protocol;
import io.ibj.nuvotifier.legacyserver.VotifierSession;
import io.ibj.nuvotifier.platform.V1KeyProvider;
import io.ibj.nuvotifier.platform.V2TokenProvider;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.util.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
@@ -18,20 +15,20 @@ import java.util.List;
* Attempts to determine if original protocol or protocol v2 is being used.
*/
public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
private static final QuietException V2_ONLY =
new QuietException("This server only accepts well-formed Votifier v2 packets.");
private static final short PROTOCOL_2_MAGIC = 0x733A;
private final boolean testMode;
private final boolean allowv1;
private final V1KeyProvider v1;
private final V2TokenProvider v2;
public VotifierProtocolDifferentiator(V1KeyProvider v1, V2TokenProvider v2, boolean testMode) {
public VotifierProtocolDifferentiator(boolean testMode, boolean allowv1) {
this.testMode = testMode;
this.v1 = v1;
this.v2 = v2;
this.allowv1 = allowv1;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) throws Exception {
// Determine the number of bytes that are available.
int readable = buf.readableBytes();
@@ -40,36 +37,41 @@ public class VotifierProtocolDifferentiator extends ByteToMessageDecoder {
return;
}
short readMagic = buf.readShort();
// Reset reader index again
buf.readerIndex(0);
short readMagic = buf.getShort(0);
VotifierSession session = ctx.channel().attr(VotifierSession.KEY).get();
if (readMagic == PROTOCOL_2_MAGIC) {
if (v2 == null) {
throw new CorruptedFrameException("This server only accepts v1 Votifier packets.");
}
// Short 0x733A + Message = Protocol v2 Vote
session.setVersion(VotifierSession.ProtocolVersion.TWO);
if (!testMode) {
ctx.pipeline().addAfter("protocolDifferentiator", "protocol2LengthDecoder", new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
ctx.pipeline().addAfter("protocol2LengthDecoder", "protocol2StringDecoder", new StringDecoder(StandardCharsets.UTF_8));
ctx.pipeline().addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder(v2));
ctx.pipeline().addAfter("protocol2VoteDecoder", "protocol2StringEncoder", new StringEncoder(StandardCharsets.UTF_8));
ctx.pipeline()
.addAfter(
"protocolDifferentiator",
"protocol2LengthDecoder",
new LengthFieldBasedFrameDecoder(1024, 2, 2, 0, 4));
ctx.pipeline()
.addAfter(
"protocol2LengthDecoder",
"protocol2StringDecoder",
new StringDecoder(StandardCharsets.UTF_8));
ctx.pipeline()
.addAfter("protocol2StringDecoder", "protocol2VoteDecoder", new VotifierProtocol2Decoder());
ctx.pipeline()
.addAfter(
"protocol2VoteDecoder",
"protocol2StringEncoder",
new StringEncoder(StandardCharsets.UTF_8));
ctx.pipeline().remove(this);
}
} else {
if (v1 == null) {
throw new CorruptedFrameException("This server only accepts well-formed Votifier v2 packets.");
if (!allowv1) {
throw V2_ONLY;
}
// Probably Protocol v1 Vote Message
session.setVersion(VotifierSession.ProtocolVersion.ONE);
if (!testMode) {
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder(v1));
ctx.pipeline().addAfter("protocolDifferentiator", "protocol1Handler", new VotifierProtocol1Decoder());
ctx.pipeline().remove(this);
}
}
@@ -1,26 +1,26 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
package com.vexsoftware.votifier.net.protocol.v1crypto;
import javax.crypto.Cipher;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
/**
* Static RSA utility methods for encrypting and decrypting blocks of
@@ -57,5 +57,4 @@ public class RSA {
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(data);
}
}
@@ -1,27 +1,26 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
package com.vexsoftware.votifier.net.protocol.v1crypto;
import java.io.IOException;
import java.util.Base64;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyFactory;
@@ -30,6 +29,7 @@ import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* Static utility methods for saving and loading RSA key pairs.
@@ -48,12 +48,10 @@ public class RSAIO {
PublicKey publicKey = keyPair.getPublic();
// Store the public and private keys.
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(
publicKey.getEncoded());
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(
privateKey.getEncoded());
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKey.getEncoded());
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
try (FileOutputStream publicOut = new FileOutputStream(directory + "/public.key");
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
FileOutputStream privateOut = new FileOutputStream(directory + "/private.key")) {
publicOut.write(Base64.getEncoder().encode(publicSpec.getEncoded()));
privateOut.write(Base64.getEncoder().encode(privateSpec.getEncoded()));
@@ -63,13 +61,16 @@ public class RSAIO {
public static byte[] readB64File(File directory, String name) throws IOException {
File f = new File(directory, name);
byte[] contents = Files.readAllBytes(f.toPath());
String strContents = new String(contents);
String strContents = new String(contents, StandardCharsets.US_ASCII);
strContents = strContents.trim();
try {
return Base64.getDecoder().decode(strContents);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Base64 decoding exception: This is probably due to a corrupted file, " +
"but in case it isn't, here is a b64 representation of what we read: " + new String(Base64.getEncoder().encode(contents), StandardCharsets.UTF_8), e);
throw new IllegalArgumentException(
"Base64 decoding exception: This is probably due to a corrupted file, "
+ "but in case it isn't, here is a b64 representation of what we read: "
+ new String(Base64.getEncoder().encode(contents), StandardCharsets.UTF_8),
e);
}
}
@@ -90,13 +91,10 @@ public class RSAIO {
// Instantiate and return the key pair.
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
encodedPublicKey);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encodedPublicKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
encodedPrivateKey);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
return new KeyPair(publicKey, privateKey);
}
}
@@ -1,22 +1,22 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package io.ibj.nuvotifier.legacyserver.protocol.v1crypto;
package com.vexsoftware.votifier.net.protocol.v1crypto;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -36,10 +36,8 @@ public class RSAKeygen {
*/
public static KeyPair generate(int bits) throws Exception {
KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA");
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits,
RSAKeyGenParameterSpec.F4);
RSAKeyGenParameterSpec spec = new RSAKeyGenParameterSpec(bits, RSAKeyGenParameterSpec.F4);
keygen.initialize(spec);
return keygen.generateKeyPair();
}
}
@@ -1,4 +1,4 @@
package io.ibj.nuvotifier.multiplatform;
package com.vexsoftware.votifier.platform;
public interface BackendServer {
String getName();
@@ -1,6 +1,7 @@
package io.ibj.nuvotifier.platform;
package com.vexsoftware.votifier.platform;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class JavaUtilLogger implements LoggingAdapter {
@@ -21,6 +22,14 @@ public class JavaUtilLogger implements LoggingAdapter {
logger.log(Level.SEVERE, s, o);
}
@Override
public void error(String s, Throwable e, Object... o) {
LogRecord lr = new LogRecord(Level.SEVERE, s);
lr.setParameters(o);
lr.setThrown(e);
logger.log(lr);
}
@Override
public void warn(String s) {
logger.warning(s);
@@ -1,13 +1,17 @@
package io.ibj.nuvotifier.platform;
package com.vexsoftware.votifier.platform;
public interface LoggingAdapter {
void error(String s);
void error(String s, Object... o);
void error(String s, Throwable e, Object... o);
void warn(String s);
void warn(String s, Object... o);
void info(String s);
void info(String s, Object... o);
}
@@ -1,9 +1,9 @@
package io.ibj.nuvotifier.multiplatform;
package com.vexsoftware.votifier.platform;
import java.util.Collection;
import java.util.Optional;
public interface ProxyVotifierPlugin {
public interface ProxyVotifierPlugin extends VotifierPlugin {
Collection<BackendServer> getAllBackendServers();
Optional<BackendServer> getServer(String name);
@@ -0,0 +1,24 @@
package com.vexsoftware.votifier.platform;
import com.vexsoftware.votifier.VoteHandler;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import io.netty.util.AttributeKey;
import java.security.Key;
import java.security.KeyPair;
import java.util.Map;
public interface VotifierPlugin extends VoteHandler {
AttributeKey<VotifierPlugin> KEY = AttributeKey.valueOf("votifier_plugin");
Map<String, Key> getTokens();
KeyPair getProtocolV1Key();
LoggingAdapter getPluginLogger();
VotifierScheduler getScheduler();
default boolean isDebug() {
return false;
}
}
@@ -1,5 +1,6 @@
package io.ibj.nuvotifier.platform.scheduler;
package com.vexsoftware.votifier.platform.scheduler;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@@ -8,24 +9,7 @@ public class ScheduledExecutorServiceVotifierScheduler implements VotifierSchedu
private final ScheduledExecutorService service;
public ScheduledExecutorServiceVotifierScheduler(ScheduledExecutorService service) {
if (service == null)
throw new NullPointerException("Service is null");
this.service = service;
}
@Override
public ScheduledVotifierTask sync(Runnable runnable) {
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
}
@Override
public ScheduledVotifierTask onPool(Runnable runnable) {
return new ScheduledVotifierTaskWrapper(service.submit(runnable));
}
@Override
public ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit) {
return new ScheduledVotifierTaskWrapper(service.schedule(runnable, delay, unit));
this.service = Objects.requireNonNull(service, "service");
}
@Override
@@ -1,4 +1,4 @@
package io.ibj.nuvotifier.platform.scheduler;
package com.vexsoftware.votifier.platform.scheduler;
public interface ScheduledVotifierTask {
void cancel();
@@ -1,13 +1,8 @@
package io.ibj.nuvotifier.platform.scheduler;
package com.vexsoftware.votifier.platform.scheduler;
import java.util.concurrent.TimeUnit;
public interface VotifierScheduler {
ScheduledVotifierTask sync(Runnable runnable);
ScheduledVotifierTask onPool(Runnable runnable);
ScheduledVotifierTask delayedSync(Runnable runnable, int delay, TimeUnit unit);
ScheduledVotifierTask delayedOnPool(Runnable runnable, int delay, TimeUnit unit);
@@ -0,0 +1,43 @@
package com.vexsoftware.votifier.support.forwarding;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.vexsoftware.votifier.model.Vote;
import java.io.CharArrayReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public abstract class AbstractPluginMessagingForwardingSink implements ForwardingVoteSink {
public AbstractPluginMessagingForwardingSink(ForwardedVoteListener listener) {
this.listener = listener;
}
private final ForwardedVoteListener listener;
public void handlePluginMessage(byte[] message) {
String strMessage = new String(message, StandardCharsets.UTF_8);
try (CharArrayReader reader = new CharArrayReader(strMessage.toCharArray())) {
JsonReader r = new JsonReader(reader);
r.setLenient(true);
while (r.peek() != JsonToken.END_DOCUMENT) {
r.beginObject();
JsonObject o = new JsonObject();
while (r.hasNext()) {
String name = r.nextName();
if (r.peek() == JsonToken.NUMBER) o.add(name, new JsonPrimitive(r.nextLong()));
else o.add(name, new JsonPrimitive(r.nextString()));
}
r.endObject();
Vote v = new Vote(o);
listener.onForward(v);
}
} catch (IOException e) {
e.printStackTrace(); // Should never happen.
}
}
}
@@ -0,0 +1,174 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.BackendServer;
import com.vexsoftware.votifier.platform.ProxyVotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.cache.FileVoteCache;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public abstract class AbstractPluginMessagingForwardingSource implements ForwardingVoteSource {
public AbstractPluginMessagingForwardingSource(
String channel, ServerFilter serverFilter, ProxyVotifierPlugin plugin, VoteCache cache, int dumpRate) {
this.channel = channel;
this.plugin = plugin;
this.cache = cache;
this.serverFilter = serverFilter;
this.dumpRate = dumpRate;
}
protected AbstractPluginMessagingForwardingSource(
String channel, ProxyVotifierPlugin plugin, VoteCache voteCache, int dumpRate) {
this(channel, null, plugin, voteCache, dumpRate);
}
protected final ProxyVotifierPlugin plugin;
protected final String channel;
protected final VoteCache cache;
protected final ServerFilter serverFilter;
private final int dumpRate;
@Override
public void forward(Vote v) {
byte[] rawData = v.serialize().toString().getBytes(StandardCharsets.UTF_8);
for (BackendServer server : plugin.getAllBackendServers()) {
if (!serverFilter.isAllowed(server.getName())) continue;
if (!forwardSpecific(server, rawData)) {
attemptToAddToCache(v, server.getName());
} else if (plugin.isDebug()) {
plugin.getPluginLogger().info("Successfully forwarded vote " + v + " to server " + server.getName());
}
}
}
protected boolean forwardSpecific(BackendServer connection, Vote vote) {
byte[] rawData = vote.serialize().toString().getBytes(StandardCharsets.UTF_8);
return forwardSpecific(connection, rawData);
}
protected boolean forwardSpecific(BackendServer connection, Collection<Vote> votes) {
StringBuilder data = new StringBuilder();
for (Vote v : votes) {
data.append(v.serialize().toString());
}
return forwardSpecific(connection, data.toString().getBytes(StandardCharsets.UTF_8));
}
private boolean forwardSpecific(BackendServer connection, byte[] data) {
return connection.sendPluginMessage(channel, data);
}
@Override
public void halt() {
if (cache instanceof FileVoteCache) {
try {
((FileVoteCache) cache).halt();
} catch (IOException e) {
plugin.getPluginLogger().error("Unable to save cached votes, votes will be lost.", e);
}
}
}
protected void onServerConnect(BackendServer server) {
if (cache == null) return;
final Collection<Vote> cachedVotes = cache.evict(server.getName());
dumpVotesToServer(cachedVotes, server, "server '" + server + "'", failedVotes -> {
for (Vote v : failedVotes) cache.addToCache(v, server.getName());
});
}
protected void attemptToAddToCache(Vote v, String server) {
if (cache != null) {
cache.addToCache(v, server);
if (plugin.isDebug()) plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> " + server);
} else if (plugin.isDebug())
plugin.getPluginLogger()
.error("Could not immediately send vote to backend, vote lost! " + v + " -> " + server);
}
protected void attemptToAddToPlayerCache(Vote v, String player) {
if (cache != null) {
cache.addToCachePlayer(v, player);
if (plugin.isDebug())
plugin.getPluginLogger().info("Added to forwarding cache: " + v + " -> (player) " + player);
} else if (plugin.isDebug())
plugin.getPluginLogger()
.error("Could not immediately send vote to backend, vote lost! " + v + " -> (player) " + player);
}
// returns a collection of failed votes
private void dumpVotesToServer(
Collection<Vote> cachedVotes, BackendServer target, String identifier, Consumer<Collection<Vote>> cb) {
dumpVotesToServer(cachedVotes, target, identifier, 0, cb);
}
private void dumpVotesToServer(
Collection<Vote> cachedVotes,
BackendServer target,
String identifier,
int evictedAlready,
Consumer<Collection<Vote>> cb) {
if (!cachedVotes.isEmpty()) {
plugin.getScheduler()
.delayedOnPool(
() -> {
int evicted = 0;
Iterator<Vote> vi = cachedVotes.iterator();
Collection<Vote> chunk = new ArrayList<>(dumpRate);
while (vi.hasNext() && evicted < dumpRate) {
chunk.add(vi.next());
vi.remove();
}
if (forwardSpecific(target, chunk)) {
evicted += chunk.size();
if (evicted >= dumpRate && !cachedVotes.isEmpty()) {
// if we evicted everything we could but still need to evict more
dumpVotesToServer(
cachedVotes, target, identifier, evictedAlready + evicted, cb);
return;
}
} else {
// so since our forwarding failed, break like we are done
cachedVotes.addAll(chunk);
}
if (plugin.isDebug()) {
plugin.getPluginLogger()
.info("Successfully evicted " + (evictedAlready + evicted) + " votes to "
+ identifier + ".");
if (!cachedVotes.isEmpty()) {
plugin.getPluginLogger()
.info("Held " + cachedVotes.size() + " votes for " + identifier + ".");
}
}
// cachedVotes contains any votes which have yet to be evicted
cb.accept(cachedVotes);
},
evictedAlready == 0 ? 3 : 1,
TimeUnit.SECONDS);
} else {
cb.accept(cachedVotes);
}
}
protected void handlePlayerSwitch(BackendServer server, String playerName) {
if (cache == null) return;
if (!serverFilter.isAllowed(server.getName())) return;
final Collection<Vote> cachedVotes = cache.evictPlayer(playerName);
dumpVotesToServer(cachedVotes, server, "player '" + playerName + "'", failedVotes -> {
for (Vote v : failedVotes) cache.addToCachePlayer(v, playerName);
});
}
}
@@ -0,0 +1,16 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
/**
* Represents a method to call when a vote is forwarded through the {@link ForwardingVoteSink}.
*/
public interface ForwardedVoteListener {
/**
* Called whenever a vote is forwarded from a {@link ForwardingVoteSink}
*
* @param v Vote that was forwarded
*/
void onForward(Vote v);
}
@@ -0,0 +1,12 @@
package com.vexsoftware.votifier.support.forwarding;
/**
* Represents a method at which to receive forwarded votes.
*/
public interface ForwardingVoteSink {
/**
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
*/
void halt();
}
@@ -0,0 +1,21 @@
package com.vexsoftware.votifier.support.forwarding;
import com.vexsoftware.votifier.model.Vote;
/**
* Represents a source for forwarding votes through to servers.
*/
public interface ForwardingVoteSource {
/**
* Forwards a vote to all servers set up to receive votes.
*
* @param v Vote to forward to servers
*/
void forward(Vote v);
/**
* Stop or close any oustanding network interfaces. Occurs on the onDisable method of a plugin.
*/
void halt();
}
@@ -0,0 +1,23 @@
package com.vexsoftware.votifier.support.forwarding;
import java.util.Collection;
import java.util.Collections;
public class ServerFilter {
public ServerFilter(Collection<String> names, boolean whitelist) {
this.names = names;
this.whitelist = whitelist;
}
public ServerFilter() {
this(Collections.emptySet(), true);
}
private final Collection<String> names;
private final boolean whitelist;
public boolean isAllowed(String name) {
return names.contains(name) == whitelist;
}
}
@@ -0,0 +1,169 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import com.vexsoftware.votifier.util.GsonInst;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class FileVoteCache extends MemoryVoteCache {
private final LoggingAdapter l;
private final File cacheFile;
private final ScheduledVotifierTask saveTask;
public FileVoteCache(final VotifierPlugin plugin, File cacheFile, long voteTTL) throws IOException {
super(plugin, voteTTL);
this.cacheFile = cacheFile;
this.l = plugin.getPluginLogger();
load();
saveTask = plugin.getScheduler()
.repeatOnPool(
() -> {
try {
save();
} catch (IOException e) {
l.error("Unable to save cached votes, votes will be lost if you restart.", e);
}
},
3,
3,
TimeUnit.MINUTES);
}
private static Set<String> keySet(JsonObject object) {
Set<String> set = new HashSet<>();
for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
set.add(entry.getKey());
}
return set;
}
private void load() throws IOException {
// Load the cache from disk
JsonObject object;
try (BufferedReader reader = Files.newBufferedReader(cacheFile.toPath(), StandardCharsets.UTF_8)) {
object = GsonInst.gson.fromJson(reader, JsonObject.class);
if (object == null) {
// When the input is not malformed but instead empty, this returns null. Simply assume it is empty.
object = new JsonObject();
}
} catch (NoSuchFileException e) {
object = new JsonObject();
}
boolean resave = false;
// First, lets figure out if we are converting from a pre2.3.6 cache
if (!object.has("players") || !object.has("servers") || !object.has("version") || object.size() != 3) {
JsonObject oldObject = object;
object = new JsonObject();
object.add("servers", oldObject);
object.add("players", new JsonObject());
object.addProperty("version", 2);
resave = true;
}
if (object.get("version").getAsInt() != 2)
throw new IllegalStateException("Could not read cache file! Unknown version '"
+ object.get("version").getAsInt() + "' read.");
JsonObject players = object.getAsJsonObject("players");
JsonObject servers = object.getAsJsonObject("servers");
for (String player : keySet(players)) {
playerVoteCache.put(player, readVotes(players.getAsJsonArray(player)));
}
for (String server : keySet(servers)) {
voteCache.put(server, readVotes(servers.getAsJsonArray(server)));
}
if (resave) {
File replacementFile;
for (int i = 0; ; i++) {
replacementFile = new File(cacheFile.getParentFile(), cacheFile.getName() + ".bak." + i);
if (!replacementFile.exists()) break;
}
if (!cacheFile.renameTo(replacementFile)) {
l.error("Backup movement failed! Will not save.");
return;
}
l.warn("Saving new vote cache format to file - backup moved to " + replacementFile.getAbsolutePath());
save();
}
}
private Collection<VoteWithRecordedTimestamp> readVotes(JsonArray voteArray) {
Collection<VoteWithRecordedTimestamp> votes = new HashSet<>(voteArray.size());
for (int i = 0; i < voteArray.size(); i++) {
JsonObject voteObject = voteArray.get(i).getAsJsonObject();
VoteWithRecordedTimestamp v = new VoteWithRecordedTimestamp(voteObject);
if (hasTimedOut(v)) l.warn("Purging out of date vote.", v);
else votes.add(v);
}
return votes;
}
public void save() throws IOException {
JsonObject votesObject = new JsonObject();
cacheLock.lock();
try {
votesObject.addProperty("version", 2);
votesObject.add("players", serializeMap(playerVoteCache));
votesObject.add("servers", serializeMap(voteCache));
} finally {
cacheLock.unlock();
}
try (BufferedWriter writer = Files.newBufferedWriter(cacheFile.toPath(), StandardCharsets.UTF_8)) {
GsonInst.gson.toJson(votesObject, writer);
}
}
public JsonObject serializeMap(Map<String, Collection<VoteWithRecordedTimestamp>> map) {
JsonObject o = new JsonObject();
Iterator<Map.Entry<String, Collection<VoteWithRecordedTimestamp>>> entryItr =
map.entrySet().iterator();
while (entryItr.hasNext()) {
Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry = entryItr.next();
JsonArray array = new JsonArray();
Iterator<VoteWithRecordedTimestamp> voteItr = entry.getValue().iterator();
while (voteItr.hasNext()) {
VoteWithRecordedTimestamp vote = voteItr.next();
// if the vote is no longer valid, notify and remove
if (hasTimedOut(vote)) {
l.warn("Purging out of date vote.", vote);
voteItr.remove();
} else {
array.add(vote.serialize());
}
}
// if, during our iteration, we TTL invalidated all of the votes
if (entry.getValue().isEmpty()) entryItr.remove();
o.add(entry.getKey(), array);
}
return o;
}
public void halt() throws IOException {
saveTask.cancel();
save();
}
}
@@ -0,0 +1,174 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledVotifierTask;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by Joe Hirschfeld on 10/20/2015.
*/
public class MemoryVoteCache implements VoteCache {
private final LoggingAdapter l;
private final long voteTTL;
protected final Map<String, Collection<VoteWithRecordedTimestamp>> voteCache;
protected final Map<String, Collection<VoteWithRecordedTimestamp>> playerVoteCache;
protected final ReentrantLock cacheLock = new ReentrantLock();
private final ScheduledVotifierTask sweepTask;
public MemoryVoteCache(VotifierPlugin p, long voteTTL) {
voteCache = new HashMap<>();
playerVoteCache = new HashMap<>();
this.voteTTL = voteTTL;
this.l = p.getPluginLogger();
this.sweepTask = p.getScheduler().repeatOnPool(this::sweep, 12, 12, TimeUnit.HOURS);
}
@Override
public Collection<String> getCachedServers() {
cacheLock.lock();
try {
return Collections.unmodifiableCollection(new ArrayList<>(voteCache.keySet()));
} finally {
cacheLock.unlock();
}
}
@Override
public void addToCache(Vote v, String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
voteCache.computeIfAbsent(server, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
}
@Override
public void addToCachePlayer(Vote v, String player) {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
playerVoteCache.computeIfAbsent(player, k -> new HashSet<>()).add(new VoteWithRecordedTimestamp(v));
} finally {
cacheLock.unlock();
}
}
@Override
public Collection<Vote> evictPlayer(String player) {
if (player == null) throw new NullPointerException();
cacheLock.lock();
try {
Collection<VoteWithRecordedTimestamp> playerVotes = playerVoteCache.remove(player);
if (playerVotes != null) {
return new HashSet<>(playerVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
}
@Override
public Collection<Vote> evict(String server) {
if (server == null) throw new NullPointerException();
cacheLock.lock();
try {
Collection<VoteWithRecordedTimestamp> serverVotes = voteCache.remove(server);
if (serverVotes != null) {
return new HashSet<>(serverVotes);
} else {
return Collections.emptySet();
}
} finally {
cacheLock.unlock();
}
}
public void sweep() {
cacheLock.lock();
try {
sweep(voteCache);
sweep(playerVoteCache);
} finally {
cacheLock.unlock();
}
}
private void sweep(Map<String, Collection<VoteWithRecordedTimestamp>> m) {
for (Map.Entry<String, Collection<VoteWithRecordedTimestamp>> entry : m.entrySet()) {
Iterator<VoteWithRecordedTimestamp> vi = entry.getValue().iterator();
while (vi.hasNext()) {
VoteWithRecordedTimestamp v = vi.next();
if (hasTimedOut(v)) {
l.warn("Purging out of date vote.", v);
vi.remove();
}
}
}
}
protected boolean hasTimedOut(VoteWithRecordedTimestamp v) {
if (voteTTL == -1) return false;
long daysAsMillis = TimeUnit.DAYS.toMillis(voteTTL);
return v.recorded + daysAsMillis < System.currentTimeMillis();
}
static class VoteWithRecordedTimestamp extends Vote {
private final long recorded;
VoteWithRecordedTimestamp(Vote vote) {
super(vote);
this.recorded = System.currentTimeMillis();
}
VoteWithRecordedTimestamp(JsonObject object) {
super(object);
if (object.has("recorded")) {
this.recorded = object.get("recorded").getAsLong();
} else {
// Assign the current time, in the hope that the stale votes will eventually get purged.
this.recorded = System.currentTimeMillis();
}
}
@Override
public JsonObject serialize() {
JsonObject object = super.serialize();
object.addProperty("recorded", this.recorded);
return object;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
VoteWithRecordedTimestamp that = (VoteWithRecordedTimestamp) o;
return recorded == that.recorded;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (int) (recorded ^ (recorded >>> 32));
return result;
}
}
}
@@ -0,0 +1,54 @@
package com.vexsoftware.votifier.support.forwarding.cache;
import com.vexsoftware.votifier.model.Vote;
import java.util.Collection;
import java.util.Collections;
/**
* Represents a method of caching votes for forwarding to a server that it was not previously capable of sending.
*/
public interface VoteCache {
/**
* Returns the names of all server in which the cache is holding votes for
*
* @return Collection of all cached servers
*/
Collection<String> getCachedServers();
/**
* Adds a vote to the vote cache for later sending
*
* @param v Vote to add to cache
* @param server Server to add vote under
*/
void addToCache(Vote v, String server);
/**
* Adds a vote to the vote cache for later sending, specifically keyed to a player. This is for the instance when the
* player is rewarded, however is not online any servers able to recieve said vote.
*
* @param v Vote to add to the cache
* @param player Server to add vote under
*/
default void addToCachePlayer(Vote v, String player) {}
/**
* Evicts all votes from the vote cache and returns a collection of vote objects
*
* @param server Server name of which to evict the votes from the cache
* @return A collection of all votes which were previously in the cache under the server
*/
Collection<Vote> evict(String server);
/**
* Evicts all votes from the vote cache for a specific player not assigned to a server and returns a collection of
* vote objects
*
* @param player Player name of the votes to evict from the cache
* @return A collection of all votes which were previously in the cache under the player
*/
default Collection<Vote> evictPlayer(String player) {
return Collections.emptyList();
}
}
@@ -0,0 +1,137 @@
package com.vexsoftware.votifier.support.forwarding.proxy;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.support.forwarding.ForwardingVoteSource;
import com.vexsoftware.votifier.support.forwarding.cache.VoteCache;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2Encoder;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierProtocol2HandshakeHandler;
import com.vexsoftware.votifier.support.forwarding.proxy.client.VotifierResponseHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class ProxyForwardingVoteSource implements ForwardingVoteSource {
private static final int MAX_RETRIES = 5;
private final VotifierPlugin plugin;
private final Supplier<Bootstrap> nettyBootstrap;
private final List<BackendServer> backendServers;
private final VoteCache voteCache;
private static final StringDecoder STRING_DECODER = new StringDecoder(StandardCharsets.US_ASCII);
public ProxyForwardingVoteSource(
VotifierPlugin plugin,
Supplier<Bootstrap> nettyBootstrap,
List<BackendServer> backendServers,
VoteCache voteCache) {
this.plugin = plugin;
this.nettyBootstrap = nettyBootstrap;
this.backendServers = backendServers;
this.voteCache = voteCache;
}
@Override
public void forward(Vote v) {
for (final BackendServer server : backendServers) {
forwardVote(server, v, 0);
}
}
private int fib(int t) {
if (t <= 1) return 1;
return fib(t - 2) + fib(t - 1);
}
private void forwardVote(final BackendServer server, final Vote v, final int tries) {
nettyBootstrap
.get()
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) {
channel.pipeline()
.addLast(new DelimiterBasedFrameDecoder(256, true, Delimiters.lineDelimiter()));
channel.pipeline().addLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));
channel.pipeline().addLast(STRING_DECODER);
channel.pipeline().addLast(new VotifierProtocol2Encoder(server.key));
channel.pipeline()
.addLast(new VotifierProtocol2HandshakeHandler(
v,
new VotifierResponseHandler() {
@Override
public void onSuccess() {
if (plugin.isDebug()) {
plugin.getPluginLogger()
.info("Successfully forwarded vote " + v + " to "
+ server.address + ".");
}
}
@Override
public void onFailure(Throwable error) {
handleFailure(server, v, error, tries);
}
},
plugin));
}
})
.connect(server.address)
.addListener((ChannelFutureListener) future -> {
if (!future.isSuccess()) {
handleFailure(server, v, future.cause(), tries);
}
});
}
private void handleFailure(final BackendServer server, final Vote v, Throwable cause, final int tries) {
int nextDelay = fib(tries + 1);
boolean willRetry = tries < MAX_RETRIES;
String msg = "Unable to send vote to " + server.address + ".";
if (willRetry) {
msg += " Will retry sending in " + nextDelay + " second(s).";
} else {
if (voteCache == null) {
msg += " This vote will be lost!";
} else {
voteCache.addToCache(v, server.name);
}
}
if (plugin.isDebug()) {
plugin.getPluginLogger().error(msg, cause);
} else {
plugin.getPluginLogger().error(msg);
}
if (willRetry) {
plugin.getScheduler().delayedOnPool(() -> forwardVote(server, v, tries + 1), nextDelay, TimeUnit.SECONDS);
}
}
@Override
public void halt() {}
public static class BackendServer {
private final String name;
private final InetSocketAddress address;
private final Key key;
public BackendServer(String name, InetSocketAddress address, Key key) {
this.name = name;
this.address = address;
this.key = key;
}
}
}
@@ -0,0 +1,26 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.vexsoftware.votifier.model.Vote;
public class VoteRequest {
private final String challenge;
private final Vote vote;
public VoteRequest(String challenge, Vote vote) {
this.challenge = challenge;
this.vote = vote;
}
public String getChallenge() {
return challenge;
}
public Vote getVote() {
return vote;
}
@Override
public String toString() {
return "VoteRequest{" + "challenge='" + challenge + '\'' + ", vote=" + vote + '}';
}
}
@@ -0,0 +1,45 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.google.gson.JsonObject;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
import javax.crypto.Mac;
public class VotifierProtocol2Encoder extends MessageToByteEncoder<VoteRequest> {
private static final short MAGIC = 0x733A;
private final Key key;
public VotifierProtocol2Encoder(Key key) {
this.key = key;
}
@Override
protected void encode(ChannelHandlerContext ctx, VoteRequest req, ByteBuf buf) throws Exception {
JsonObject object = new JsonObject();
JsonObject payloadObject = req.getVote().serialize();
payloadObject.addProperty("challenge", req.getChallenge());
String payload = payloadObject.toString();
object.addProperty("payload", payload);
// Generate the MAC
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(key);
mac.update(payload.getBytes(StandardCharsets.UTF_8));
String computed = Base64.getEncoder().encodeToString(mac.doFinal());
object.addProperty("signature", computed);
// JSON message is ready for encoding.
String finalMessage = object.toString();
buf.writeShort(MAGIC);
buf.writeShort(finalMessage.length());
ByteBuf messageBytes = Unpooled.copiedBuffer(finalMessage, StandardCharsets.UTF_8);
buf.writeBytes(messageBytes);
messageBytes.release();
}
}
@@ -0,0 +1,42 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.CorruptedFrameException;
public class VotifierProtocol2HandshakeHandler extends SimpleChannelInboundHandler<String> {
private final Vote toSend;
private final VotifierResponseHandler responseHandler;
private final VotifierPlugin nuVotifier;
public VotifierProtocol2HandshakeHandler(
Vote toSend, VotifierResponseHandler responseHandler, VotifierPlugin nuVotifier) {
this.toSend = toSend;
this.responseHandler = responseHandler;
this.nuVotifier = nuVotifier;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) {
String[] handshakeContents = s.split(" ");
if (handshakeContents.length != 3) {
throw new CorruptedFrameException("Handshake is not valid.");
}
VoteRequest request = new VoteRequest(handshakeContents[2], toSend);
if (nuVotifier.isDebug()) {
nuVotifier.getPluginLogger().info("Sent request: " + request.toString());
}
ctx.writeAndFlush(request);
ctx.pipeline().addLast(new VotifierProtocol2ResponseHandler(responseHandler));
ctx.pipeline().remove(this);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
responseHandler.onFailure(cause);
ctx.close();
}
}
@@ -0,0 +1,34 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
import com.google.gson.JsonObject;
import com.vexsoftware.votifier.util.GsonInst;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class VotifierProtocol2ResponseHandler extends SimpleChannelInboundHandler<String> {
private final VotifierResponseHandler responseHandler;
public VotifierProtocol2ResponseHandler(VotifierResponseHandler responseHandler) {
this.responseHandler = responseHandler;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
JsonObject object = GsonInst.gson.fromJson(msg, JsonObject.class);
String status = object.get("status").getAsString();
if (status.equals("ok")) {
responseHandler.onSuccess();
} else {
responseHandler.onFailure(
new Exception("Remote server error: " + object.get("cause").getAsString() + ": "
+ object.get("error").getAsString()));
}
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
responseHandler.onFailure(cause);
ctx.close();
}
}
@@ -0,0 +1,7 @@
package com.vexsoftware.votifier.support.forwarding.proxy.client;
public interface VotifierResponseHandler {
void onSuccess();
void onFailure(Throwable error);
}
@@ -0,0 +1,60 @@
package com.vexsoftware.votifier.util;
import com.vexsoftware.votifier.model.Vote;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ArgsToVote {
private static final Pattern argumentPattern = Pattern.compile("([a-zA-Z]+)=(\\S*)");
public static Vote parse(String[] arguments) {
return parse(arguments, null);
}
public static Vote parse(String[] arguments, Map<String, String> additionalArgs) {
String serviceName = "TestVote";
String username = null;
String address = "127.0.0.1";
long localTimestamp = System.currentTimeMillis();
String timestamp = Long.toString(localTimestamp, 10);
for (String s : arguments) {
Matcher m = argumentPattern.matcher(s);
if (m.matches()) {
String key = m.group(1).toLowerCase();
String v = m.group(2);
switch (key) {
case "servicename":
serviceName = v;
break;
case "username":
if (v.length() > 16)
throw new IllegalArgumentException(
"Illegal username - must be less than 16 characters long.");
username = v;
break;
case "address":
address = v;
break;
case "timestamp":
timestamp = v;
break;
default:
if (additionalArgs != null) additionalArgs.put(key, v);
break;
}
} else {
if (s.length() > 16)
throw new IllegalArgumentException("Illegal username - must be less than 16 characters long.");
username = s; // ezpz
}
}
if (username == null) throw new IllegalArgumentException("Username not specified!");
return new Vote(serviceName, username, address, timestamp);
}
}
@@ -1,8 +1,9 @@
package io.ibj.nuvotifier.legacyserver;
package com.vexsoftware.votifier.util;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonInst {
public static final Gson gson = new GsonBuilder().create();
}
@@ -0,0 +1,17 @@
package com.vexsoftware.votifier.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class IOUtil {
public static byte[] readAllBytes(InputStream from) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(8192);
byte[] buf = new byte[8192];
int read;
while ((read = from.read(buf)) != -1) {
out.write(buf, 0, read);
}
return out.toByteArray();
}
}
@@ -0,0 +1,11 @@
package com.vexsoftware.votifier.util;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import javax.crypto.spec.SecretKeySpec;
public class KeyCreator {
public static Key createKeyFrom(String token) {
return new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
}
}
@@ -0,0 +1,14 @@
package com.vexsoftware.votifier.util;
public class QuietException extends Exception {
public QuietException() {}
public QuietException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
return null;
}
}
@@ -1,12 +1,10 @@
package io.ibj.nuvotifier.legacyserver;
package com.vexsoftware.votifier.util;
import java.math.BigInteger;
import java.security.SecureRandom;
public class TokenUtil {
private TokenUtil() {
}
private TokenUtil() {}
private static final SecureRandom RANDOM = new SecureRandom();
@@ -0,0 +1,80 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.net.VotifierServerBootstrap;
import com.vexsoftware.votifier.net.VotifierSession;
import com.vexsoftware.votifier.platform.JavaUtilLogger;
import com.vexsoftware.votifier.platform.LoggingAdapter;
import com.vexsoftware.votifier.platform.VotifierPlugin;
import com.vexsoftware.votifier.platform.scheduler.ScheduledExecutorServiceVotifierScheduler;
import com.vexsoftware.votifier.platform.scheduler.VotifierScheduler;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyPair;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class StandaloneVotifierPlugin implements VotifierPlugin {
private final Map<String, Key> tokens;
private final VoteReceiver receiver;
private final KeyPair v1Key;
private final InetSocketAddress bind;
private final VotifierScheduler scheduler;
private VotifierServerBootstrap bootstrap;
public StandaloneVotifierPlugin(
Map<String, Key> tokens, VoteReceiver receiver, KeyPair v1Key, InetSocketAddress bind) {
this.receiver = receiver;
this.bind = bind;
this.tokens = Collections.unmodifiableMap(new HashMap<>(tokens));
this.v1Key = v1Key;
this.scheduler = new ScheduledExecutorServiceVotifierScheduler(Executors.newScheduledThreadPool(1));
}
public void start() {
start(o -> {});
}
public void start(Consumer<Throwable> error) {
if (bootstrap != null) {
bootstrap.shutdown();
}
this.bootstrap = new VotifierServerBootstrap(bind.getHostString(), bind.getPort(), this, v1Key == null);
this.bootstrap.start(error);
}
@Override
public Map<String, Key> getTokens() {
return tokens;
}
@Override
public KeyPair getProtocolV1Key() {
return v1Key;
}
@Override
public LoggingAdapter getPluginLogger() {
return new JavaUtilLogger(Logger.getAnonymousLogger());
}
@Override
public VotifierScheduler getScheduler() {
return scheduler;
}
@Override
public void onVoteReceived(Vote vote, VotifierSession.ProtocolVersion protocolVersion, String remoteAddress)
throws Exception {
receiver.onVote(vote);
}
@Override
public void onError(Throwable throwable, boolean alreadyHandledVote, String remoteAddress) {
receiver.onException(throwable);
}
}
@@ -0,0 +1,9 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.model.Vote;
public interface VoteReceiver {
void onVote(Vote vote) throws Exception;
void onException(Throwable throwable);
}
@@ -0,0 +1,56 @@
package com.vexsoftware.votifier.util.standalone;
import com.vexsoftware.votifier.net.protocol.v1crypto.RSAIO;
import com.vexsoftware.votifier.util.KeyCreator;
import java.io.File;
import java.net.InetSocketAddress;
import java.security.Key;
import java.security.KeyPair;
import java.security.interfaces.RSAKey;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class VotifierServerBuilder {
private final Map<String, Key> keyMap = new HashMap<>();
private KeyPair v1Key;
private InetSocketAddress bind;
private VoteReceiver receiver;
public VotifierServerBuilder addToken(String service, String token) {
Objects.requireNonNull(service, "service");
Objects.requireNonNull(token, "key");
keyMap.put(service, KeyCreator.createKeyFrom(token));
return this;
}
public VotifierServerBuilder v1Key(KeyPair v1Key) {
this.v1Key = Objects.requireNonNull(v1Key, "v1Key");
if (!(v1Key.getPrivate() instanceof RSAKey)) {
throw new IllegalArgumentException("Provided key is not an RSA key.");
}
return this;
}
public VotifierServerBuilder v1KeyFolder(File file) throws Exception {
this.v1Key = RSAIO.load(Objects.requireNonNull(file, "file"));
return this;
}
public VotifierServerBuilder bind(InetSocketAddress bind) {
this.bind = Objects.requireNonNull(bind, "bind");
return this;
}
public VotifierServerBuilder receiver(VoteReceiver receiver) {
this.receiver = Objects.requireNonNull(receiver, "receiver");
return this;
}
public StandaloneVotifierPlugin create() {
Objects.requireNonNull(bind, "need an address to bind to");
Objects.requireNonNull(receiver, "need a receiver for votes");
return new StandaloneVotifierPlugin(keyMap, receiver, v1Key, bind);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<module name="Checker">
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="TreeWalker">
<module name="FinalLocalVariable"/>
<module name="AvoidStarImport"/>
<module name="Indentation">
<property name="severity" value="ignore"/>
</module>
</module>
<module name="RegexpSingleline">
<property name="format" value="\borg\.junit\.jupiter\.api\.Assertions\.fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="ignoreCase" value="true"/>
<property name="severity" value="warning"/>
</module>
<module name="RegexpSingleline">
<property name="format" value="(?&lt;!\w)fail\s*\(\s*&quot;Not yet implemented&quot;\s*\)\s*;"/>
<property name="message" value="Remove placeholder fail() and implement the test."/>
<property name="severity" value="warning"/>
</module>
</module>
+404
View File
@@ -0,0 +1,404 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="23">
<profile kind="CodeFormatterProfile" name="TrueOG Network Formatting" version="23">
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.javadoc_do_not_separate_block_tags" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case_after_arrow" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.align_arrows_in_switch_on_columns" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.join_line_comments" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="2"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="20"/>
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="common_lines"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="next_line_on_wrap"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
</profile>
</profiles>
+151
View File
@@ -0,0 +1,151 @@
/* This is free and unencumbered software released into the public domain */
import org.gradle.plugins.ide.eclipse.model.EclipseModel
import java.nio.file.Files
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
val kotlinPluginProjects = configurations
.getByName("compileClasspath")
.allDependencies
.filterIsInstance<ProjectDependency>()
.filter { it -> it.attributes.getAttribute(kotlinAttribute) != null }
.map { it.path }
kotlinPluginProjects.forEach { evaluationDependsOn(it) }
val ideLibs: Configuration by
configurations.creating {
isCanBeResolved = true
isCanBeConsumed = false
}
configure<EclipseModel> {
project {
name = "Template-OG-Plugin"
}
classpath {
plusConfigurations.add(ideLibs)
}
}
val ideLibDir = layout.buildDirectory.dir("ide-libs")
val hashRegex = Regex("-[0-9a-fA-F]{10}(?=\\.jar$)") // Black magic.
val copyTasks = mutableListOf<TaskProvider<Copy>>()
kotlinPluginProjects.forEach { path ->
val sub = project(path)
val shadowJarProv = sub.tasks.named("shadowJar")
val copyTask =
tasks.register<Copy>("ideCopy${sub.name.replaceFirstChar(Char::titlecase)}") {
dependsOn(shadowJarProv)
from(shadowJarProv)
into(ideLibDir)
rename { it.replace(hashRegex, "") } // Remove git commit hash from jarfile.
}
copyTasks += copyTask
}
/* Ensure the jars exist before .classpath is generated */
tasks.named("eclipseClasspath").configure { dependsOn(copyTasks) }
/* Supply those jars to the ideLibs configuration (after evaluation) */
afterEvaluate { dependencies { add("ideLibs", fileTree(ideLibDir) { include("*.jar") }) } }
val injectIdeLibs =
tasks.register("injectIdeLibs") {
dependsOn("eclipse") // run after all eclipse files are generated
doLast {
val cpFile = file(".classpath")
if (!cpFile.exists()) return@doLast
val jars = fileTree(ideLibDir.get()) { include("*.jar") }.files.sortedBy { it.name }
if (jars.isEmpty()) return@doLast
// Parse DOM
val dbf = DocumentBuilderFactory.newInstance()
val doc = dbf.newDocumentBuilder().parse(cpFile)
val root = doc.documentElement
// Helper to see if an entry already exists
fun exists(path: String): Boolean =
root.getElementsByTagName("classpathentry").let { list ->
(0 until list.length).any { i ->
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue
kind == "lib" && p == path
}
}
// Remove any entry that points to ide-libs dir (folder or jars) to avoid dupes
val toRemove = mutableListOf<org.w3c.dom.Node>()
val list = root.getElementsByTagName("classpathentry")
val dirPath = ideLibDir.get().asFile.absolutePath
for (i in 0 until list.length) {
val n = list.item(i)
val kind = n.attributes?.getNamedItem("kind")?.nodeValue
val p = n.attributes?.getNamedItem("path")?.nodeValue ?: ""
if (kind == "lib" && (p == dirPath || p.startsWith("$dirPath/"))) {
toRemove += n
}
}
toRemove.forEach { root.removeChild(it) }
// Append our jar entries LAST
jars.forEach { f ->
val abs = f.absolutePath
if (!exists(abs)) {
val entry = doc.createElement("classpathentry")
entry.setAttribute("kind", "lib")
entry.setAttribute("path", abs)
root.appendChild(entry)
}
}
// Write back pretty
val tf =
TransformerFactory.newInstance().newTransformer().apply {
setOutputProperty(OutputKeys.INDENT, "yes")
setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "1")
setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no")
setOutputProperty(OutputKeys.ENCODING, "UTF-8")
}
Files.newBufferedWriter(cpFile.toPath()).use { w -> tf.transform(DOMSource(doc), StreamResult(w)) }
}
}
tasks.named("eclipse").configure { finalizedBy(injectIdeLibs) }
val eclipseModel = extensions.getByType<EclipseModel>()
fun Project.addResolvableEclipseConfigs() {
val jarAttr =
objects.named(org.gradle.api.attributes.LibraryElements::class, org.gradle.api.attributes.LibraryElements.JAR)
val apiAttr = objects.named(org.gradle.api.attributes.Usage::class, org.gradle.api.attributes.Usage.JAVA_API)
val compileOnlyRes =
configurations.create("eclipseCompileOnly") {
extendsFrom(configurations.getByName("compileOnly"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
val compileOnlyApiRes =
configurations.create("eclipseCompileOnlyApi") {
extendsFrom(configurations.getByName("compileOnlyApi"))
isCanBeResolved = true
isCanBeConsumed = false
attributes.attribute(org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE, apiAttr)
attributes.attribute(org.gradle.api.attributes.LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, jarAttr)
}
eclipseModel.classpath.plusConfigurations.addAll(listOf(compileOnlyRes, compileOnlyApiRes))
}
addResolvableEclipseConfigs()
-16
View File
@@ -1,16 +0,0 @@
plugins {
id 'java'
}
group 'io.ibj.nuvotifier'
version '3.0.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}
+3
View File
@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G
group=net.trueog.votifier-og
version=3.0.2
Binary file not shown.
+3 -2
View File
@@ -1,6 +1,7 @@
#Thu May 31 03:56:27 EDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
Vendored
+196 -117
View File
@@ -1,78 +1,129 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright © 2015-2021 the original 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
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -81,92 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+45 -35
View File
@@ -1,4 +1,22 @@
@if "%DEBUG%" == "" @echo off
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -9,25 +27,29 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -35,48 +57,36 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
-10
View File
@@ -1,10 +0,0 @@
dependencies {
api project(":model")
api project(":platform")
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.google.guava:guava:24.1-jre'
implementation group: "io.netty", name: "netty-handler", version: "4.1.32.Final"
}
@@ -1,99 +0,0 @@
package io.ibj.nuvotifier.legacyserver;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.ibj.nuvotifier.legacyserver.protocol.VoteInboundHandler;
import io.ibj.nuvotifier.legacyserver.protocol.VotifierGreetingHandler;
import io.ibj.nuvotifier.legacyserver.protocol.VotifierProtocolDifferentiator;
import io.ibj.nuvotifier.platform.LoggingAdapter;
import io.ibj.nuvotifier.platform.V1KeyProvider;
import io.ibj.nuvotifier.platform.V2TokenProvider;
import io.ibj.nuvotifier.platform.VoteHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundInvoker;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.PromiseCombiner;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
public class VotifierServer implements AutoCloseable {
private NioEventLoopGroup bossLoopGroup;
private NioEventLoopGroup eventLoopGroup;
private Set<Channel> serverChannels;
private final List<InetSocketAddress> bindAddresses;
private final V1KeyProvider v1KeyProvider;
private final V2TokenProvider v2TokenProvider;
private final LoggingAdapter loggingAdapter;
private final boolean testMode;
private final VoteHandler voteHandler;
VotifierServer(VotifierServerBuilder builder) {
this.bindAddresses = builder.bindAddresses;
this.v1KeyProvider = builder.v1KeyProvider;
this.v2TokenProvider = builder.v2TokenProvider;
this.loggingAdapter = builder.loggingAdapter;
this.testMode = builder.testMode;
this.voteHandler = builder.voteHandler;
}
public void start(Consumer<Throwable> error) {
this.bossLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO boss").build());
this.eventLoopGroup = new NioEventLoopGroup(1, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Votifier NIO worker").build());
Objects.requireNonNull(error, "error");
ServerBootstrap bootstrap = new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.group(bossLoopGroup, eventLoopGroup)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
channel.attr(VotifierSession.KEY).set(new VotifierSession());
channel.pipeline().addLast("greetingHandler", new VotifierGreetingHandler());
channel.pipeline().addLast("protocolDifferentiator", new VotifierProtocolDifferentiator(v1KeyProvider, v2TokenProvider, testMode));
channel.pipeline().addLast("voteHandler", new VoteInboundHandler(voteHandler));
}
});
ChannelFutureListener listener = future -> {
Channel serverChannel = future.channel();
if (future.isSuccess()) {
serverChannels.add(serverChannel);
loggingAdapter.info("Votifier enabled on socket " + serverChannel.localAddress() + ".");
error.accept(null);
} else {
loggingAdapter.error("Votifier was not able to bind to " + serverChannel.localAddress().toString(), future.cause());
error.accept(future.cause());
}
};
for (InetSocketAddress socketAddress : this.bindAddresses) {
bootstrap.bind(socketAddress).addListener(listener);
}
}
@Override
public void close() throws Exception {
if (!serverChannels.isEmpty()) {
PromiseCombiner closePromises = new PromiseCombiner();
serverChannels.stream().map(ChannelOutboundInvoker::close).forEach(closePromises::add);
DefaultPromise<Void> closesDone = new DefaultPromise<>(ImmediateEventExecutor.INSTANCE);
closePromises.finish(closesDone);
closesDone.awaitUninterruptibly();
}
eventLoopGroup.shutdownGracefully();
bossLoopGroup.shutdownGracefully();
}
}
@@ -1,65 +0,0 @@
package io.ibj.nuvotifier.legacyserver;
import io.ibj.nuvotifier.platform.LoggingAdapter;
import io.ibj.nuvotifier.platform.V1KeyProvider;
import io.ibj.nuvotifier.platform.V2TokenProvider;
import io.ibj.nuvotifier.platform.VoteHandler;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class VotifierServerBuilder {
// Which addresses to bind to? We support multiple now!
List<InetSocketAddress> bindAddresses = new ArrayList<>();
// Provides the server the ability to decode v1 keys. Defaults to
V1KeyProvider v1KeyProvider = null;
// Provides the server the ability to validate v2 token payloads
V2TokenProvider v2TokenProvider = null;
// Logging adapter which will be used to communicate various logs
LoggingAdapter loggingAdapter = null;
// Whether not the server is in 'test mode'. Spoiler: unless you are testing, its not.
boolean testMode = false;
// Handler to invoke when a vote is sent.
VoteHandler voteHandler = null;
public VotifierServerBuilder addBindAddress(InetSocketAddress bindAddress) {
this.bindAddresses.add(bindAddress);
return this;
}
public VotifierServerBuilder setV1KeyProvider(V1KeyProvider v1KeyProvider) {
this.v1KeyProvider = v1KeyProvider;
return this;
}
public VotifierServerBuilder setV2TokenProvider(V2TokenProvider v2TokenProvider) {
this.v2TokenProvider = v2TokenProvider;
return this;
}
public VotifierServerBuilder setLoggingAdapter(LoggingAdapter loggingAdapter) {
this.loggingAdapter = loggingAdapter;
return this;
}
public VotifierServerBuilder setTestMode(boolean testMode) {
this.testMode = testMode;
return this;
}
public VotifierServerBuilder setVoteHandler(VoteHandler voteHandler) {
this.voteHandler = voteHandler;
return this;
}
public VotifierServer build() {
return new VotifierServer(this);
}
}
@@ -1,62 +0,0 @@
package io.ibj.nuvotifier.legacyserver.protocol;
import io.ibj.nuvotifier.legacyserver.protocol.v1crypto.RSA;
import io.ibj.nuvotifier.model.NuVote;
import io.ibj.nuvotifier.platform.V1KeyProvider;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Decodes original protocol votes.
*/
public class VotifierProtocol1Decoder extends ByteToMessageDecoder {
private final V1KeyProvider keyProvider;
public VotifierProtocol1Decoder(V1KeyProvider keyProvider) {
this.keyProvider = keyProvider;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list) {
if (buf.readableBytes() < 256) {
return;
}
byte[] block = new byte[buf.readableBytes()];
buf.getBytes(0, block);
// "Drain" the whole buffer
buf.readerIndex(buf.readableBytes());
try {
block = RSA.decrypt(block, keyProvider.getPrivateKey());
} catch (Exception e) {
throw new CorruptedFrameException("Could not decrypt data (is your key correct?)", e);
}
// Parse the string we received.
String all = new String(block, StandardCharsets.US_ASCII);
String[] split = all.split("\n");
if (split.length < 5) {
throw new CorruptedFrameException("Not enough fields specified in vote.");
}
if (!split[0].equals("VOTE")) {
throw new CorruptedFrameException("VOTE opcode not found");
}
// Create the vote.
NuVote vote = NuVote.createLegacy(split[1], split[2], split[3], split[4]);
list.add(vote);
// We are done, remove ourselves. Why? Sometimes, we will decode multiple vote messages.
// Netty doesn't like this, so we must remove ourselves from the pipeline. With Protocol 1,
// ending votes is a "fire and forget" operation, so this is safe.
ctx.pipeline().remove(this);
}
}
-3
View File
@@ -1,3 +0,0 @@
# model
This module contains the 'model' for Votifier. It includes the legacy 'Vote' object as well as a 'NuVote' object which plugins which require extended information should depend upon.
View File
Whitespace-only changes.
@@ -1,89 +0,0 @@
/*
* Copyright (C) 2011 Vex Software LLC
* This file is part of Votifier.
*
* Votifier is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Votifier is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Votifier. If not, see <http://www.gnu.org/licenses/>.
*/
package com.vexsoftware.votifier.model;
/**
* A model for a vote.
*
* @author Blake Beaupain
*/
public class Vote {
/** The name of the vote service. */
private final String serviceName;
/** The username of the voter. */
private final String username;
/** The address of the voter. */
private final String address;
/** The date and time of the vote. */
private final String timeStamp;
public Vote(String serviceName, String username, String address, String timeStamp) {
this.serviceName = serviceName;
this.username = username;
this.address = address;
this.timeStamp = timeStamp;
}
@Override
public String toString() {
return "Vote (from:" + serviceName + " username:" + username
+ " address:" + address + " timeStamp:" + timeStamp + ")";
}
/**
* Gets the serviceName.
*
* @return The serviceName
*/
public String getServiceName() {
return serviceName;
}
/**
* Gets the username.
*
* @return The username
*/
public String getUsername() {
return username;
}
/**
* Gets the address.
*
* @return The address
*/
public String getAddress() {
return address;
}
/**
* Gets the time stamp.
*
* @return The time stamp
*/
public String getTimeStamp() {
return timeStamp;
}
}
@@ -1,17 +0,0 @@
package io.ibj.nuvotifier.model;
import com.vexsoftware.votifier.model.Vote;
/**
* This is the NuVote object. It extends the old 'Vote' object. The old 'Vote' object is frozen - i.e. no changes will be
* made to it. API additions to Vote happen in this file.
*/
public class NuVote extends Vote {
private NuVote(String serviceName, String username, String address, String timeStamp) {
super(serviceName, username, address, timeStamp);
}
public static NuVote createLegacy(String serviceName, String username, String address, String timeStamp) {
return new NuVote(serviceName, username, address, timeStamp);
}
}
View File
Whitespace-only changes.
-3
View File
@@ -1,3 +0,0 @@
dependencies {
api project(":model")
}
@@ -1,7 +0,0 @@
package io.ibj.nuvotifier.platform;
import java.security.PrivateKey;
public interface V1KeyProvider {
PrivateKey getPrivateKey();
}
@@ -1,7 +0,0 @@
package io.ibj.nuvotifier.platform;
import java.security.Key;
public interface V2TokenProvider {
Key getKeyForService(String serviceName);
}
@@ -1,12 +0,0 @@
package io.ibj.nuvotifier.platform;
import io.ibj.nuvotifier.model.NuVote;
public interface VoteHandler {
void onVote(NuVote vote);
default void onError(Throwable throwable, boolean voteAlreadyCompleted, String remoteAddress) {
throw new RuntimeException("Unimplemented onError handler");
}
}
-19
View File
@@ -1,19 +0,0 @@
rootProject.name = 'nuvotifier'
// The model for a 'Vote'.
include 'model'
// The 'platform' api.
include 'platform'
include 'multiplatform'
// Servers
include 'legacyserver'
// Apis
include 'bukkit-api'
include 'bungeecord-api'
include 'sponge-api'
include 'velocity-api'
include 'engine'
+21
View File
@@ -0,0 +1,21 @@
rootProject.name = "Votifier-OG"
ProcessBuilder("sh", "bootstrap.sh").directory(rootDir).inheritIO().start().let {
if (it.waitFor() != 0) throw GradleException("bootstrap.sh failed")
}
file("libs")
.listFiles()
?.filter { it.isDirectory && !it.name.startsWith(".") }
?.forEach { dir ->
include(":libs:${dir.name}")
project(":libs:${dir.name}").projectDir = dir
}
include(":nuvotifier-api", ":nuvotifier-common", ":nuvotifier-bukkit")
project(":nuvotifier-api").projectDir = file("api")
project(":nuvotifier-common").projectDir = file("common")
project(":nuvotifier-bukkit").projectDir = file("bukkit")
-4
View File
@@ -1,4 +0,0 @@
dependencies {
api group: "org.spongepowered", name:"spongeapi", version: "7.1.0"
implementation project(":model")
}
@@ -1,28 +0,0 @@
package com.vexsoftware.votifier.sponge.event;
import com.vexsoftware.votifier.model.Vote;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.impl.AbstractEvent;
/**
* This event is posted whenever a vote is received and processed by NuVotifier. Note that NuVotifier posts this event
* asynchronously, so you can only use limited portions of the Sponge API.
*/
public class VotifierEvent extends AbstractEvent {
private final Vote vote;
private final Cause cause;
public VotifierEvent(Vote vote, Cause cause) {
this.vote = vote;
this.cause = cause;
}
@Override
public Cause getCause() {
return cause;
}
public Vote getVote() {
return vote;
}
}
-4
View File
@@ -1,4 +0,0 @@
dependencies {
api group: "com.velocitypowered", name: "velocity-api", version: "1.0-SNAPSHOT"
implementation project(":model")
}
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more