temporary commit
This commit is contained in:
16
build.gradle
16
build.gradle
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
alias catalog.plugins.kotlin
|
||||
alias catalog.plugins.kotlin.jvm
|
||||
alias catalog.plugins.envelope
|
||||
id 'maven-publish'
|
||||
}
|
||||
@@ -55,11 +55,19 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', E
|
||||
mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer'
|
||||
systemProperty 'java.util.logging.config.class', 'net.woggioni.gbcs.LoggingConfig'
|
||||
systemProperty 'log.config.source', 'logging.properties'
|
||||
|
||||
manifest {
|
||||
attributes([
|
||||
'Add-Exports' : 'java.base/sun.security.x509'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
wrapper {
|
||||
distributionType = Wrapper.DistributionType.BIN
|
||||
gradleVersion = getProperty('gradle.version')
|
||||
envelopeRun {
|
||||
|
||||
mainModule = 'net.woggioni.envelope'
|
||||
modularity.inferModulePath = true
|
||||
jvmArgs('--add-exports=java.base/sun.security.x509=io.netty.handler')
|
||||
}
|
||||
|
||||
def envelopeJarArtifact = artifacts.add('archives', envelopeJarTaskProvider.get().archiveFile.get().asFile) {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
gbcs.version = 0.1-SNAPSHOT
|
||||
|
||||
gradle.version = 7.5.1
|
||||
lys.version = 0.1-SNAPSHOT
|
||||
lys.version = 0.2-SNAPSHOT
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@@ -11,6 +11,7 @@ pluginManagement {
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
includeBuild('../envelope')
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
@@ -30,3 +31,4 @@ dependencyResolutionManagement {
|
||||
}
|
||||
|
||||
rootProject.name = 'gbcs'
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
|
||||
module net.woggioni.gbcs {
|
||||
requires java.xml;
|
||||
requires java.logging;
|
||||
@@ -12,4 +14,6 @@ module net.woggioni.gbcs {
|
||||
requires net.woggioni.jwo;
|
||||
|
||||
exports net.woggioni.gbcs;
|
||||
opens net.woggioni.gbcs to net.woggioni.envelope;
|
||||
uses java.net.URLStreamHandlerFactory;
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.CertPathValidator
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.PKIXParameters
|
||||
import java.security.cert.PKIXRevocationChecker
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.EnumSet
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||
import io.netty.handler.ssl.SslHandler
|
||||
import io.netty.handler.ssl.SslHandshakeCompletionEvent
|
||||
import javax.net.ssl.SSLSession
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
|
||||
class ClientCertificateValidator private constructor(private val sslHandler : SslHandler, private val x509TrustManager: X509TrustManager) : ChannelInboundHandlerAdapter() {
|
||||
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
|
||||
if (evt is SslHandshakeCompletionEvent) {
|
||||
if (evt.isSuccess) {
|
||||
val session: SSLSession = sslHandler.engine().session
|
||||
val clientCertificateChain = session.peerCertificates as Array<X509Certificate>
|
||||
val authType: String = clientCertificateChain[0].publicKey.algorithm
|
||||
x509TrustManager.checkClientTrusted(clientCertificateChain, authType)
|
||||
} else {
|
||||
// Handle the failure, for example by closing the channel.
|
||||
}
|
||||
}
|
||||
super.userEventTriggered(ctx, evt)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(sslHandler : SslHandler, trustStore : KeyStore?) : ClientCertificateValidator {
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
|
||||
val validator = CertPathValidator.getInstance("PKIX").apply {
|
||||
val rc = revocationChecker as PKIXRevocationChecker
|
||||
rc.options = EnumSet.of(
|
||||
PKIXRevocationChecker.Option.NO_FALLBACK,
|
||||
PKIXRevocationChecker.Option.SOFT_FAIL,
|
||||
PKIXRevocationChecker.Option.PREFER_CRLS)
|
||||
}
|
||||
|
||||
val manager = if(trustStore != null) {
|
||||
val params = PKIXParameters(trustStore)
|
||||
object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
||||
validator.validate(clientCertificateChain, params)
|
||||
}
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
trustManagerFactory.trustManagers.asSequence().filter { it is X509TrustManager }.single() as X509TrustManager
|
||||
}
|
||||
return ClientCertificateValidator(sslHandler, manager)
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,10 +1,95 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import org.w3c.dom.Document
|
||||
import net.woggioni.gbcs.Xml.asIterable
|
||||
import org.w3c.dom.Element
|
||||
|
||||
data class HostAndPort(val host: String, val port : Integer) {
|
||||
override fun toString() = "$host:$port"
|
||||
}
|
||||
|
||||
data class TlsConfiguration(val keyStore: KeyStore?, val trustStore: TrustStore?, val verifyClients : Boolean)
|
||||
data class KeyStore(
|
||||
val file : Path,
|
||||
val password : String?,
|
||||
val keyAlias: String,
|
||||
val keyPassword : String?
|
||||
)
|
||||
|
||||
data class TrustStore(
|
||||
val file : Path,
|
||||
val password : String?,
|
||||
)
|
||||
|
||||
data class Configuration(
|
||||
val cacheFolder : Path,
|
||||
val host : String,
|
||||
val port : Int,
|
||||
val users : Map<String, Set<Role>>
|
||||
)
|
||||
val users : Map<String, Set<Role>>,
|
||||
val tlsConfiguration: TlsConfiguration?,
|
||||
val serverPath : String
|
||||
) {
|
||||
companion object {
|
||||
fun parse(document : Element) : Configuration {
|
||||
|
||||
var cacheFolder = Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
||||
var host : String = "127.0.0.1"
|
||||
var port : Int = 11080
|
||||
var users = emptyMap<String, Set<Role>>()
|
||||
var tlsConfiguration : TlsConfiguration? = null
|
||||
var serverPath = "/"
|
||||
|
||||
for(child in document.asIterable()) {
|
||||
when(child.nodeName) {
|
||||
"bind" -> {
|
||||
host = child.getAttribute("host")
|
||||
port = Integer.parseInt(child.getAttribute("port"))
|
||||
}
|
||||
"cache" -> {
|
||||
cacheFolder = Paths.get(child.textContent)
|
||||
}
|
||||
"tls" -> {
|
||||
val verifyClients = child.getAttribute("verify-clients")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toBoolean) ?: false
|
||||
var keyStore : KeyStore? = null
|
||||
var trustStore : TrustStore? = null
|
||||
for(granChild in child.asIterable()) {
|
||||
when(granChild.nodeName) {
|
||||
"keystore" -> {
|
||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||
val trustStorePassword = granChild.getAttribute("password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
val keyAlias = granChild.getAttribute("server-key-alias")
|
||||
val keyPasswordPassword = granChild.getAttribute("server-key-password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
keyStore = KeyStore(
|
||||
trustStoreFile,
|
||||
trustStorePassword,
|
||||
keyAlias,
|
||||
keyPasswordPassword
|
||||
)
|
||||
}
|
||||
"truststore" -> {
|
||||
val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
||||
val trustStorePassword = granChild.getAttribute("password")
|
||||
.takeIf(String::isNotEmpty)
|
||||
trustStore = TrustStore(
|
||||
trustStoreFile,
|
||||
trustStorePassword
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
tlsConfiguration = TlsConfiguration(keyStore, trustStore, verifyClients)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Configuration(cacheFolder, host, port, users, tlsConfiguration, serverPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,20 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URL
|
||||
import java.net.URLStreamHandlerFactory
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.AbstractMap.SimpleEntry
|
||||
import java.util.Base64
|
||||
import java.util.ServiceLoader
|
||||
import io.netty.bootstrap.ServerBootstrap
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled
|
||||
@@ -30,25 +45,18 @@ import io.netty.handler.codec.http.HttpResponseStatus
|
||||
import io.netty.handler.codec.http.HttpServerCodec
|
||||
import io.netty.handler.codec.http.HttpUtil
|
||||
import io.netty.handler.codec.http.LastHttpContent
|
||||
import io.netty.handler.ssl.SslContextBuilder
|
||||
import io.netty.handler.ssl.util.SelfSignedCertificate
|
||||
import io.netty.handler.stream.ChunkedNioFile
|
||||
import io.netty.handler.stream.ChunkedWriteHandler
|
||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||
import io.netty.util.concurrent.EventExecutorGroup
|
||||
import net.woggioni.jwo.Application
|
||||
import net.woggioni.jwo.JWO
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.security.MessageDigest
|
||||
import java.util.AbstractMap.SimpleEntry
|
||||
import java.util.Base64
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLEngine
|
||||
import net.woggioni.jwo.Tuple2
|
||||
|
||||
|
||||
class GradleBuildCacheServer {
|
||||
class GradleBuildCacheServer(private val cfg : Configuration) {
|
||||
|
||||
internal class HttpChunkContentCompressor(threshold : Int, vararg compressionOptions: CompressionOptions = emptyArray())
|
||||
: HttpContentCompressor(threshold, *compressionOptions) {
|
||||
@@ -115,25 +123,62 @@ class GradleBuildCacheServer {
|
||||
}
|
||||
}
|
||||
|
||||
private class ServerInitializer(private val cacheDir: Path) : ChannelInitializer<Channel>() {
|
||||
private class ServerInitializer(private val cfg : Configuration) : ChannelInitializer<Channel>() {
|
||||
|
||||
companion object {
|
||||
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
||||
fun loadKeystore(file : Path, password : String?) : KeyStore {
|
||||
val ext = JWO.splitExtension(file)
|
||||
.map(Tuple2<String, String>::get_2)
|
||||
.orElseThrow {
|
||||
IllegalArgumentException(
|
||||
"Keystore file '${file}' must have .jks or p12 extension")
|
||||
}
|
||||
val keystore = when(ext.lowercase()) {
|
||||
"jks" -> KeyStore.getInstance("JKS")
|
||||
"p12", "pfx" -> KeyStore.getInstance("PKCS12")
|
||||
else -> throw IllegalArgumentException(
|
||||
"Keystore file '${file}' must have .jks or p12 extension")
|
||||
}
|
||||
Files.newInputStream(file).use {
|
||||
keystore.load(it, password?.let(String::toCharArray))
|
||||
}
|
||||
return keystore
|
||||
}
|
||||
}
|
||||
|
||||
override fun initChannel(ch: Channel) {
|
||||
val sslEngine: SSLEngine = SSLContext.getDefault().createSSLEngine()
|
||||
sslEngine.useClientMode = false
|
||||
val pipeline = ch.pipeline()
|
||||
// pipeline.addLast(SslHandler(sslEngine))
|
||||
val tlsConfiguration = cfg.tlsConfiguration
|
||||
if(tlsConfiguration != null) {
|
||||
val ssc = SelfSignedCertificate()
|
||||
val keyStore = tlsConfiguration.keyStore
|
||||
val sslCtx = if(keyStore == null) {
|
||||
SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build()
|
||||
} else {
|
||||
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
||||
val serverKey = javaKeyStore.getKey(
|
||||
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)) as PrivateKey
|
||||
val serverCert = javaKeyStore.getCertificateChain(keyStore.keyAlias) as Array<X509Certificate>
|
||||
SslContextBuilder.forServer(serverKey, *serverCert).build()
|
||||
}
|
||||
val sslHandler = sslCtx.newHandler(ch.alloc())
|
||||
pipeline.addLast(sslHandler)
|
||||
if(tlsConfiguration.verifyClients) {
|
||||
val trustStore = tlsConfiguration.trustStore?.let {
|
||||
loadKeystore(it.file, it.password)
|
||||
}
|
||||
pipeline.addLast(ClientCertificateValidator.of(sslHandler, trustStore))
|
||||
}
|
||||
}
|
||||
pipeline.addLast(HttpServerCodec())
|
||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||
pipeline.addLast(ChunkedWriteHandler())
|
||||
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
|
||||
pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
|
||||
pipeline.addLast(group, ServerHandler(cacheDir, "/cache"))
|
||||
// pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
|
||||
pipeline.addLast(group, ServerHandler(cfg.cacheFolder, cfg.serverPath))
|
||||
pipeline.addLast(ExceptionHandler())
|
||||
Files.createDirectories(cacheDir)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
|
||||
Files.createDirectories(cfg.cacheFolder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,12 +299,13 @@ class GradleBuildCacheServer {
|
||||
// Configure the server
|
||||
httpBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel::class.java)
|
||||
.childHandler(ServerInitializer(Paths.get("/tmp/gbcs"))) // <-- Our handler created here
|
||||
.childHandler(ServerInitializer(cfg))
|
||||
.option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||
|
||||
// Bind and start to accept incoming connections.
|
||||
val httpChannel = httpBootstrap.bind(HTTP_PORT).sync()
|
||||
val bindAddress = InetSocketAddress(cfg.host, cfg.port)
|
||||
val httpChannel = httpBootstrap.bind(bindAddress).sync()
|
||||
|
||||
// Wait until server socket is closed
|
||||
httpChannel.channel().closeFuture().sync()
|
||||
@@ -270,11 +316,69 @@ class GradleBuildCacheServer {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val HTTP_PORT = 8080
|
||||
|
||||
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
|
||||
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
|
||||
|
||||
/**
|
||||
* Reset any cached handlers just in case a jar protocol has already been used. We
|
||||
* reset the handler by trying to set a null [URLStreamHandlerFactory] which
|
||||
* should have no effect other than clearing the handlers cache.
|
||||
*/
|
||||
private fun resetCachedUrlHandlers() {
|
||||
try {
|
||||
URL.setURLStreamHandlerFactory(null)
|
||||
} catch (ex: Error) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
fun registerUrlProtocolHandler() {
|
||||
val handlers = System.getProperty(PROTOCOL_HANDLER, "")
|
||||
System.setProperty(
|
||||
PROTOCOL_HANDLER,
|
||||
if (handlers == null || handlers.isEmpty()) HANDLERS_PACKAGE else "$handlers|$HANDLERS_PACKAGE"
|
||||
)
|
||||
resetCachedUrlHandlers()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SelfSignedCertificate()
|
||||
ServiceLoader.load(javaClass.module.layer, URLStreamHandlerFactory::class.java).stream().forEach {
|
||||
println(it.type())
|
||||
}
|
||||
// registerUrlProtocolHandler()
|
||||
Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
||||
GradleBuildCacheServer().run()
|
||||
val app = Application.builder("gbcs")
|
||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||
.configurationDirectoryPropertyKey("net.woggioni.gbcs.conf.dir")
|
||||
.build()
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve("gbcs.xml")
|
||||
|
||||
if(!Files.exists(configurationFile)) {
|
||||
Files.createDirectories(confDir)
|
||||
val defaultConfigurationFileResourcePath = "net/woggioni/gbcs/gbcs-default.xml"
|
||||
val defaultConfigurationFileResource = GradleBuildCacheServer.javaClass.classLoader
|
||||
.getResource(defaultConfigurationFileResourcePath)
|
||||
?: throw IllegalStateException(
|
||||
"Missing default configuration file 'classpath:$defaultConfigurationFileResourcePath'")
|
||||
Files.newOutputStream(configurationFile).use { outputStream ->
|
||||
defaultConfigurationFileResource.openStream().use { inputStream ->
|
||||
JWO.copy(inputStream, outputStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
val schemaResource = "net/woggioni/gbcs/gbcs.xsd"
|
||||
val schemaUrl = URL("classpath:net/woggioni/gbcs/gbcs.xsd")
|
||||
// val schemaUrl = GradleBuildCacheServer::class.java.classLoader.getResource(schemaResource)
|
||||
// ?: throw IllegalStateException("Missing configuration schema '$schemaResource'")
|
||||
val schemaUrl2 = URL(schemaUrl.toString())
|
||||
val dbf = Xml.newDocumentBuilderFactory()
|
||||
dbf.schema = Xml.getSchema(schemaUrl)
|
||||
val doc = Files.newInputStream(configurationFile)
|
||||
.use(dbf.newDocumentBuilder()::parse)
|
||||
GradleBuildCacheServer(Configuration.parse(doc.documentElement)).run()
|
||||
}
|
||||
|
||||
fun digest(data : ByteArray,
|
||||
|
@@ -1,11 +1,5 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.w3c.dom.Document
|
||||
import org.xml.sax.ErrorHandler
|
||||
import org.xml.sax.SAXNotRecognizedException
|
||||
import org.xml.sax.SAXNotSupportedException
|
||||
import org.xml.sax.SAXParseException
|
||||
import java.net.URL
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
||||
@@ -15,7 +9,60 @@ import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.validation.Schema
|
||||
import javax.xml.validation.SchemaFactory
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import org.xml.sax.ErrorHandler
|
||||
import org.xml.sax.SAXNotRecognizedException
|
||||
import org.xml.sax.SAXNotSupportedException
|
||||
import org.xml.sax.SAXParseException
|
||||
|
||||
class NodeListIterator(private val nodeList: NodeList) : Iterator<Node> {
|
||||
private var cursor : Int = 0
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor < nodeList.length
|
||||
}
|
||||
|
||||
override fun next(): Node {
|
||||
return if (hasNext()) nodeList.item(cursor++) else throw NoSuchElementException()
|
||||
}
|
||||
}
|
||||
|
||||
class ElementIterator(parent: Element, name: String? = null) : Iterator<Element> {
|
||||
private val it: NodeListIterator
|
||||
private val name: String?
|
||||
private var next: Element?
|
||||
|
||||
init {
|
||||
it = NodeListIterator(parent.childNodes)
|
||||
this.name = name
|
||||
next = getNext()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return next != null
|
||||
}
|
||||
|
||||
override fun next(): Element {
|
||||
val result = next ?: throw NoSuchElementException()
|
||||
next = getNext()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getNext(): Element? {
|
||||
var result: Element? = null
|
||||
while (it.hasNext()) {
|
||||
val node: Node = it.next()
|
||||
if (node is Element && (name == null || name == node.tagName)) {
|
||||
result = node
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
object Xml {
|
||||
|
||||
@@ -66,17 +113,15 @@ object Xml {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSchema(schemaResourceURL: String): Schema {
|
||||
fun getSchema(schema: URL): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
val schemaUrl: URL = Xml::class.java.classLoader.getResource(schemaResourceURL)
|
||||
?: throw IllegalStateException(String.format("Missing configuration schema '%s'", schemaResourceURL))
|
||||
return sf.newSchema(schemaUrl)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
return sf.newSchema(schema)
|
||||
}
|
||||
|
||||
private fun newDocumentBuilderFactory(schemaResourceURL: String?): DocumentBuilderFactory {
|
||||
fun newDocumentBuilderFactory(): DocumentBuilderFactory {
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
disableProperty(dbf, ACCESS_EXTERNAL_SCHEMA)
|
||||
@@ -84,35 +129,31 @@ object Xml {
|
||||
dbf.isExpandEntityReferences = false
|
||||
dbf.isIgnoringComments = true
|
||||
dbf.isNamespaceAware = true
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
if (schemaResourceURL != null) {
|
||||
dbf.schema = getSchema(schemaResourceURL)
|
||||
}
|
||||
return dbf
|
||||
}
|
||||
|
||||
fun newDocumentBuilder(resource: URL, schemaResourceURL: String?): DocumentBuilder {
|
||||
val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder()
|
||||
db.setErrorHandler(XmlErrorHandler(resource))
|
||||
return db
|
||||
}
|
||||
// fun newDocumentBuilder(resource: URL, schemaResourceURL: String?): DocumentBuilder {
|
||||
// val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder()
|
||||
// db.setErrorHandler(XmlErrorHandler(resource))
|
||||
// return db
|
||||
// }
|
||||
|
||||
fun parseXmlResource(resource: URL, schemaResourceURL: String?): Document {
|
||||
val db = newDocumentBuilder(resource, schemaResourceURL)
|
||||
return resource.openStream().use(db::parse)
|
||||
}
|
||||
// fun parseXmlResource(resource: URL, schemaResourceURL: String?): Document {
|
||||
// val db = newDocumentBuilder(resource, schemaResourceURL)
|
||||
// return resource.openStream().use(db::parse)
|
||||
// }
|
||||
//
|
||||
// fun newDocumentBuilder(resource: URL): DocumentBuilder {
|
||||
// val db = newDocumentBuilderFactory(null).newDocumentBuilder()
|
||||
// db.setErrorHandler(XmlErrorHandler(resource))
|
||||
// return db
|
||||
// }
|
||||
|
||||
fun newDocumentBuilder(resource: URL): DocumentBuilder {
|
||||
val db = newDocumentBuilderFactory(null).newDocumentBuilder()
|
||||
db.setErrorHandler(XmlErrorHandler(resource))
|
||||
return db
|
||||
}
|
||||
// fun parseXmlResource(resource: URL): Document {
|
||||
// val db = newDocumentBuilder(resource, null)
|
||||
// return resource.openStream().use(db::parse)
|
||||
// }
|
||||
|
||||
fun parseXmlResource(resource: URL): Document {
|
||||
val db = newDocumentBuilder(resource, null)
|
||||
return resource.openStream().use(db::parse)
|
||||
}
|
||||
fun Element.asIterable() = Iterable { ElementIterator(this, null) }
|
||||
fun NodeList.asIterable() = Iterable { NodeListIterator(this) }
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<server xmlns="urn:gradle-build-cache-server">
|
||||
<bind host="127.0.0.1" port="5680"/>
|
||||
<server xmlns="urn:gradle-build-cache-server" path="/cache">
|
||||
<bind host="127.0.0.1" port="11443"/>
|
||||
<cache path="/tmp/gbcs"/>
|
||||
<groups>
|
||||
<readers>
|
||||
@@ -10,5 +10,5 @@
|
||||
<!-- <user name="writer"/>-->
|
||||
</writers>
|
||||
</groups>
|
||||
<tls name=""/>
|
||||
<tls verify-clients="true"/>
|
||||
</server>
|
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<xs:schema elementFormDefault="qualified" targetNamespace="urn:gradle-build-cache-server" version="1.0"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:gbcs="urn:gradle-build-cache-server">
|
||||
<xs:element name="server" type="gbcs:serverType"/>
|
||||
|
||||
<xs:complexType name="serverType">
|
||||
<xs:sequence minOccurs="0">
|
||||
<xs:element name="bind" type="gbcs:bindType"/>
|
||||
@@ -10,6 +11,7 @@
|
||||
<xs:element name="groups" type="gbcs:groupsType"/>
|
||||
<xs:element name="tls" type="gbcs:tlsType"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="path" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="bindType">
|
||||
@@ -39,28 +41,28 @@
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="tlsType">
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
|
||||
<xs:complexType name="instancesType">
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" name="instance" type="contour:instanceType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="instanceType">
|
||||
<xs:all>
|
||||
<xs:element name="database" type="contour:databaseType"/>
|
||||
<xs:element name="application-properties" type="contour:propertiesType"/>
|
||||
<xs:element name="corda-node" type="contour:cordaNodeType" minOccurs="0" maxOccurs="1"/>
|
||||
<xs:element name="keystore" type="gbcs:keyStoreType" minOccurs="0"/>
|
||||
<xs:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/>
|
||||
</xs:all>
|
||||
<xs:attribute name="name" type="xs:token" use="required"/>
|
||||
<xs:attribute name="verify-clients" type="xs:boolean" use="optional"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="keyStoreType">
|
||||
<xs:attribute name="file" type="xs:string" use="required"/>
|
||||
<xs:attribute name="password" type="xs:string"/>
|
||||
<xs:attribute name="key-alias" type="xs:string" use="required"/>
|
||||
<xs:attribute name="key-password" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="trustStoreType">
|
||||
<xs:attribute name="file" type="xs:string" use="required"/>
|
||||
<xs:attribute name="password" type="xs:string"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="propertiesType">
|
||||
<xs:sequence>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" name="property" type="contour:propertyType"/>
|
||||
<xs:element maxOccurs="unbounded" minOccurs="0" name="property" type="gbcs:propertyType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
@@ -72,114 +74,10 @@
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="servicesType">
|
||||
<xs:all>
|
||||
<xs:element name="postgresDatabase" type="contour:postgresDatabaseType"/>
|
||||
<xs:element name="mailhogServer" type="contour:mailhogServerType"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="hostAndPortType">
|
||||
<xs:attribute name="host" type="xs:string" use="required"/>
|
||||
<xs:attribute name="port" type="xs:unsignedShort" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="postgresDatabaseType">
|
||||
<xs:all>
|
||||
<xs:element name="container-name" type="xs:token"/>
|
||||
<xs:element name="port" type="xs:unsignedShort"/>
|
||||
<xs:element name="password" type="xs:token"/>
|
||||
<xs:element name="image" type="xs:token"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="mailhogServerType">
|
||||
<xs:all>
|
||||
<xs:element name="container-name" type="xs:token"/>
|
||||
<xs:element name="http-port" type="xs:unsignedShort"/>
|
||||
<xs:element name="smtp-port" type="xs:unsignedShort"/>
|
||||
<xs:element name="image" type="xs:token"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="codeType">
|
||||
<xs:all>
|
||||
<xs:element name="front-end" type="contour:codeRepositoryType"/>
|
||||
<xs:element name="back-end" type="contour:codeRepositoryType"/>
|
||||
<xs:element name="cordapps" type="contour:codeRepositoryType"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="codeRepositoryType">
|
||||
<xs:attribute name="location" type="xs:token" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="databaseType">
|
||||
<xs:all>
|
||||
<xs:element name="url" type="xs:string"/>
|
||||
<xs:element name="name">
|
||||
<xs:complexType>
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string"/>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="urlType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:string"/>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="jvmType">
|
||||
<xs:attribute name="location" type="xs:token" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="artifactsType">
|
||||
<xs:all>
|
||||
<xs:element name="contract-cordapp" type="contour:mavenArtifactType"/>
|
||||
<xs:element name="workflow-cordapp" type="contour:mavenArtifactType"/>
|
||||
<xs:element name="business-tool-cordapp" type="contour:mavenArtifactType"/>
|
||||
<xs:element name="spring-backend" type="contour:mavenArtifactType"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="mavenArtifactType">
|
||||
<xs:attribute name="groupId" type="xs:string" use="required"/>
|
||||
<xs:attribute name="artifactId" type="xs:string" use="required"/>
|
||||
<xs:attribute name="version" type="xs:string" use="required"/>
|
||||
<xs:attribute name="ext" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="cordaNodeType">
|
||||
<xs:choice>
|
||||
<xs:element name="config" type="contour:simpleCordaConfigType"/>
|
||||
<xs:element name="configFile" type="xs:string"/>
|
||||
</xs:choice>
|
||||
<xs:attribute name="x500Name" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="rpcUsersType">
|
||||
<xs:sequence>
|
||||
<xs:element name="user" type="contour:rpcUserType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="rpcUserType">
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
<xs:attribute name="password" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
|
||||
<xs:complexType name="simpleCordaConfigType">
|
||||
<xs:all>
|
||||
<xs:element name="h2Port" type="xs:unsignedShort"/>
|
||||
<xs:element name="devMode" type="xs:boolean"/>
|
||||
<xs:element name="p2p-address" type="contour:hostAndPortType"/>
|
||||
<xs:element name="rpc-address" type="contour:hostAndPortType"/>
|
||||
<xs:element name="rpc-admin-address" type="contour:hostAndPortType"/>
|
||||
<xs:element name="rpc-users" type="contour:rpcUsersType"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
||||
|
Reference in New Issue
Block a user