diff options
Diffstat (limited to 'src/main/kotlin/moe/nea/ledger/utils')
8 files changed, 146 insertions, 4 deletions
diff --git a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt index 4ba313e..344c8bc 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt @@ -10,6 +10,11 @@ class ErrorUtil { @Inject lateinit var reporter: EventRecorder + fun reportAdHoc(message: String) { + report(Exception(message), message) + + } + fun report(exception: Throwable, message: String?) { Span.current().recordException(reporter, exception, message) } diff --git a/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt new file mode 100644 index 0000000..d3c1f6e --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils + +import com.google.gson.reflect.TypeToken + +object GsonUtil { + inline fun <reified T> typeToken(): TypeToken<T> { + return object : TypeToken<T>() {} + } + +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt index 6940f72..133637a 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt @@ -6,6 +6,9 @@ import java.util.Stack @Suppress("UNCHECKED_CAST") class DI { + private fun formatInjectionStack() = + injectionStack.joinToString(" -> ") + private fun <T : Any, C> internalProvide(type: Class<T>, element: AnnotatedElement? = null): T { val provider = providers[type] as BaseDIProvider<T, C> val context = if (element == null) provider.createEmptyContext() else provider.createContext(element) @@ -13,13 +16,13 @@ class DI { val existingValue = values[key] if (existingValue != null) return existingValue as T if (type in injectionStack) { - error("Found injection cycle: ${injectionStack.joinToString(" -> ")} -> $type") + error("Found injection cycle: ${formatInjectionStack()} -> $type") } injectionStack.push(type) val value = try { provider.provideWithContext(this, context) } catch (ex: Exception) { - throw RuntimeException("Could not create instance for type $type", ex) + throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex) } val cycleCheckCookie = injectionStack.pop() require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt index b5ce550..bd5b9ef 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt @@ -24,6 +24,7 @@ fun interface DIProvider<T : Any> : BaseDIProvider<T, Unit> { ?: clazz.constructors.find { it.parameterCount == 0 } ?: error("Could not find DI injection entrypoint for class $clazz")) as Constructor<out T> + // TODO: consider using unsafe init to inject the parameters *before* calling the constructor return DIProvider { di -> val typArgs = cons.parameters.map { di.provide(it.type, it) diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt new file mode 100644 index 0000000..ddf2fcc --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt @@ -0,0 +1,40 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.JsonElement +import java.net.URL + +data class Request( + val url: URL, + val method: Method, + val body: String?, + val headers: Map<String, String>, +) { + enum class Method { + GET, POST + } + + enum class MediaType(val text: String) { + JSON("application/json"), + TEXT("text/plain"), + HTML("text/html"), + ANY("*/*"), + } + + fun withHeaders(map: Map<String, String>): Request { + // TODO: enforce caselessness? + return this.copy(headers = headers + map) + } + + fun post() = copy(method = Method.POST) + fun get() = copy(method = Method.GET) + + fun json(element: JsonElement) = copy( + headers = headers + mapOf("content-type" to "application/json"), + body = element.toString()) + + fun accept(request: MediaType) = withHeaders(mapOf("accept" to request.text)) + + fun acceptJson() = accept(MediaType.JSON) + + fun execute(requestUtil: RequestUtil) = requestUtil.executeRequest(this) +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt new file mode 100644 index 0000000..a49c65a --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt @@ -0,0 +1,63 @@ +package moe.nea.ledger.utils.network + +import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.di.Inject +import java.net.URL +import java.net.URLConnection +import java.security.KeyStore +import java.util.zip.GZIPInputStream +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory + +class RequestUtil @Inject constructor(val errorUtil: ErrorUtil) { + + private fun createSSLContext(): SSLContext? = errorUtil.catch { + val keyStorePath = RequestUtil::class.java.getResourceAsStream("/ledgerkeystore.jks") + ?: error("Could not locate keystore") + val keyStore = KeyStore.getInstance("JKS") + keyStore.load(keyStorePath, "neuneu".toCharArray()) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + kmf.init(keyStore, null) + tmf.init(keyStore) + val ctx = SSLContext.getInstance("TLS") + ctx.init(kmf.keyManagers, tmf.trustManagers, null) + return@catch ctx + } + + val sslContext = createSSLContext() + + fun enhanceConnection(connection: URLConnection) { + if (connection is HttpsURLConnection && sslContext != null) { + connection.sslSocketFactory = sslContext.socketFactory + } + } + + fun createRequest(url: String) = createRequest(URL(url)) + fun createRequest(url: URL) = Request(url, Request.Method.GET, null, mapOf()) + + fun executeRequest(request: Request): Response { + val connection = request.url.openConnection() + enhanceConnection(connection) + connection.setRequestProperty("accept-encoding", "gzip") + request.headers.forEach { (k, v) -> + connection.setRequestProperty(k, v) + } + if (request.body != null) { + connection.getOutputStream().write(request.body.encodeToByteArray()) + connection.getOutputStream().close() + } + var stream = connection.getInputStream() + if (connection.contentEncoding == "gzip") { + stream = GZIPInputStream(stream) + } + val text = stream.bufferedReader().readText() + stream.close() + // Do NOT call connection.disconnect() to allow for connection reuse + return Response(request, text, connection.headerFields) + } + + +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt new file mode 100644 index 0000000..daae7f7 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt @@ -0,0 +1,19 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.reflect.TypeToken +import moe.nea.ledger.Ledger + +data class Response( + val source: Request, + // TODO: allow other body processors, to avoid loading everything as strings + val response: String, + val headers: Map<String, List<String>>, +) { + fun <T : Any> json(typ: TypeToken<T>): T { + return Ledger.gson.fromJson(response, typ.type) + } + + fun <T : Any> json(clazz: Class<T>): T { + return Ledger.gson.fromJson(response, clazz) + } +}
\ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt index df588a8..96b70ec 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt @@ -22,8 +22,9 @@ class ExceptionContextValue(val exception: Throwable) : ContextValue { obj.addProperty("message", exception.message) // TODO: allow exceptions to implement an "extra info" interface if (searchDepth > 0) { - if (exception.cause != null) { - obj.add("cause", walkExceptions(exception, searchDepth - 1)) + val cause = exception.cause + if (cause != null && cause !== exception) { + obj.add("cause", walkExceptions(cause, searchDepth - 1)) } val suppressions = JsonArray() for (suppressedException in exception.suppressedExceptions) { |