Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
241d95fe1c
|
|||
3b7030c302
|
|||
a8670277e7
|
|||
03ee75266d
|
@@ -0,0 +1,11 @@
|
|||||||
|
package net.woggioni.gbcs.api.exception;
|
||||||
|
|
||||||
|
public class ConfigurationException extends GbcsException {
|
||||||
|
public ConfigurationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConfigurationException(String message) {
|
||||||
|
this(message, null);
|
||||||
|
}
|
||||||
|
}
|
@@ -6,8 +6,9 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly project(':gbcs-api')
|
implementation project(':gbcs-api')
|
||||||
compileOnly catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
|
implementation catalog.jwo
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
@@ -3,6 +3,7 @@ module net.woggioni.gbcs.base {
|
|||||||
requires java.logging;
|
requires java.logging;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
requires kotlin.stdlib;
|
requires kotlin.stdlib;
|
||||||
|
requires net.woggioni.jwo;
|
||||||
|
|
||||||
exports net.woggioni.gbcs.base;
|
exports net.woggioni.gbcs.base;
|
||||||
}
|
}
|
@@ -2,6 +2,7 @@ package net.woggioni.gbcs.base
|
|||||||
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.event.Level
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.logging.LogManager
|
import java.util.logging.LogManager
|
||||||
@@ -52,6 +53,12 @@ inline fun log(log : Logger,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun Logger.log(level : Level, messageBuilder : () -> String) {
|
||||||
|
if(isEnabledForLevel(level)) {
|
||||||
|
makeLoggingEventBuilder(level).log(messageBuilder())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline fun Logger.trace(messageBuilder : () -> String) {
|
inline fun Logger.trace(messageBuilder : () -> String) {
|
||||||
if(isTraceEnabled) {
|
if(isTraceEnabled) {
|
||||||
trace(messageBuilder())
|
trace(messageBuilder())
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
package net.woggioni.gbcs.base
|
package net.woggioni.gbcs.base
|
||||||
|
|
||||||
|
import net.woggioni.jwo.CollectionUtils.mapValues
|
||||||
|
import net.woggioni.jwo.CollectionUtils.toUnmodifiableTreeMap
|
||||||
|
import net.woggioni.jwo.JWO
|
||||||
|
import net.woggioni.jwo.MapBuilder
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.event.Level
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.Node
|
import org.w3c.dom.Node
|
||||||
@@ -11,6 +16,8 @@ import org.xml.sax.SAXParseException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.TreeMap
|
||||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
||||||
import javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING
|
import javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING
|
||||||
@@ -80,31 +87,36 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun warning(ex: SAXParseException) {
|
override fun warning(ex: SAXParseException)= err(ex, Level.WARN)
|
||||||
log.warn(
|
|
||||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
|
||||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(ex: SAXParseException) {
|
private fun err(ex: SAXParseException, level: Level) {
|
||||||
log.error(
|
log.log(level) {
|
||||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
"Problem at ${fileURL}:${ex.lineNumber}:${ex.columnNumber} parsing deployment configuration: ${ex.message}"
|
||||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
}
|
||||||
)
|
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fatalError(ex: SAXParseException) {
|
override fun error(ex: SAXParseException) = err(ex, Level.ERROR)
|
||||||
log.error(
|
override fun fatalError(ex: SAXParseException) = err(ex, Level.ERROR)
|
||||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
|
||||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
|
||||||
)
|
|
||||||
throw ex
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val dictMap: Map<String, Map<String, Any>> = sequenceOf(
|
||||||
|
"env" to System.getenv().asSequence().map { (k, v) -> k to (v as Any) }.toMap(),
|
||||||
|
"sys" to System.getProperties().asSequence().map { (k, v) -> k as String to (v as Any) }.toMap()
|
||||||
|
).toMap()
|
||||||
|
|
||||||
|
private fun renderConfigurationTemplate(template: String): String {
|
||||||
|
return JWO.renderTemplate(template, emptyMap(), dictMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Element.renderAttribute(name : String, namespaceURI: String? = null) = if(namespaceURI == null) {
|
||||||
|
getAttribute(name)
|
||||||
|
} else {
|
||||||
|
getAttributeNS(name, namespaceURI)
|
||||||
|
}.takeIf(String::isNotEmpty)?.let(Companion::renderConfigurationTemplate)
|
||||||
|
|
||||||
|
|
||||||
fun Element.asIterable() = Iterable { ElementIterator(this, null) }
|
fun Element.asIterable() = Iterable { ElementIterator(this, null) }
|
||||||
fun NodeList.asIterable() = Iterable { NodeListIterator(this) }
|
fun NodeList.asIterable() = Iterable { NodeListIterator(this) }
|
||||||
|
|
||||||
|
@@ -4,6 +4,8 @@ plugins {
|
|||||||
alias catalog.plugins.envelope
|
alias catalog.plugins.envelope
|
||||||
alias catalog.plugins.sambal
|
alias catalog.plugins.sambal
|
||||||
alias catalog.plugins.graalvm.native.image
|
alias catalog.plugins.graalvm.native.image
|
||||||
|
alias catalog.plugins.graalvm.jlink
|
||||||
|
alias catalog.plugins.jpms.check
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,6 +14,8 @@ import net.woggioni.gradle.envelope.EnvelopeJarTask
|
|||||||
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
|
||||||
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
import net.woggioni.gradle.graalvm.NativeImagePlugin
|
||||||
import net.woggioni.gradle.graalvm.NativeImageTask
|
import net.woggioni.gradle.graalvm.NativeImageTask
|
||||||
|
import net.woggioni.gradle.graalvm.JlinkPlugin
|
||||||
|
import net.woggioni.gradle.graalvm.JlinkTask
|
||||||
|
|
||||||
Property<String> mainClassName = objects.property(String.class)
|
Property<String> mainClassName = objects.property(String.class)
|
||||||
mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli')
|
mainClassName.set('net.woggioni.gbcs.cli.GradleBuildCacheServerCli')
|
||||||
@@ -56,15 +60,20 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', E
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {
|
||||||
mainClass = 'net.woggioni.gbcs.GraalNativeImageConfiguration'
|
mainClass = mainClassName
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||||
mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer'
|
mainClass = mainClassName
|
||||||
useMusl = true
|
useMusl = true
|
||||||
buildStaticImage = true
|
buildStaticImage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.named(JlinkPlugin.JLINK_TASK_NAME, JlinkTask) {
|
||||||
|
mainClass = mainClassName
|
||||||
|
mainModule = 'net.woggioni.gbcs.cli'
|
||||||
|
}
|
||||||
|
|
||||||
artifacts {
|
artifacts {
|
||||||
release(envelopeJarTaskProvider)
|
release(envelopeJarTaskProvider)
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ module net.woggioni.gbcs.cli {
|
|||||||
requires net.woggioni.gbcs.client;
|
requires net.woggioni.gbcs.client;
|
||||||
requires kotlin.stdlib;
|
requires kotlin.stdlib;
|
||||||
requires net.woggioni.jwo;
|
requires net.woggioni.jwo;
|
||||||
|
requires net.woggioni.gbcs.api;
|
||||||
|
|
||||||
exports net.woggioni.gbcs.cli.impl.converters to info.picocli;
|
exports net.woggioni.gbcs.cli.impl.converters to info.picocli;
|
||||||
opens net.woggioni.gbcs.cli.impl.commands to info.picocli;
|
opens net.woggioni.gbcs.cli.impl.commands to info.picocli;
|
||||||
|
@@ -13,6 +13,7 @@ import java.util.Base64
|
|||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
@@ -53,9 +54,12 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
val entries = let {
|
val entries = let {
|
||||||
val completionQueue = LinkedBlockingQueue<Future<Pair<String, ByteArray>>>(numberOfEntries)
|
val completionQueue = LinkedBlockingQueue<Future<Pair<String, ByteArray>>>(numberOfEntries)
|
||||||
val start = Instant.now()
|
val start = Instant.now()
|
||||||
|
val totalElapsedTime = AtomicLong(0)
|
||||||
entryGenerator.take(numberOfEntries).forEach { entry ->
|
entryGenerator.take(numberOfEntries).forEach { entry ->
|
||||||
|
val requestStart = System.nanoTime()
|
||||||
val future = client.put(entry.first, entry.second).thenApply { entry }
|
val future = client.put(entry.first, entry.second).thenApply { entry }
|
||||||
future.whenComplete { _, _ ->
|
future.whenComplete { _, _ ->
|
||||||
|
totalElapsedTime.addAndGet((System.nanoTime() - requestStart))
|
||||||
completionQueue.put(future)
|
completionQueue.put(future)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,6 +82,9 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
val elapsed = Duration.between(start, end).toMillis()
|
val elapsed = Duration.between(start, end).toMillis()
|
||||||
"Insertion rate: ${numberOfEntries.toDouble() / elapsed * 1000} ops/s"
|
"Insertion rate: ${numberOfEntries.toDouble() / elapsed * 1000} ops/s"
|
||||||
}
|
}
|
||||||
|
log.info {
|
||||||
|
"Average time per insertion: ${totalElapsedTime.get() / numberOfEntries.toDouble() * 1000} ms"
|
||||||
|
}
|
||||||
inserted
|
inserted
|
||||||
}
|
}
|
||||||
log.info {
|
log.info {
|
||||||
@@ -86,8 +93,11 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
if (entries.isNotEmpty()) {
|
if (entries.isNotEmpty()) {
|
||||||
val completionQueue = LinkedBlockingQueue<Future<Unit>>(entries.size)
|
val completionQueue = LinkedBlockingQueue<Future<Unit>>(entries.size)
|
||||||
val start = Instant.now()
|
val start = Instant.now()
|
||||||
|
val totalElapsedTime = AtomicLong(0)
|
||||||
entries.forEach { entry ->
|
entries.forEach { entry ->
|
||||||
|
val requestStart = System.nanoTime()
|
||||||
val future = client.get(entry.first).thenApply {
|
val future = client.get(entry.first).thenApply {
|
||||||
|
totalElapsedTime.addAndGet((System.nanoTime() - requestStart))
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
log.error {
|
log.error {
|
||||||
"Missing entry for key '${entry.first}'"
|
"Missing entry for key '${entry.first}'"
|
||||||
@@ -112,6 +122,9 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
val elapsed = Duration.between(start, end).toMillis()
|
val elapsed = Duration.between(start, end).toMillis()
|
||||||
"Retrieval rate: ${entries.size.toDouble() / elapsed * 1000} ops/s"
|
"Retrieval rate: ${entries.size.toDouble() / elapsed * 1000} ops/s"
|
||||||
}
|
}
|
||||||
|
log.info {
|
||||||
|
"Average time per retrieval: ${totalElapsedTime.get() / numberOfEntries.toDouble() * 1e6} ms"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
||||||
}
|
}
|
||||||
|
@@ -2,11 +2,11 @@ package net.woggioni.gbcs.cli.impl.commands
|
|||||||
|
|
||||||
import net.woggioni.gbcs.GradleBuildCacheServer
|
import net.woggioni.gbcs.GradleBuildCacheServer
|
||||||
import net.woggioni.gbcs.GradleBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL
|
import net.woggioni.gbcs.GradleBuildCacheServer.Companion.DEFAULT_CONFIGURATION_URL
|
||||||
|
import net.woggioni.gbcs.api.Configuration
|
||||||
import net.woggioni.gbcs.base.contextLogger
|
import net.woggioni.gbcs.base.contextLogger
|
||||||
import net.woggioni.gbcs.base.debug
|
import net.woggioni.gbcs.base.debug
|
||||||
import net.woggioni.gbcs.base.info
|
import net.woggioni.gbcs.base.info
|
||||||
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||||
import net.woggioni.gbcs.client.GbcsClient
|
|
||||||
import net.woggioni.jwo.Application
|
import net.woggioni.jwo.Application
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
@@ -42,8 +42,8 @@ class ServerCommand(app : Application) : GbcsCommand() {
|
|||||||
)
|
)
|
||||||
private var configurationFile: Path = findConfigurationFile(app, "gbcs-server.xml")
|
private var configurationFile: Path = findConfigurationFile(app, "gbcs-server.xml")
|
||||||
|
|
||||||
val configuration : GbcsClient.Configuration by lazy {
|
val configuration : Configuration by lazy {
|
||||||
GbcsClient.Configuration.parse(configurationFile)
|
GradleBuildCacheServer.loadConfiguration(configurationFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@@ -15,8 +15,6 @@
|
|||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="io.netty" level="debug"/>
|
|
||||||
<logger name="io.netty.handler.ssl.BouncyCastlePemReader" level="info"/>
|
|
||||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
<logger name="com.google.code.yanf4j" level="warn"/>
|
||||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
||||||
</configuration>
|
</configuration>
|
@@ -4,6 +4,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':gbcs-api')
|
||||||
implementation project(':gbcs-base')
|
implementation project(':gbcs-base')
|
||||||
implementation catalog.picocli
|
implementation catalog.picocli
|
||||||
implementation catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
|
@@ -1,262 +0,0 @@
|
|||||||
package net.woggioni.gbcs.benchmark;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import net.woggioni.jwo.Fun;
|
|
||||||
import org.openjdk.jmh.annotations.Benchmark;
|
|
||||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
|
||||||
import org.openjdk.jmh.annotations.Level;
|
|
||||||
import org.openjdk.jmh.annotations.Mode;
|
|
||||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
|
||||||
import org.openjdk.jmh.annotations.Scope;
|
|
||||||
import org.openjdk.jmh.annotations.Setup;
|
|
||||||
import org.openjdk.jmh.annotations.State;
|
|
||||||
import org.openjdk.jmh.annotations.TearDown;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private static Properties loadProperties() {
|
|
||||||
Properties properties = new Properties();
|
|
||||||
try (final var is = Main.class.getResourceAsStream("/benchmark.properties")) {
|
|
||||||
properties.load(is);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Properties properties = loadProperties();
|
|
||||||
|
|
||||||
@State(Scope.Thread)
|
|
||||||
public static class ExecutionPlan {
|
|
||||||
private final Random random = new Random(101325);
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final HttpClient client = createHttpClient();
|
|
||||||
|
|
||||||
private final Map<String, byte[]> entries = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
private HttpClient createHttpClient() {
|
|
||||||
final var clientBuilder = HttpClient.newBuilder();
|
|
||||||
getSslContext().ifPresent(clientBuilder::sslContext);
|
|
||||||
return clientBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Map<String, byte[]> getEntries() {
|
|
||||||
return Collections.unmodifiableMap(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map.Entry<String, byte[]> newEntry() {
|
|
||||||
final var keyBuffer = new byte[0x20];
|
|
||||||
random.nextBytes(keyBuffer);
|
|
||||||
final var key = Base64.getUrlEncoder().encodeToString(keyBuffer);
|
|
||||||
final var value = new byte[0x1000];
|
|
||||||
random.nextBytes(value);
|
|
||||||
return Map.entry(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public HttpRequest.Builder newRequestBuilder(String key) {
|
|
||||||
final var requestBuilder = HttpRequest.newBuilder()
|
|
||||||
.uri(getServerURI().resolve(key));
|
|
||||||
String user = getUser();
|
|
||||||
if (user != null) {
|
|
||||||
requestBuilder.header("Authorization", buildAuthorizationHeader(user, getPassword()));
|
|
||||||
}
|
|
||||||
return requestBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public URI getServerURI() {
|
|
||||||
return new URI(properties.getProperty("gbcs.server.url"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientTrustStorePassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.truststore.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<KeyStore> getClientTrustStore() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.truststore.file"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.map(Path::of)
|
|
||||||
.map((Fun<Path, KeyStore>) keyStoreFile -> {
|
|
||||||
final var keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (final var is = Files.newInputStream(keyStoreFile)) {
|
|
||||||
keyStore.load(is, getClientTrustStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
}
|
|
||||||
return keyStore;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<KeyStore> getClientKeyStore() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.keystore.file"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.map(Path::of)
|
|
||||||
.map((Fun<Path, KeyStore>) keyStoreFile -> {
|
|
||||||
final var keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (final var is = Files.newInputStream(keyStoreFile)) {
|
|
||||||
keyStore.load(is, getClientKeyStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
}
|
|
||||||
return keyStore;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientKeyStorePassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.keystore.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientKeyPassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.key.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public String getUser() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.server.username"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public String getPassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.server.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildAuthorizationHeader(String user, String password) {
|
|
||||||
final var b64 = Base64.getEncoder().encode(String.format("%s:%s", user, password).getBytes(StandardCharsets.UTF_8));
|
|
||||||
return "Basic " + new String(b64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private Optional<SSLContext> getSslContext() {
|
|
||||||
return getClientKeyStore().map((Fun<KeyStore, SSLContext>) clientKeyStore -> {
|
|
||||||
final var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
|
||||||
kmf.init(clientKeyStore, getClientKeyStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
|
|
||||||
|
|
||||||
// Set up trust manager factory with the truststore
|
|
||||||
final var trustManagers = getClientTrustStore().map((Fun<KeyStore, TrustManager[]>) ts -> {
|
|
||||||
final var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
tmf.init(ts);
|
|
||||||
return tmf.getTrustManagers();
|
|
||||||
}).orElse(new TrustManager[0]);
|
|
||||||
|
|
||||||
// Create SSL context with the key and trust managers
|
|
||||||
final var sslContext = SSLContext.getInstance("TLS");
|
|
||||||
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
|
|
||||||
return sslContext;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Setup(Level.Trial)
|
|
||||||
public void setUp() {
|
|
||||||
final var client = getClient();
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
final var pair = newEntry();
|
|
||||||
final var requestBuilder = newRequestBuilder(pair.getKey())
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(pair.getValue()));
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (201 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
} else {
|
|
||||||
entries.put(pair.getKey(), pair.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TearDown
|
|
||||||
public void tearDown() {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Iterator<Map.Entry<String, byte[]>> it = null;
|
|
||||||
|
|
||||||
private Map.Entry<String, byte[]> nextEntry() {
|
|
||||||
if (it == null || !it.hasNext()) {
|
|
||||||
it = getEntries().entrySet().iterator();
|
|
||||||
}
|
|
||||||
return it.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Benchmark
|
|
||||||
@BenchmarkMode(Mode.Throughput)
|
|
||||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
|
||||||
public void get(ExecutionPlan plan) {
|
|
||||||
final var client = plan.getClient();
|
|
||||||
final var entry = plan.nextEntry();
|
|
||||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
|
||||||
.header("Accept", "application/octet-stream")
|
|
||||||
.GET();
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
if (200 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
} else {
|
|
||||||
if (!Arrays.equals(entry.getValue(), response.body())) {
|
|
||||||
throw new IllegalStateException("Retrieved unexpected value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Benchmark
|
|
||||||
@BenchmarkMode(Mode.Throughput)
|
|
||||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
|
||||||
public void put(Main.ExecutionPlan plan) {
|
|
||||||
final var client = plan.getClient();
|
|
||||||
final var entry = plan.nextEntry();
|
|
||||||
|
|
||||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(entry.getValue()));
|
|
||||||
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
if (201 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,6 +0,0 @@
|
|||||||
gbcs.server.url= https://gbcs.woggioni.net:443
|
|
||||||
gbcs.client.ssl.keystore.file=conf/woggioni@c962475fa38.p12
|
|
||||||
gbcs.client.ssl.keystore.password=password
|
|
||||||
gbcs.client.ssl.key.password=password
|
|
||||||
gbcs.client.ssl.truststore.file=conf/truststore.pfx
|
|
||||||
gbcs.client.ssl.truststore.password=password
|
|
@@ -7,6 +7,7 @@ module net.woggioni.gbcs.client {
|
|||||||
requires io.netty.buffer;
|
requires io.netty.buffer;
|
||||||
requires java.xml;
|
requires java.xml;
|
||||||
requires net.woggioni.gbcs.base;
|
requires net.woggioni.gbcs.base;
|
||||||
|
requires net.woggioni.gbcs.api;
|
||||||
requires io.netty.codec;
|
requires io.netty.codec;
|
||||||
requires org.slf4j;
|
requires org.slf4j;
|
||||||
|
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package net.woggioni.gbcs.client.impl
|
package net.woggioni.gbcs.client.impl
|
||||||
|
|
||||||
|
import net.woggioni.gbcs.api.exception.ConfigurationException
|
||||||
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
||||||
|
import net.woggioni.gbcs.base.Xml.Companion.renderAttribute
|
||||||
import net.woggioni.gbcs.client.GbcsClient
|
import net.woggioni.gbcs.client.GbcsClient
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@@ -21,17 +23,17 @@ object Parser {
|
|||||||
val tagName = child.localName
|
val tagName = child.localName
|
||||||
when (tagName) {
|
when (tagName) {
|
||||||
"profile" -> {
|
"profile" -> {
|
||||||
val name = child.getAttribute("name")
|
val name = child.renderAttribute("name") ?: throw ConfigurationException("name attribute is required")
|
||||||
val uri = child.getAttribute("base-url").let(::URI)
|
val uri = child.renderAttribute("base-url")?.let(::URI) ?: throw ConfigurationException("base-url attribute is required")
|
||||||
var authentication: GbcsClient.Configuration.Authentication? = null
|
var authentication: GbcsClient.Configuration.Authentication? = null
|
||||||
for (gchild in child.asIterable()) {
|
for (gchild in child.asIterable()) {
|
||||||
when (gchild.localName) {
|
when (gchild.localName) {
|
||||||
"tls-client-auth" -> {
|
"tls-client-auth" -> {
|
||||||
val keyStoreFile = gchild.getAttribute("key-store-file")
|
val keyStoreFile = gchild.renderAttribute("key-store-file")
|
||||||
val keyStorePassword =
|
val keyStorePassword =
|
||||||
gchild.getAttribute("key-store-password").takeIf(String::isNotEmpty)
|
gchild.renderAttribute("key-store-password")
|
||||||
val keyAlias = gchild.getAttribute("key-alias")
|
val keyAlias = gchild.renderAttribute("key-alias")
|
||||||
val keyPassword = gchild.getAttribute("key-password").takeIf(String::isNotEmpty)
|
val keyPassword = gchild.renderAttribute("key-password")
|
||||||
|
|
||||||
val keystore = KeyStore.getInstance("PKCS12").apply {
|
val keystore = KeyStore.getInstance("PKCS12").apply {
|
||||||
Files.newInputStream(Path.of(keyStoreFile)).use {
|
Files.newInputStream(Path.of(keyStoreFile)).use {
|
||||||
@@ -48,15 +50,14 @@ object Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"basic-auth" -> {
|
"basic-auth" -> {
|
||||||
val username = gchild.getAttribute("user")
|
val username = gchild.renderAttribute("user") ?: throw ConfigurationException("username attribute is required")
|
||||||
val password = gchild.getAttribute("password")
|
val password = gchild.renderAttribute("password") ?: throw ConfigurationException("password attribute is required")
|
||||||
authentication =
|
authentication =
|
||||||
GbcsClient.Configuration.Authentication.BasicAuthenticationCredentials(username, password)
|
GbcsClient.Configuration.Authentication.BasicAuthenticationCredentials(username, password)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val maxConnections = child.getAttribute("max-connections")
|
val maxConnections = child.renderAttribute("max-connections")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: 50
|
?: 50
|
||||||
profiles[name] = GbcsClient.Configuration.Profile(uri, authentication, maxConnections)
|
profiles[name] = GbcsClient.Configuration.Profile(uri, authentication, maxConnections)
|
||||||
|
@@ -23,14 +23,14 @@
|
|||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="basicAuthType">
|
<xs:complexType name="basicAuthType">
|
||||||
<xs:attribute name="user" type="xs:string" use="required"/>
|
<xs:attribute name="user" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="password" type="xs:string" use="required"/>
|
<xs:attribute name="password" type="xs:string" use="required"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="tlsClientAuthType">
|
<xs:complexType name="tlsClientAuthType">
|
||||||
<xs:attribute name="key-store-file" type="xs:anyURI" use="required"/>
|
<xs:attribute name="key-store-file" type="xs:anyURI" use="required"/>
|
||||||
<xs:attribute name="key-store-password" type="xs:string" use="required"/>
|
<xs:attribute name="key-store-password" type="xs:string" use="required"/>
|
||||||
<xs:attribute name="key-alias" type="xs:string" use="required"/>
|
<xs:attribute name="key-alias" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="key-password" type="xs:string" use="optional"/>
|
<xs:attribute name="key-password" type="xs:string" use="optional"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
@@ -2,10 +2,12 @@ package net.woggioni.gbcs.memcached
|
|||||||
|
|
||||||
import net.rubyeye.xmemcached.transcoders.CompressionMode
|
import net.rubyeye.xmemcached.transcoders.CompressionMode
|
||||||
import net.woggioni.gbcs.api.CacheProvider
|
import net.woggioni.gbcs.api.CacheProvider
|
||||||
|
import net.woggioni.gbcs.api.exception.ConfigurationException
|
||||||
import net.woggioni.gbcs.base.GBCS
|
import net.woggioni.gbcs.base.GBCS
|
||||||
import net.woggioni.gbcs.base.HostAndPort
|
import net.woggioni.gbcs.base.HostAndPort
|
||||||
import net.woggioni.gbcs.base.Xml
|
import net.woggioni.gbcs.base.Xml
|
||||||
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
||||||
|
import net.woggioni.gbcs.base.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -22,20 +24,13 @@ class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
|||||||
|
|
||||||
override fun deserialize(el: Element): MemcachedCacheConfiguration {
|
override fun deserialize(el: Element): MemcachedCacheConfiguration {
|
||||||
val servers = mutableListOf<HostAndPort>()
|
val servers = mutableListOf<HostAndPort>()
|
||||||
val maxAge = el.getAttribute("max-age")
|
val maxAge = el.renderAttribute("max-age")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(Duration::parse)
|
?.let(Duration::parse)
|
||||||
?: Duration.ofDays(1)
|
?: Duration.ofDays(1)
|
||||||
val maxSize = el.getAttribute("max-size")
|
val maxSize = el.renderAttribute("max-size")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: 0x100000
|
?: 0x100000
|
||||||
val enableCompression = el.getAttribute("enable-compression")
|
val compressionMode = el.renderAttribute("compression-mode")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toBoolean)
|
|
||||||
?: false
|
|
||||||
val compressionMode = el.getAttribute("compression-mode")
|
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let {
|
?.let {
|
||||||
when (it) {
|
when (it) {
|
||||||
"gzip" -> CompressionMode.GZIP
|
"gzip" -> CompressionMode.GZIP
|
||||||
@@ -44,11 +39,13 @@ class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
?: CompressionMode.ZIP
|
?: CompressionMode.ZIP
|
||||||
val digestAlgorithm = el.getAttribute("digest").takeIf(String::isNotEmpty)
|
val digestAlgorithm = el.renderAttribute("digest")
|
||||||
for (child in el.asIterable()) {
|
for (child in el.asIterable()) {
|
||||||
when (child.nodeName) {
|
when (child.nodeName) {
|
||||||
"server" -> {
|
"server" -> {
|
||||||
servers.add(HostAndPort(child.getAttribute("host"), child.getAttribute("port").toInt()))
|
val host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
||||||
|
val port = child.renderAttribute("port")?.toInt() ?: throw ConfigurationException("port attribute is required")
|
||||||
|
servers.add(HostAndPort(host, port))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<xs:import schemaLocation="classpath:net/woggioni/gbcs/schema/gbcs.xsd" namespace="urn:net.woggioni.gbcs"/>
|
<xs:import schemaLocation="classpath:net/woggioni/gbcs/schema/gbcs.xsd" namespace="urn:net.woggioni.gbcs"/>
|
||||||
|
|
||||||
<xs:complexType name="memcachedServerType">
|
<xs:complexType name="memcachedServerType">
|
||||||
<xs:attribute name="host" type="xs:string" use="required"/>
|
<xs:attribute name="host" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="port" type="xs:positiveInteger" use="required"/>
|
<xs:attribute name="port" type="xs:positiveInteger" use="required"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
@@ -507,9 +507,9 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
val DEFAULT_CONFIGURATION_URL by lazy { "classpath:net/woggioni/gbcs/gbcs-default.xml".toUrl() }
|
val DEFAULT_CONFIGURATION_URL by lazy { "classpath:net/woggioni/gbcs/gbcs-default.xml".toUrl() }
|
||||||
|
|
||||||
fun loadConfiguration(configurationFile: Path): Configuration {
|
fun loadConfiguration(configurationFile: Path): Configuration {
|
||||||
val dbf = Xml.newDocumentBuilderFactory(null)
|
val doc = Files.newInputStream(configurationFile).use {
|
||||||
val db = dbf.newDocumentBuilder()
|
Xml.parseXml(configurationFile.toUri().toURL(), it)
|
||||||
val doc = Files.newInputStream(configurationFile).use(db::parse)
|
}
|
||||||
return Parser.parse(doc)
|
return Parser.parse(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ package net.woggioni.gbcs.cache
|
|||||||
import net.woggioni.gbcs.api.CacheProvider
|
import net.woggioni.gbcs.api.CacheProvider
|
||||||
import net.woggioni.gbcs.base.GBCS
|
import net.woggioni.gbcs.base.GBCS
|
||||||
import net.woggioni.gbcs.base.Xml
|
import net.woggioni.gbcs.base.Xml
|
||||||
|
import net.woggioni.gbcs.base.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@@ -18,22 +19,18 @@ class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
|||||||
override fun getXmlNamespace() = "urn:net.woggioni.gbcs"
|
override fun getXmlNamespace() = "urn:net.woggioni.gbcs"
|
||||||
|
|
||||||
override fun deserialize(el: Element): FileSystemCacheConfiguration {
|
override fun deserialize(el: Element): FileSystemCacheConfiguration {
|
||||||
val path = el.getAttribute("path")
|
val path = el.renderAttribute("path")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(Path::of)
|
?.let(Path::of)
|
||||||
val maxAge = el.getAttribute("max-age")
|
val maxAge = el.renderAttribute("max-age")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(Duration::parse)
|
?.let(Duration::parse)
|
||||||
?: Duration.ofDays(1)
|
?: Duration.ofDays(1)
|
||||||
val enableCompression = el.getAttribute("enable-compression")
|
val enableCompression = el.renderAttribute("enable-compression")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toBoolean)
|
?.let(String::toBoolean)
|
||||||
?: true
|
?: true
|
||||||
val compressionLevel = el.getAttribute("compression-level")
|
val compressionLevel = el.renderAttribute("compression-level")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toInt)
|
?.let(String::toInt)
|
||||||
?: Deflater.DEFAULT_COMPRESSION
|
?: Deflater.DEFAULT_COMPRESSION
|
||||||
val digestAlgorithm = el.getAttribute("digest").takeIf(String::isNotEmpty) ?: "MD5"
|
val digestAlgorithm = el.renderAttribute("digest") ?: "MD5"
|
||||||
|
|
||||||
return FileSystemCacheConfiguration(
|
return FileSystemCacheConfiguration(
|
||||||
path,
|
path,
|
||||||
|
@@ -12,14 +12,15 @@ import net.woggioni.gbcs.api.Configuration.TlsCertificateExtractor
|
|||||||
import net.woggioni.gbcs.api.Configuration.TrustStore
|
import net.woggioni.gbcs.api.Configuration.TrustStore
|
||||||
import net.woggioni.gbcs.api.Configuration.User
|
import net.woggioni.gbcs.api.Configuration.User
|
||||||
import net.woggioni.gbcs.api.Role
|
import net.woggioni.gbcs.api.Role
|
||||||
|
import net.woggioni.gbcs.api.exception.ConfigurationException
|
||||||
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
||||||
|
import net.woggioni.gbcs.base.Xml.Companion.renderAttribute
|
||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import org.w3c.dom.TypeInfo
|
import org.w3c.dom.TypeInfo
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
|
|
||||||
fun parse(document: Document): Configuration {
|
fun parse(document: Document): Configuration {
|
||||||
val root = document.documentElement
|
val root = document.documentElement
|
||||||
val anonymousUser = User("", null, emptySet())
|
val anonymousUser = User("", null, emptySet())
|
||||||
@@ -29,9 +30,8 @@ object Parser {
|
|||||||
var users : Map<String, User> = mapOf(anonymousUser.name to anonymousUser)
|
var users : Map<String, User> = mapOf(anonymousUser.name to anonymousUser)
|
||||||
var groups = emptyMap<String, Group>()
|
var groups = emptyMap<String, Group>()
|
||||||
var tls: Tls? = null
|
var tls: Tls? = null
|
||||||
val serverPath = root.getAttribute("path")
|
val serverPath = root.renderAttribute("path")
|
||||||
val useVirtualThread = root.getAttribute("useVirtualThreads")
|
val useVirtualThread = root.renderAttribute("useVirtualThreads")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toBoolean) ?: true
|
?.let(String::toBoolean) ?: true
|
||||||
var authentication: Authentication? = null
|
var authentication: Authentication? = null
|
||||||
for (child in root.asIterable()) {
|
for (child in root.asIterable()) {
|
||||||
@@ -54,8 +54,8 @@ object Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"bind" -> {
|
"bind" -> {
|
||||||
host = child.getAttribute("host")
|
host = child.renderAttribute("host") ?: throw ConfigurationException("host attribute is required")
|
||||||
port = Integer.parseInt(child.getAttribute("port"))
|
port = Integer.parseInt(child.renderAttribute("port"))
|
||||||
}
|
}
|
||||||
|
|
||||||
"cache" -> {
|
"cache" -> {
|
||||||
@@ -80,14 +80,14 @@ object Parser {
|
|||||||
for (ggchild in gchild.asIterable()) {
|
for (ggchild in gchild.asIterable()) {
|
||||||
when (ggchild.localName) {
|
when (ggchild.localName) {
|
||||||
"group-extractor" -> {
|
"group-extractor" -> {
|
||||||
val attrName = ggchild.getAttribute("attribute-name")
|
val attrName = ggchild.renderAttribute("attribute-name")
|
||||||
val pattern = ggchild.getAttribute("pattern")
|
val pattern = ggchild.renderAttribute("pattern")
|
||||||
tlsExtractorGroup = TlsCertificateExtractor(attrName, pattern)
|
tlsExtractorGroup = TlsCertificateExtractor(attrName, pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
"user-extractor" -> {
|
"user-extractor" -> {
|
||||||
val attrName = ggchild.getAttribute("attribute-name")
|
val attrName = ggchild.renderAttribute("attribute-name")
|
||||||
val pattern = ggchild.getAttribute("pattern")
|
val pattern = ggchild.renderAttribute("pattern")
|
||||||
tlsExtractorUser = TlsCertificateExtractor(attrName, pattern)
|
tlsExtractorUser = TlsCertificateExtractor(attrName, pattern)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,20 +99,17 @@ object Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"tls" -> {
|
"tls" -> {
|
||||||
val verifyClients = child.getAttribute("verify-clients")
|
val verifyClients = child.renderAttribute("verify-clients")
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toBoolean) ?: false
|
?.let(String::toBoolean) ?: false
|
||||||
var keyStore: KeyStore? = null
|
var keyStore: KeyStore? = null
|
||||||
var trustStore: TrustStore? = null
|
var trustStore: TrustStore? = null
|
||||||
for (granChild in child.asIterable()) {
|
for (granChild in child.asIterable()) {
|
||||||
when (granChild.localName) {
|
when (granChild.localName) {
|
||||||
"keystore" -> {
|
"keystore" -> {
|
||||||
val keyStoreFile = Paths.get(granChild.getAttribute("file"))
|
val keyStoreFile = Paths.get(granChild.renderAttribute("file"))
|
||||||
val keyStorePassword = granChild.getAttribute("password")
|
val keyStorePassword = granChild.renderAttribute("password")
|
||||||
.takeIf(String::isNotEmpty)
|
val keyAlias = granChild.renderAttribute("key-alias")
|
||||||
val keyAlias = granChild.getAttribute("key-alias")
|
val keyPassword = granChild.renderAttribute("key-password")
|
||||||
val keyPassword = granChild.getAttribute("key-password")
|
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
keyStore = KeyStore(
|
keyStore = KeyStore(
|
||||||
keyStoreFile,
|
keyStoreFile,
|
||||||
keyStorePassword,
|
keyStorePassword,
|
||||||
@@ -122,11 +119,9 @@ object Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"truststore" -> {
|
"truststore" -> {
|
||||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
val trustStoreFile = Paths.get(granChild.renderAttribute("file"))
|
||||||
val trustStorePassword = granChild.getAttribute("password")
|
val trustStorePassword = granChild.renderAttribute("password")
|
||||||
.takeIf(String::isNotEmpty)
|
val checkCertificateStatus = granChild.renderAttribute("check-certificate-status")
|
||||||
val checkCertificateStatus = granChild.getAttribute("check-certificate-status")
|
|
||||||
.takeIf(String::isNotEmpty)
|
|
||||||
?.let(String::toBoolean)
|
?.let(String::toBoolean)
|
||||||
?: false
|
?: false
|
||||||
trustStore = TrustStore(
|
trustStore = TrustStore(
|
||||||
@@ -153,15 +148,15 @@ object Parser {
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
||||||
it.getAttribute("ref")
|
it.renderAttribute("ref")
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
private fun parseUsers(root: Element): Sequence<User> {
|
private fun parseUsers(root: Element): Sequence<User> {
|
||||||
return root.asIterable().asSequence().filter {
|
return root.asIterable().asSequence().filter {
|
||||||
it.localName == "user"
|
it.localName == "user"
|
||||||
}.map { el ->
|
}.map { el ->
|
||||||
val username = el.getAttribute("name")
|
val username = el.renderAttribute("name")
|
||||||
val password = el.getAttribute("password").takeIf(String::isNotEmpty)
|
val password = el.renderAttribute("password")
|
||||||
User(username, password, emptySet())
|
User(username, password, emptySet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,7 +167,7 @@ object Parser {
|
|||||||
val groups = root.asIterable().asSequence().filter {
|
val groups = root.asIterable().asSequence().filter {
|
||||||
it.localName == "group"
|
it.localName == "group"
|
||||||
}.map { el ->
|
}.map { el ->
|
||||||
val groupName = el.getAttribute("name")
|
val groupName = el.renderAttribute("name") ?: throw ConfigurationException("Group name is required")
|
||||||
var roles = emptySet<Role>()
|
var roles = emptySet<Role>()
|
||||||
for (child in el.asIterable()) {
|
for (child in el.asIterable()) {
|
||||||
when (child.localName) {
|
when (child.localName) {
|
||||||
|
@@ -28,7 +28,7 @@
|
|||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="bindType">
|
<xs:complexType name="bindType">
|
||||||
<xs:attribute name="host" type="xs:string" use="required"/>
|
<xs:attribute name="host" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="port" type="xs:unsignedShort" use="required"/>
|
<xs:attribute name="port" type="xs:unsignedShort" use="required"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
@@ -48,14 +48,14 @@
|
|||||||
|
|
||||||
<xs:complexType name="tlsCertificateAuthorizationType">
|
<xs:complexType name="tlsCertificateAuthorizationType">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element name="group-extractor" type="gbcs:X500NameExtractorType"/>
|
<xs:element name="group-extractor" type="gbcs:X500NameExtractorType" minOccurs="0"/>
|
||||||
<xs:element name="user-extractor" type="gbcs:X500NameExtractorType"/>
|
<xs:element name="user-extractor" type="gbcs:X500NameExtractorType" minOccurs="0"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="X500NameExtractorType">
|
<xs:complexType name="X500NameExtractorType">
|
||||||
<xs:attribute name="attribute-name" type="xs:string"/>
|
<xs:attribute name="attribute-name" type="xs:token"/>
|
||||||
<xs:attribute name="pattern" type="xs:string"/>
|
<xs:attribute name="pattern" type="xs:token"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="authorizationType">
|
<xs:complexType name="authorizationType">
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:complexType name="userType">
|
<xs:complexType name="userType">
|
||||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
<xs:attribute name="name" type="xs:token" use="required"/>
|
||||||
<xs:attribute name="password" type="xs:string" use="optional"/>
|
<xs:attribute name="password" type="xs:string" use="optional"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
@@ -105,11 +105,11 @@
|
|||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="roles" type="gbcs:rolesType" maxOccurs="1" minOccurs="0"/>
|
<xs:element name="roles" type="gbcs:rolesType" maxOccurs="1" minOccurs="0"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
<xs:attribute name="name" type="xs:string"/>
|
<xs:attribute name="name" type="xs:token"/>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:simpleType name="role" final="restriction" >
|
<xs:simpleType name="role" final="restriction" >
|
||||||
<xs:restriction base="xs:string">
|
<xs:restriction base="xs:token">
|
||||||
<xs:enumeration value="READER" />
|
<xs:enumeration value="READER" />
|
||||||
<xs:enumeration value="WRITER" />
|
<xs:enumeration value="WRITER" />
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
|
Reference in New Issue
Block a user