renamed project to "Remote Cache Build Server" (RBCS)
This commit is contained in:
21
rbcs-common/build.gradle
Normal file
21
rbcs-common/build.gradle
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'maven-publish'
|
||||
alias catalog.plugins.kotlin.jvm
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':rbcs-api')
|
||||
implementation catalog.slf4j.api
|
||||
implementation catalog.jwo
|
||||
implementation catalog.netty.buffer
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
11
rbcs-common/src/main/java/module-info.java
Normal file
11
rbcs-common/src/main/java/module-info.java
Normal file
@@ -0,0 +1,11 @@
|
||||
module net.woggioni.rbcs.common {
|
||||
requires java.xml;
|
||||
requires java.logging;
|
||||
requires org.slf4j;
|
||||
requires kotlin.stdlib;
|
||||
requires net.woggioni.jwo;
|
||||
requires io.netty.buffer;
|
||||
|
||||
provides java.net.spi.URLStreamHandlerProvider with net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory;
|
||||
exports net.woggioni.rbcs.common;
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import java.io.InputStream
|
||||
|
||||
class ByteBufInputStream(private val buf : ByteBuf) : InputStream() {
|
||||
override fun read(): Int {
|
||||
return buf.takeIf {
|
||||
it.readableBytes() > 0
|
||||
}?.let(ByteBuf::readByte)
|
||||
?.let(Byte::toInt) ?: -1
|
||||
}
|
||||
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
val readableBytes = buf.readableBytes()
|
||||
if(readableBytes == 0) return -1
|
||||
val result = len.coerceAtMost(readableBytes)
|
||||
buf.readBytes(b, off, result)
|
||||
return result
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
buf.release()
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import java.io.OutputStream
|
||||
|
||||
class ByteBufOutputStream(private val buf : ByteBuf) : OutputStream() {
|
||||
override fun write(b: Int) {
|
||||
buf.writeByte(b)
|
||||
}
|
||||
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
buf.writeBytes(b, off, len)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
buf.release()
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
class ResourceNotFoundException(msg : String? = null, cause: Throwable? = null) : RuntimeException(msg, cause) {
|
||||
}
|
||||
|
||||
class ModuleNotFoundException(msg : String? = null, cause: Throwable? = null) : RuntimeException(msg, cause) {
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
|
||||
data class HostAndPort(val host: String, val port: Int = 0) {
|
||||
override fun toString(): String {
|
||||
return "$host:$port"
|
||||
}
|
||||
}
|
111
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Logging.kt
Normal file
111
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Logging.kt
Normal file
@@ -0,0 +1,111 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.event.Level
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.logging.LogManager
|
||||
|
||||
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
||||
|
||||
inline fun Logger.traceParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isTraceEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
trace(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.debugParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isDebugEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
info(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.infoParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isInfoEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
info(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.warnParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isWarnEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
warn(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.errorParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isErrorEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
error(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline fun log(log : Logger,
|
||||
filter : Logger.() -> Boolean,
|
||||
loggerMethod : Logger.(String) -> Unit, messageBuilder : () -> String) {
|
||||
if(log.filter()) {
|
||||
log.loggerMethod(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.log(level : Level, messageBuilder : () -> String) {
|
||||
if(isEnabledForLevel(level)) {
|
||||
makeLoggingEventBuilder(level).log(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.trace(messageBuilder : () -> String) {
|
||||
if(isTraceEnabled) {
|
||||
trace(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.debug(messageBuilder : () -> String) {
|
||||
if(isDebugEnabled) {
|
||||
debug(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.info(messageBuilder : () -> String) {
|
||||
if(isInfoEnabled) {
|
||||
info(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.warn(messageBuilder : () -> String) {
|
||||
if(isWarnEnabled) {
|
||||
warn(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.error(messageBuilder : () -> String) {
|
||||
if(isErrorEnabled) {
|
||||
error(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LoggingConfig {
|
||||
|
||||
init {
|
||||
val logManager = LogManager.getLogManager()
|
||||
System.getProperty("log.config.source")?.let withSource@ { source ->
|
||||
val urls = LoggingConfig::class.java.classLoader.getResources(source)
|
||||
while(urls.hasMoreElements()) {
|
||||
val url = urls.nextElement()
|
||||
url.openStream().use { inputStream ->
|
||||
logManager.readConfiguration(inputStream)
|
||||
return@withSource
|
||||
}
|
||||
}
|
||||
Path.of(source).takeIf(Files::exists)
|
||||
?.let(Files::newInputStream)
|
||||
?.use(logManager::readConfiguration)
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.KeySpec
|
||||
import java.util.Base64
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
|
||||
object PasswordSecurity {
|
||||
private const val KEY_LENGTH = 256
|
||||
|
||||
private fun concat(arr1: ByteArray, arr2: ByteArray): ByteArray {
|
||||
val result = ByteArray(arr1.size + arr2.size)
|
||||
var j = 0
|
||||
for(element in arr1) {
|
||||
result[j] = element
|
||||
j += 1
|
||||
}
|
||||
for(element in arr2) {
|
||||
result[j] = element
|
||||
j += 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun hashPassword(password : String, salt : String? = null) : String {
|
||||
val actualSalt = salt?.let(Base64.getDecoder()::decode) ?: SecureRandom().run {
|
||||
val result = ByteArray(16)
|
||||
nextBytes(result)
|
||||
result
|
||||
}
|
||||
val spec: KeySpec = PBEKeySpec(password.toCharArray(), actualSalt, 10, KEY_LENGTH)
|
||||
val factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
|
||||
val hash = factory.generateSecret(spec).encoded
|
||||
return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
|
||||
}
|
||||
|
||||
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> {
|
||||
val decoded = Base64.getDecoder().decode(passwordHash)
|
||||
val hash = ByteArray(KEY_LENGTH / 8)
|
||||
val salt = ByteArray(decoded.size - KEY_LENGTH / 8)
|
||||
System.arraycopy(decoded, 0, hash, 0, hash.size)
|
||||
System.arraycopy(decoded, hash.size, salt, 0, salt.size)
|
||||
return hash to salt
|
||||
}
|
||||
}
|
29
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt
Normal file
29
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/RBCS.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import net.woggioni.jwo.JWO
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
|
||||
object RBCS {
|
||||
fun String.toUrl() : URL = URL.of(URI(this), null)
|
||||
|
||||
const val RBCS_NAMESPACE_URI: String = "urn:net.woggioni.rbcs.server"
|
||||
const val RBCS_PREFIX: String = "rbcs"
|
||||
const val XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
|
||||
fun digest(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): ByteArray {
|
||||
md.update(data)
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
fun digestString(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): String {
|
||||
return JWO.bytesToHex(digest(data, md))
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.net.URLConnection
|
||||
import java.net.URLStreamHandler
|
||||
import java.net.spi.URLStreamHandlerProvider
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
class RbcsUrlStreamHandlerFactory : URLStreamHandlerProvider() {
|
||||
|
||||
private class ClasspathHandler(private val classLoader: ClassLoader = RbcsUrlStreamHandlerFactory::class.java.classLoader) :
|
||||
URLStreamHandler() {
|
||||
|
||||
override fun openConnection(u: URL): URLConnection? {
|
||||
return javaClass.module
|
||||
?.takeIf { m: Module -> m.layer != null }
|
||||
?.let {
|
||||
val path = u.path
|
||||
val i = path.lastIndexOf('/')
|
||||
val packageName = path.substring(0, i).replace('/', '.')
|
||||
val modules = packageMap[packageName]!!
|
||||
ClasspathResourceURLConnection(
|
||||
u,
|
||||
modules
|
||||
)
|
||||
}
|
||||
?: classLoader.getResource(u.path)?.let(URL::openConnection)
|
||||
}
|
||||
}
|
||||
|
||||
private class JpmsHandler : URLStreamHandler() {
|
||||
|
||||
override fun openConnection(u: URL): URLConnection {
|
||||
val moduleName = u.host
|
||||
val thisModule = javaClass.module
|
||||
val sourceModule =
|
||||
thisModule
|
||||
?.let(Module::getLayer)
|
||||
?.let { layer: ModuleLayer ->
|
||||
layer.findModule(moduleName).orElse(null)
|
||||
} ?: if(thisModule.layer == null) {
|
||||
thisModule
|
||||
} else throw ModuleNotFoundException("Module '$moduleName' not found")
|
||||
|
||||
return JpmsResourceURLConnection(u, sourceModule)
|
||||
}
|
||||
}
|
||||
|
||||
private class JpmsResourceURLConnection(url: URL, private val module: Module) : URLConnection(url) {
|
||||
override fun connect() {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputStream(): InputStream {
|
||||
val resource = getURL().path
|
||||
return module.getResourceAsStream(resource)
|
||||
?: throw ResourceNotFoundException("Resource '$resource' not found in module '${module.name}'")
|
||||
}
|
||||
}
|
||||
|
||||
override fun createURLStreamHandler(protocol: String): URLStreamHandler? {
|
||||
return when (protocol) {
|
||||
"classpath" -> ClasspathHandler()
|
||||
"jpms" -> JpmsHandler()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private class ClasspathResourceURLConnection(url: URL?, private val modules: List<Module>) :
|
||||
URLConnection(url) {
|
||||
override fun connect() {}
|
||||
|
||||
override fun getInputStream(): InputStream? {
|
||||
for (module in modules) {
|
||||
val result = module.getResourceAsStream(getURL().path)
|
||||
if (result != null) return result
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val installed = AtomicBoolean(false)
|
||||
fun install() {
|
||||
if (!installed.getAndSet(true)) {
|
||||
URL.setURLStreamHandlerFactory(RbcsUrlStreamHandlerFactory())
|
||||
}
|
||||
}
|
||||
|
||||
private val packageMap: Map<String, List<Module>> by lazy {
|
||||
RbcsUrlStreamHandlerFactory::class.java.module.layer
|
||||
.modules()
|
||||
.stream()
|
||||
.flatMap { m: Module ->
|
||||
m.packages.stream()
|
||||
.map { p: String -> p to m }
|
||||
}
|
||||
.collect(
|
||||
Collectors.groupingBy(
|
||||
Pair<String, Module>::first,
|
||||
Collectors.mapping(
|
||||
Pair<String, Module>::second,
|
||||
Collectors.toUnmodifiableList<Module>()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
244
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Xml.kt
Normal file
244
rbcs-common/src/main/kotlin/net/woggioni/rbcs/common/Xml.kt
Normal file
@@ -0,0 +1,244 @@
|
||||
package net.woggioni.rbcs.common
|
||||
|
||||
import net.woggioni.jwo.JWO
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.event.Level
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import org.xml.sax.SAXNotRecognizedException
|
||||
import org.xml.sax.SAXNotSupportedException
|
||||
import org.xml.sax.SAXParseException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.URL
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
||||
import javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING
|
||||
import javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import javax.xml.transform.stream.StreamSource
|
||||
import javax.xml.validation.Schema
|
||||
import javax.xml.validation.SchemaFactory
|
||||
import org.xml.sax.ErrorHandler as ErrHandler
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
class Xml(val doc: Document, val element: Element) {
|
||||
|
||||
class ErrorHandler(private val fileURL: URL) : ErrHandler {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
||||
}
|
||||
|
||||
override fun warning(ex: SAXParseException)= err(ex, Level.WARN)
|
||||
|
||||
private fun err(ex: SAXParseException, level: Level) {
|
||||
log.log(level) {
|
||||
"Problem at ${fileURL}:${ex.lineNumber}:${ex.columnNumber} parsing deployment configuration: ${ex.message}"
|
||||
}
|
||||
throw ex
|
||||
}
|
||||
|
||||
override fun error(ex: SAXParseException) = err(ex, Level.ERROR)
|
||||
override fun fatalError(ex: SAXParseException) = err(ex, Level.ERROR)
|
||||
}
|
||||
|
||||
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).replace("$$", "$")
|
||||
}
|
||||
|
||||
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 NodeList.asIterable() = Iterable { NodeListIterator(this) }
|
||||
|
||||
private fun disableProperty(dbf: DocumentBuilderFactory, propertyName: String) {
|
||||
try {
|
||||
dbf.setAttribute(propertyName, "")
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
// Property not supported.
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableProperty(sf: SchemaFactory, propertyName: String) {
|
||||
try {
|
||||
sf.setProperty(propertyName, "")
|
||||
} catch (ex: SAXNotRecognizedException) {
|
||||
// Property not supported.
|
||||
} catch (ex: SAXNotSupportedException) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getSchema(schema: URL): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
||||
sf.errorHandler = ErrorHandler(schema)
|
||||
return sf.newSchema(schema)
|
||||
}
|
||||
|
||||
fun getSchema(inputStream: InputStream): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
return sf.newSchema(StreamSource(inputStream))
|
||||
}
|
||||
|
||||
fun newDocumentBuilderFactory(schemaResourceURL: URL?): DocumentBuilderFactory {
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
||||
dbf.setAttribute(ACCESS_EXTERNAL_SCHEMA, "all")
|
||||
disableProperty(dbf, ACCESS_EXTERNAL_DTD)
|
||||
dbf.isExpandEntityReferences = true
|
||||
dbf.isIgnoringComments = true
|
||||
dbf.isNamespaceAware = true
|
||||
dbf.isValidating = schemaResourceURL == null
|
||||
dbf.setFeature("http://apache.org/xml/features/validation/schema", true)
|
||||
schemaResourceURL?.let {
|
||||
dbf.schema = getSchema(it)
|
||||
}
|
||||
return dbf
|
||||
}
|
||||
|
||||
fun newDocumentBuilder(resource: URL, schemaResourceURL: URL?): DocumentBuilder {
|
||||
val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder()
|
||||
db.setErrorHandler(ErrorHandler(resource))
|
||||
return db
|
||||
}
|
||||
|
||||
fun parseXmlResource(resource: URL, schemaResourceURL: URL?): Document {
|
||||
val db = newDocumentBuilder(resource, schemaResourceURL)
|
||||
return resource.openStream().use(db::parse)
|
||||
}
|
||||
|
||||
fun parseXml(sourceURL: URL, sourceStream: InputStream? = null, schemaResourceURL: URL? = null): Document {
|
||||
val db = newDocumentBuilder(sourceURL, schemaResourceURL)
|
||||
return sourceStream?.let(db::parse) ?: sourceURL.openStream().use(db::parse)
|
||||
}
|
||||
|
||||
fun write(doc: Document, output: OutputStream) {
|
||||
val transformerFactory = TransformerFactory.newInstance()
|
||||
val transformer = transformerFactory.newTransformer()
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
|
||||
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes")
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")
|
||||
val source = DOMSource(doc)
|
||||
val result = StreamResult(output)
|
||||
transformer.transform(source, result)
|
||||
}
|
||||
|
||||
fun of(
|
||||
namespaceURI: String,
|
||||
qualifiedName: String,
|
||||
schemaResourceURL: URL? = null,
|
||||
cb: Xml.(el: Element) -> Unit
|
||||
): Document {
|
||||
val dbf = newDocumentBuilderFactory(schemaResourceURL)
|
||||
val db = dbf.newDocumentBuilder()
|
||||
val doc = db.newDocument()
|
||||
val root = doc.createElementNS(namespaceURI, qualifiedName)
|
||||
.also(doc::appendChild)
|
||||
Xml(doc, root).cb(root)
|
||||
return doc
|
||||
}
|
||||
|
||||
fun of(doc: Document, el: Element, cb: Xml.(el: Element) -> Unit): Element {
|
||||
Xml(doc, el).cb(el)
|
||||
return el
|
||||
}
|
||||
|
||||
fun Element.removeChildren() {
|
||||
while (true) {
|
||||
removeChild(firstChild ?: break)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun node(
|
||||
name: String,
|
||||
namespaceURI: String? = null,
|
||||
attrs: Map<String, String> = emptyMap(),
|
||||
cb: Xml.(el: Element) -> Unit = {}
|
||||
): Element {
|
||||
val child = doc.createElementNS(namespaceURI, name)
|
||||
for ((key, value) in attrs) {
|
||||
child.setAttribute(key, value)
|
||||
}
|
||||
return child
|
||||
.also {
|
||||
element.appendChild(it)
|
||||
Xml(doc, it).cb(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun attr(key: String, value: String, namespaceURI: String? = null) {
|
||||
element.setAttributeNS(namespaceURI, key, value)
|
||||
}
|
||||
|
||||
fun text(txt: String) {
|
||||
element.appendChild(doc.createTextNode(txt))
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
net.woggioni.rbcs.common.RbcsUrlStreamHandlerFactory
|
Reference in New Issue
Block a user