temporary commit

This commit is contained in:
2023-02-12 22:36:37 +08:00
parent 47ec94caf2
commit f28ecca45e
11 changed files with 409 additions and 198 deletions

View File

@@ -1,5 +1,5 @@
plugins { plugins {
alias catalog.plugins.kotlin alias catalog.plugins.kotlin.jvm
alias catalog.plugins.envelope alias catalog.plugins.envelope
id 'maven-publish' id 'maven-publish'
} }
@@ -55,11 +55,19 @@ Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', E
mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer' mainClass = 'net.woggioni.gbcs.GradleBuildCacheServer'
systemProperty 'java.util.logging.config.class', 'net.woggioni.gbcs.LoggingConfig' systemProperty 'java.util.logging.config.class', 'net.woggioni.gbcs.LoggingConfig'
systemProperty 'log.config.source', 'logging.properties' systemProperty 'log.config.source', 'logging.properties'
manifest {
attributes([
'Add-Exports' : 'java.base/sun.security.x509'
])
}
} }
wrapper { envelopeRun {
distributionType = Wrapper.DistributionType.BIN
gradleVersion = getProperty('gradle.version') 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) { def envelopeJarArtifact = artifacts.add('archives', envelopeJarTaskProvider.get().archiveFile.get().asFile) {

View File

@@ -1,4 +1,3 @@
gbcs.version = 0.1-SNAPSHOT gbcs.version = 0.1-SNAPSHOT
gradle.version = 7.5.1 lys.version = 0.2-SNAPSHOT
lys.version = 0.1-SNAPSHOT

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -11,6 +11,7 @@ pluginManagement {
} }
gradlePluginPortal() gradlePluginPortal()
} }
includeBuild('../envelope')
} }
dependencyResolutionManagement { dependencyResolutionManagement {
@@ -30,3 +31,4 @@ dependencyResolutionManagement {
} }
rootProject.name = 'gbcs' rootProject.name = 'gbcs'

View File

@@ -1,3 +1,5 @@
import java.net.URLStreamHandlerFactory;
module net.woggioni.gbcs { module net.woggioni.gbcs {
requires java.xml; requires java.xml;
requires java.logging; requires java.logging;
@@ -12,4 +14,6 @@ module net.woggioni.gbcs {
requires net.woggioni.jwo; requires net.woggioni.jwo;
exports net.woggioni.gbcs; exports net.woggioni.gbcs;
opens net.woggioni.gbcs to net.woggioni.envelope;
uses java.net.URLStreamHandlerFactory;
} }

View File

@@ -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)
}
}
}

View File

@@ -1,10 +1,95 @@
package net.woggioni.gbcs package net.woggioni.gbcs
import java.nio.file.Path 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( data class Configuration(
val cacheFolder : Path, val cacheFolder : Path,
val host : String, val host : String,
val port : Int, 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)
}
}
}

View File

@@ -1,5 +1,20 @@
package net.woggioni.gbcs 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.bootstrap.ServerBootstrap
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled 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.HttpServerCodec
import io.netty.handler.codec.http.HttpUtil import io.netty.handler.codec.http.HttpUtil
import io.netty.handler.codec.http.LastHttpContent 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.ChunkedNioFile
import io.netty.handler.stream.ChunkedWriteHandler import io.netty.handler.stream.ChunkedWriteHandler
import io.netty.util.concurrent.DefaultEventExecutorGroup import io.netty.util.concurrent.DefaultEventExecutorGroup
import io.netty.util.concurrent.EventExecutorGroup import io.netty.util.concurrent.EventExecutorGroup
import net.woggioni.jwo.Application
import net.woggioni.jwo.JWO import net.woggioni.jwo.JWO
import java.nio.channels.FileChannel import net.woggioni.jwo.Tuple2
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
class GradleBuildCacheServer { class GradleBuildCacheServer(private val cfg : Configuration) {
internal class HttpChunkContentCompressor(threshold : Int, vararg compressionOptions: CompressionOptions = emptyArray()) internal class HttpChunkContentCompressor(threshold : Int, vararg compressionOptions: CompressionOptions = emptyArray())
: HttpContentCompressor(threshold, *compressionOptions) { : 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) { override fun initChannel(ch: Channel) {
val sslEngine: SSLEngine = SSLContext.getDefault().createSSLEngine()
sslEngine.useClientMode = false
val pipeline = ch.pipeline() 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(HttpServerCodec())
pipeline.addLast(HttpChunkContentCompressor(1024)) pipeline.addLast(HttpChunkContentCompressor(1024))
pipeline.addLast(ChunkedWriteHandler()) pipeline.addLast(ChunkedWriteHandler())
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE)) pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" }) // pipeline.addLast(NettyHttpBasicAuthenticator(mapOf("user" to "password")) { user, _ -> user == "user" })
pipeline.addLast(group, ServerHandler(cacheDir, "/cache")) pipeline.addLast(group, ServerHandler(cfg.cacheFolder, cfg.serverPath))
pipeline.addLast(ExceptionHandler()) pipeline.addLast(ExceptionHandler())
Files.createDirectories(cacheDir) Files.createDirectories(cfg.cacheFolder)
}
companion object {
val group: EventExecutorGroup = DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors())
} }
} }
@@ -254,12 +299,13 @@ class GradleBuildCacheServer {
// Configure the server // Configure the server
httpBootstrap.group(bossGroup, workerGroup) httpBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel::class.java) .channel(NioServerSocketChannel::class.java)
.childHandler(ServerInitializer(Paths.get("/tmp/gbcs"))) // <-- Our handler created here .childHandler(ServerInitializer(cfg))
.option(ChannelOption.SO_BACKLOG, 128) .option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.SO_KEEPALIVE, true)
// Bind and start to accept incoming connections. // 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 // Wait until server socket is closed
httpChannel.channel().closeFuture().sync() httpChannel.channel().closeFuture().sync()
@@ -270,11 +316,69 @@ class GradleBuildCacheServer {
} }
companion object { 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 @JvmStatic
fun main(args: Array<String>) { 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 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, fun digest(data : ByteArray,

View File

@@ -1,11 +1,5 @@
package net.woggioni.gbcs 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 java.net.URL
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
@@ -15,7 +9,60 @@ import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.validation.Schema import javax.xml.validation.Schema
import javax.xml.validation.SchemaFactory 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 { 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) val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
sf.setFeature(FEATURE_SECURE_PROCESSING, true) sf.setFeature(FEATURE_SECURE_PROCESSING, true)
disableProperty(sf, ACCESS_EXTERNAL_SCHEMA) // disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
disableProperty(sf, ACCESS_EXTERNAL_DTD) // disableProperty(sf, ACCESS_EXTERNAL_DTD)
val schemaUrl: URL = Xml::class.java.classLoader.getResource(schemaResourceURL) return sf.newSchema(schema)
?: throw IllegalStateException(String.format("Missing configuration schema '%s'", schemaResourceURL))
return sf.newSchema(schemaUrl)
} }
private fun newDocumentBuilderFactory(schemaResourceURL: String?): DocumentBuilderFactory { fun newDocumentBuilderFactory(): DocumentBuilderFactory {
val dbf = DocumentBuilderFactory.newInstance() val dbf = DocumentBuilderFactory.newInstance()
dbf.setFeature(FEATURE_SECURE_PROCESSING, true) dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
disableProperty(dbf, ACCESS_EXTERNAL_SCHEMA) disableProperty(dbf, ACCESS_EXTERNAL_SCHEMA)
@@ -84,35 +129,31 @@ object Xml {
dbf.isExpandEntityReferences = false dbf.isExpandEntityReferences = false
dbf.isIgnoringComments = true dbf.isIgnoringComments = true
dbf.isNamespaceAware = 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 return dbf
} }
fun newDocumentBuilder(resource: URL, schemaResourceURL: String?): DocumentBuilder { // fun newDocumentBuilder(resource: URL, schemaResourceURL: String?): DocumentBuilder {
val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder() // val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder()
db.setErrorHandler(XmlErrorHandler(resource)) // db.setErrorHandler(XmlErrorHandler(resource))
return db // return db
} // }
fun parseXmlResource(resource: URL, schemaResourceURL: String?): Document { // fun parseXmlResource(resource: URL, schemaResourceURL: String?): Document {
val db = newDocumentBuilder(resource, schemaResourceURL) // val db = newDocumentBuilder(resource, schemaResourceURL)
return resource.openStream().use(db::parse) // 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 { // fun parseXmlResource(resource: URL): Document {
val db = newDocumentBuilderFactory(null).newDocumentBuilder() // val db = newDocumentBuilder(resource, null)
db.setErrorHandler(XmlErrorHandler(resource)) // return resource.openStream().use(db::parse)
return db // }
}
fun parseXmlResource(resource: URL): Document { fun Element.asIterable() = Iterable { ElementIterator(this, null) }
val db = newDocumentBuilder(resource, null) fun NodeList.asIterable() = Iterable { NodeListIterator(this) }
return resource.openStream().use(db::parse)
}
} }

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<server xmlns="urn:gradle-build-cache-server"> <server xmlns="urn:gradle-build-cache-server" path="/cache">
<bind host="127.0.0.1" port="5680"/> <bind host="127.0.0.1" port="11443"/>
<cache path="/tmp/gbcs"/> <cache path="/tmp/gbcs"/>
<groups> <groups>
<readers> <readers>
@@ -10,5 +10,5 @@
<!-- <user name="writer"/>--> <!-- <user name="writer"/>-->
</writers> </writers>
</groups> </groups>
<tls name=""/> <tls verify-clients="true"/>
</server> </server>

View File

@@ -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" <xs:schema elementFormDefault="qualified" targetNamespace="urn:gradle-build-cache-server" version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:gbcs="urn:gradle-build-cache-server"> xmlns:gbcs="urn:gradle-build-cache-server">
<xs:element name="server" type="gbcs:serverType"/> <xs:element name="server" type="gbcs:serverType"/>
<xs:complexType name="serverType"> <xs:complexType name="serverType">
<xs:sequence minOccurs="0"> <xs:sequence minOccurs="0">
<xs:element name="bind" type="gbcs:bindType"/> <xs:element name="bind" type="gbcs:bindType"/>
@@ -10,6 +11,7 @@
<xs:element name="groups" type="gbcs:groupsType"/> <xs:element name="groups" type="gbcs:groupsType"/>
<xs:element name="tls" type="gbcs:tlsType"/> <xs:element name="tls" type="gbcs:tlsType"/>
</xs:sequence> </xs:sequence>
<xs:attribute name="path" type="xs:string"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="bindType"> <xs:complexType name="bindType">
@@ -39,28 +41,28 @@
</xs:complexType> </xs:complexType>
<xs:complexType name="tlsType"> <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:all>
<xs:element name="database" type="contour:databaseType"/> <xs:element name="keystore" type="gbcs:keyStoreType" minOccurs="0"/>
<xs:element name="application-properties" type="contour:propertiesType"/> <xs:element name="truststore" type="gbcs:trustStoreType" minOccurs="0"/>
<xs:element name="corda-node" type="contour:cordaNodeType" minOccurs="0" maxOccurs="1"/>
</xs:all> </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>
<xs:complexType name="propertiesType"> <xs:complexType name="propertiesType">
<xs:sequence> <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:sequence>
</xs:complexType> </xs:complexType>
@@ -72,114 +74,10 @@
</xs:simpleContent> </xs:simpleContent>
</xs:complexType> </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:complexType name="hostAndPortType">
<xs:attribute name="host" type="xs:string" use="required"/> <xs:attribute name="host" type="xs:string" use="required"/>
<xs:attribute name="port" type="xs:unsignedShort" use="required"/> <xs:attribute name="port" type="xs:unsignedShort" use="required"/>
</xs:complexType> </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> </xs:schema>