diff --git a/api-webapp/pom.xml b/api-webapp/pom.xml
index aefa2a2..4ddf8bc 100644
--- a/api-webapp/pom.xml
+++ b/api-webapp/pom.xml
@@ -114,6 +114,11 @@
+
+ org.jeudego.pairgoth
+ pairgoth-common
+ ${project.version}
+
org.jetbrains.kotlin
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt
index 343dfc3..f37dbb1 100644
--- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt
+++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/ApiServlet.kt
@@ -65,7 +65,7 @@ class ApiServlet: HttpServlet() {
// validate request
- if ("dev" == WebappManager.getProperty("env")) {
+ if ("dev" == WebappManager.properties["env"]) {
response.addHeader("Access-Control-Allow-Origin", "*")
}
validateAccept(request);
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/WebappManager.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/WebappManager.kt
index bbddeed..dc7412b 100644
--- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/WebappManager.kt
+++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/server/WebappManager.kt
@@ -1,171 +1,30 @@
package org.jeudego.pairgoth.server
import com.republicate.mailer.SmtpLoop
-import org.apache.commons.lang3.tuple.Pair
-import org.slf4j.LoggerFactory
-import java.io.IOException
-import java.lang.IllegalAccessError
-import java.security.SecureRandom
-import java.security.cert.X509Certificate
-import java.util.*
-import java.util.IllegalFormatCodePointException
-import javax.net.ssl.*
+import org.jeudego.pairgoth.web.BaseWebappManager
import javax.servlet.*
import javax.servlet.annotation.WebListener
-import javax.servlet.http.HttpSessionEvent
-import javax.servlet.http.HttpSessionListener
@WebListener
-class WebappManager : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
- private fun disableSSLCertificateChecks() {
- // see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
- try {
- // Create a trust manager that does not validate certificate chains
- val trustAllCerts = arrayOf(object : X509TrustManager {
- override fun getAcceptedIssuers(): Array? {
- return null
- }
-
- @Suppress("TrustAllX509TrustManager")
- override fun checkClientTrusted(certs: Array, authType: String) {}
- @Suppress("TrustAllX509TrustManager")
- override fun checkServerTrusted(certs: Array, authType: String) {}
- }
- )
-
- // Install the all-trusting trust manager
- val sc = SSLContext.getInstance("SSL")
- sc.init(null, trustAllCerts, SecureRandom())
- HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
-
- // Create all-trusting host name verifier
- val allHostsValid = HostnameVerifier { hostname, session -> true }
-
- // Install the all-trusting host verifier
- HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
- } catch (e: Exception) {
- logger.error("could not disable SSL certificate checks", e)
- }
- }
+class WebappManager : BaseWebappManager("API Server","api") {
/* ServletContextListener interface */
override fun contextInitialized(sce: ServletContextEvent) {
- context = sce.servletContext
- logger.info("---------- Starting $WEBAPP_NAME ----------")
- webappRoot = context.getRealPath("/")
- try {
- // load default properties
- properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
- // override with system properties after stripping off the 'pairgoth.' prefix
- System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
- }.forEach { (key, value) ->
- properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
- }
+ super.contextInitialized(sce)
- logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
+ logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
- // set system user agent string to empty string
- System.setProperty("http.agent", "")
-
- // disable (for now ?) the SSL certificate checks, because many sites
- // fail to correctly implement SSL...
- disableSSLCertificateChecks()
-
- // start smtp loop
- if (properties.containsKey("smtp.host")) {
- registerService("smtp", SmtpLoop(properties))
- startService("smtp")
- }
-
- } catch (ioe: IOException) {
- logger.error("webapp initialization error", ioe)
+ // start smtp loop
+ if (properties.containsKey("smtp.host")) {
+ logger.info("Launching SMTP loop")
+ registerService("smtp", SmtpLoop(properties))
+ startService("smtp")
}
}
- override fun contextDestroyed(sce: ServletContextEvent) {
- logger.info("---------- Stopping $WEBAPP_NAME ----------")
-
- stopService("smtp");
-
- val context = sce.servletContext
- for (service in webServices.keys) stopService(service, true)
- // ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
-
- logger.info("---------- Stopped $WEBAPP_NAME ----------")
- }
-
- /* ServletContextAttributeListener interface */
- override fun attributeAdded(event: ServletContextAttributeEvent) {}
- override fun attributeRemoved(event: ServletContextAttributeEvent) {}
- override fun attributeReplaced(event: ServletContextAttributeEvent) {}
-
- /* HttpSessionListener interface */
- override fun sessionCreated(se: HttpSessionEvent) {}
- override fun sessionDestroyed(se: HttpSessionEvent) {}
-
companion object {
- const val WEBAPP_NAME = "Pairgoth API Server"
- const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
- lateinit var webappRoot: String
- lateinit var context: ServletContext
- private val webServices: MutableMap> = TreeMap()
- var logger = LoggerFactory.getLogger(WebappManager::class.java)
- val properties = Properties()
- fun getProperty(prop: String): String? {
- return properties.getProperty(prop)
- }
- fun getMandatoryProperty(prop: String): String {
- return getProperty(prop) ?: throw Error("missing property: ${prop}")
- }
-
- val webappURL by lazy { getProperty("webapp.external.url") }
-
- private val services = mutableMapOf>()
-
- @JvmOverloads
- fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
- if (webServices.containsKey(name)) {
- logger.warn("service {} already registered")
- return
- }
- logger.debug("registered service {}", name)
- webServices[name] =
- Pair.of(task, null)
- }
-
- fun startService(name: String?) {
- val service = webServices[name]!!
- if (service.right != null && service.right!!.isAlive) {
- logger.warn("service {} is already running", name)
- return
- }
- logger.debug("starting service {}", name)
- val thread = Thread(service.left, name)
- thread.start()
- webServices[name] =
- Pair.of(
- service.left,
- thread
- )
- }
-
- @JvmOverloads
- fun stopService(name: String?, webappClosing: Boolean = false) {
- val service = webServices[name]!!
- val thread = service.right
- if (thread == null || !thread.isAlive) {
- logger.warn("service {} is already stopped", name)
- return
- }
- logger.debug("stopping service {}", name)
- thread.interrupt()
- try {
- thread.join()
- } catch (ie: InterruptedException) {
- }
- if (!webappClosing) {
- webServices[name] = Pair.of(service.left, null)
- }
- }
+ val properties get() = BaseWebappManager.properties
+ fun getMandatoryProperty(prop: String) = properties.getProperty(prop) ?: throw Error("missing property: $prop")
+ val context get() = BaseWebappManager.context
}
}
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt
index fd95cca..34f8fc5 100644
--- a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt
+++ b/api-webapp/src/main/kotlin/org/jeudego/pairgoth/store/Store.kt
@@ -3,10 +3,10 @@ package org.jeudego.pairgoth.store
import org.jeudego.pairgoth.server.WebappManager
private fun createStoreImplementation(): IStore {
- return when (val storeProperty = WebappManager.getProperty("store") ?: "memory") {
+ return when (val storeProperty = WebappManager.properties.getProperty("store") ?: "memory") {
"memory" -> MemoryStore()
"file" -> {
- val filePath = WebappManager.getProperty("store.file.path") ?: "."
+ val filePath = WebappManager.properties.getProperty("store.file.path") ?: "."
FileStore(filePath)
}
else -> throw Error("unknown store: $storeProperty")
diff --git a/pairgoth-common/pom.xml b/pairgoth-common/pom.xml
new file mode 100644
index 0000000..4b545b7
--- /dev/null
+++ b/pairgoth-common/pom.xml
@@ -0,0 +1,150 @@
+
+
+ 4.0.0
+
+
+ org.jeudego.pairgoth
+ engine-parent
+ 0.3
+
+ pairgoth-common
+
+ jar
+ ${project.groupId}:${project.artifactId}
+ Pairgoth common utilities
+ TODO
+
+ package
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+
+
+
+
+ org.junit
+ junit-bom
+ 5.9.3
+ pom
+ import
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ ${kotlin.version}
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlinx
+ kotlinx-datetime-jvm
+ 0.4.0
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${servlet.api.version}
+ provided
+
+
+ com.sun.mail
+ jakarta.mail
+ 1.6.7
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ commons-io
+ commons-io
+ 2.13.0
+
+
+
+ io.github.microutils
+ kotlin-logging-jvm
+ 3.0.5
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+ com.diogonunes
+ JColor
+ 5.0.1
+
+
+
+ com.republicate
+ simple-mailer
+ 1.6
+
+
+
+ com.republicate.kson
+ essential-kson-jvm
+ 2.4
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.mockito.kotlin
+ mockito-kotlin
+ 4.1.0
+ test
+
+
+
diff --git a/pairgoth-common/pom.xml.versionsBackup b/pairgoth-common/pom.xml.versionsBackup
new file mode 100644
index 0000000..3c2381a
--- /dev/null
+++ b/pairgoth-common/pom.xml.versionsBackup
@@ -0,0 +1,307 @@
+
+
+ 4.0.0
+
+
+ org.jeudego.pairgoth
+ engine-parent
+ 1.0-SNAPSHOT
+
+ api-webapp
+
+ war
+ ${project.groupId}:${project.artifactId}
+ PairGoth pairing system
+ TODO
+
+ 5.7.1
+
+
+ package
+ src/main/kotlin
+ src/test/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ com.republicate:webapp-slf4j-logger
+
+
+ ${project.build.testOutputDirectory}
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+ ${jetty.version}
+
+ 0
+
+ ${pairgoth.api.host}
+ ${pairgoth.api.port}
+
+
+ ${pairgoth.env}
+ ${pairgoth.version}
+ ${pairgoth.api.external.url}
+ ${pairgoth.webapp.external.url}
+ ${pairgoth.store}
+ ${pairgoth.store.file.path}
+ ${pairgoth.logger.level}
+ ${pairgoth.logger.format}
+
+
+ ${pairgoth.api.context}/
+
+
+
+
+ org.codehaus.mojo
+ jaxb2-maven-plugin
+ 3.1.0
+
+
+ gen-schema
+
+ xjc
+
+
+
+
+ org.jeudego.pairgoth.opengotha
+ XmlSchema
+
+ src/main/resources/xsd
+
+
+
+
+
+
+
+
+ org.junit
+ junit-bom
+ 5.9.3
+ pom
+ import
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-test-junit5
+ ${kotlin.version}
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-reflect
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlinx
+ kotlinx-datetime-jvm
+ 0.4.0
+
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${servlet.api.version}
+ provided
+
+
+ com.sun.mail
+ jakarta.mail
+ 1.6.7
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ commons-io
+ commons-io
+ 2.13.0
+
+
+
+ org.pac4j
+ pac4j-oauth
+ ${pac4j.version}
+
+
+
+ io.github.microutils
+ kotlin-logging-jvm
+ 3.0.5
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ com.republicate
+ webapp-slf4j-logger
+ 3.0
+ runtime
+
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+ test
+
+
+ com.diogonunes
+ JColor
+ 5.0.1
+
+
+
+ com.republicate
+ simple-mailer
+ 1.6
+
+
+
+ com.republicate.kson
+ essential-kson-jvm
+ 2.4
+
+
+
+
+
+ org.apache.pdfbox
+ pdfbox
+ 2.0.28
+
+
+
+ com.republicate
+ jeasse-servlet3
+ 1.2
+
+
+
+ org.jgrapht
+ jgrapht-core
+ 1.5.2
+
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ 4.0.0
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+ 4.0.3
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.mockito.kotlin
+ mockito-kotlin
+ 4.1.0
+ test
+
+
+
+ com.icegreen
+ greenmail
+ 1.6.12
+ test
+
+
+ junit
+ junit
+
+
+ javax.activation
+ activation
+
+
+ com.sun.mail
+ javax.mail
+
+
+
+
+
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt
similarity index 100%
rename from view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt
rename to pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
similarity index 100%
rename from api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
rename to pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt
similarity index 100%
rename from view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt
rename to pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
similarity index 100%
rename from api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
rename to pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
diff --git a/api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt
similarity index 100%
rename from api-webapp/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt
rename to pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/util/XmlUtils.kt
diff --git a/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/BaseWebappManager.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/BaseWebappManager.kt
new file mode 100644
index 0000000..5e2c143
--- /dev/null
+++ b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/BaseWebappManager.kt
@@ -0,0 +1,147 @@
+package org.jeudego.pairgoth.web
+
+import com.republicate.mailer.SmtpLoop
+import org.apache.commons.lang3.tuple.Pair
+import org.slf4j.LoggerFactory
+import java.io.IOException
+import java.lang.IllegalAccessError
+import java.security.SecureRandom
+import java.security.cert.X509Certificate
+import java.util.*
+import java.util.IllegalFormatCodePointException
+import javax.net.ssl.*
+import javax.servlet.*
+import javax.servlet.annotation.WebListener
+import javax.servlet.http.HttpSessionEvent
+import javax.servlet.http.HttpSessionListener
+
+abstract class BaseWebappManager(val webappName: String, loggerName: String) : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
+
+ val logger = LoggerFactory.getLogger(loggerName)
+
+ protected fun disableSSLCertificateChecks() {
+ // see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
+ try {
+ // Create a trust manager that does not validate certificate chains
+ val trustAllCerts = arrayOf(object : X509TrustManager {
+ override fun getAcceptedIssuers(): Array? {
+ return null
+ }
+
+ @Suppress("TrustAllX509TrustManager")
+ override fun checkClientTrusted(certs: Array, authType: String) {}
+ @Suppress("TrustAllX509TrustManager")
+ override fun checkServerTrusted(certs: Array, authType: String) {}
+ }
+ )
+
+ // Install the all-trusting trust manager
+ val sc = SSLContext.getInstance("SSL")
+ sc.init(null, trustAllCerts, SecureRandom())
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
+
+ // Create all-trusting host name verifier
+ val allHostsValid = HostnameVerifier { hostname, session -> true }
+
+ // Install the all-trusting host verifier
+ HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
+ } catch (e: Exception) {
+ logger.error("could not disable SSL certificate checks", e)
+ }
+ }
+
+ private val webServices: MutableMap> = TreeMap()
+
+ @JvmOverloads
+ fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
+ if (webServices.containsKey(name)) {
+ logger.warn("service {} already registered")
+ return
+ }
+ logger.debug("registered service {}", name)
+ webServices[name] =
+ Pair.of(task, null)
+ }
+
+ fun startService(name: String?) {
+ val service = webServices[name]!!
+ if (service.right != null && service.right!!.isAlive) {
+ logger.warn("service {} is already running", name)
+ return
+ }
+ logger.debug("starting service {}", name)
+ val thread = Thread(service.left, name)
+ thread.start()
+ webServices[name] =
+ Pair.of(
+ service.left,
+ thread
+ )
+ }
+
+ @JvmOverloads
+ fun stopService(name: String?, webappClosing: Boolean = false) {
+ val service = webServices[name]!!
+ val thread = service.right
+ if (thread == null || !thread.isAlive) {
+ logger.warn("service {} is already stopped", name)
+ return
+ }
+ logger.debug("stopping service {}", name)
+ thread.interrupt()
+ try {
+ thread.join()
+ } catch (ie: InterruptedException) {
+ }
+ if (!webappClosing) {
+ webServices[name] = Pair.of(service.left, null)
+ }
+ }
+
+ /* ServletContextListener interface */
+ override fun contextInitialized(sce: ServletContextEvent) {
+ context = sce.servletContext
+ logger.info("---------- Starting $webappName ----------")
+ webappRoot = context.getRealPath("/")
+ try {
+ // load default properties
+ properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
+ // override with system properties after stripping off the 'pairgoth.' prefix
+ System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
+ }.forEach { (key, value) ->
+ properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
+ }
+
+ // set system user agent string to empty string
+ System.setProperty("http.agent", "")
+
+ // disable (for now ?) the SSL certificate checks, because many sites
+ // fail to correctly implement SSL...
+ disableSSLCertificateChecks()
+
+ } catch (ioe: IOException) {
+ logger.error("webapp initialization error", ioe)
+ }
+ }
+
+ override fun contextDestroyed(sce: ServletContextEvent) {
+ logger.info("---------- Stopping $webappName ----------")
+ for (service in webServices.keys) stopService(service, true)
+ }
+
+ /* ServletContextAttributeListener interface */
+ override fun attributeAdded(event: ServletContextAttributeEvent) {}
+ override fun attributeRemoved(event: ServletContextAttributeEvent) {}
+ override fun attributeReplaced(event: ServletContextAttributeEvent) {}
+
+ /* HttpSessionListener interface */
+ override fun sessionCreated(se: HttpSessionEvent) {}
+ override fun sessionDestroyed(se: HttpSessionEvent) {}
+
+ companion object {
+ const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
+ lateinit var webappRoot: String
+ lateinit var context: ServletContext
+ val properties = Properties()
+ }
+}
diff --git a/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/Logging.kt b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/Logging.kt
new file mode 100644
index 0000000..ae4f8e3
--- /dev/null
+++ b/pairgoth-common/src/main/kotlin/org/jeudego/pairgoth/web/Logging.kt
@@ -0,0 +1,73 @@
+package org.jeudego.pairgoth.web
+
+import com.republicate.kson.Json
+import org.jeudego.pairgoth.util.Colorizer.blue
+import org.jeudego.pairgoth.util.Colorizer.green
+import org.jeudego.pairgoth.util.toString
+import org.slf4j.Logger
+import java.io.StringWriter
+import javax.servlet.http.HttpServletRequest
+
+fun Logger.logRequest(req: HttpServletRequest, logHeaders: Boolean = false) {
+ val builder = StringBuilder()
+ builder.append(req.method).append(' ')
+ .append(req.scheme).append("://")
+ .append(req.localName)
+ val port = req.localPort
+ if (port != 80) builder.append(':').append(port)
+ /*
+ if (!req.contextPath.isEmpty()) {
+ builder.append(req.contextPath)
+ }
+ */
+ builder.append(req.requestURI)
+ if (req.method == "GET") {
+ val qs = req.queryString
+ if (qs != null) builder.append('?').append(qs)
+ }
+ // builder.append(' ').append(req.getProtocol());
+ info(blue("<< {}"), builder.toString())
+ if (isTraceEnabled && logHeaders) {
+ // CB TODO - should be bufferized and asynchronously written in synchronous chunks
+ // so that header lines from parallel requests are not mixed up in the logs ;
+ // synchronizing the whole request log is not desirable
+ val headerNames = req.headerNames
+ while (headerNames.hasMoreElements()) {
+ val name = headerNames.nextElement()
+ val value = req.getHeader(name)
+ trace(blue("<< {}: {}"), name, value)
+ }
+ }
+}
+
+fun Logger.logPayload(prefix: String?, payload: Json, upstream: Boolean) {
+ val writer = StringWriter()
+ //payload.toPrettyString(writer, "");
+ payload.toString(writer)
+ if (isTraceEnabled) {
+ for (line in writer.toString().split("\n".toRegex()).dropLastWhile { it.isEmpty() }
+ .toTypedArray()) {
+ trace(if (upstream) blue("{}{}") else green("{}{}"), prefix, line)
+ }
+ } else {
+ var line = writer.toString()
+ val pos = line.indexOf('\n')
+ if (pos != -1) line = line.substring(0, pos)
+ if (line.length > 50) line = line.substring(0, 50) + "..."
+ debug(if (upstream) blue("{}{}") else green("{}{}"), prefix, line)
+ }
+}
+
+
+fun HttpServletRequest.getRemoteAddress(): String? {
+ var ip = getHeader("X-Forwarded-For")
+ if (ip == null) {
+ ip = remoteAddr
+ } else {
+ val comma = ip.indexOf(',')
+ if (comma != -1) {
+ ip = ip.substring(0, comma).trim { it <= ' ' } // keep the left-most IP address
+ }
+ }
+ return ip
+}
diff --git a/pom.xml b/pom.xml
index 307653f..15348a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -93,6 +93,7 @@
+ pairgoth-common
api-webapp
view-webapp
webserver
diff --git a/view-webapp/pom.xml b/view-webapp/pom.xml
index 666107d..a96c5a8 100644
--- a/view-webapp/pom.xml
+++ b/view-webapp/pom.xml
@@ -137,6 +137,11 @@
+
+ org.jeudego.pairgoth
+ pairgoth-common
+ ${project.version}
+
org.jetbrains.kotlin
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt
index 15eeb51..262b06d 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsHandler.kt
@@ -22,7 +22,7 @@ abstract class RatingsHandler(val origin: RatingsManager.Ratings) {
private var updated = false
val url: URL by lazy {
- WebappManager.getProperty("ratings.${origin.name.lowercase(Locale.ROOT)}")?.let { URL(it) } ?: defaultURL
+ WebappManager.properties.getProperty("ratings.${origin.name.lowercase(Locale.ROOT)}")?.let { URL(it) } ?: defaultURL
}
fun updateIfNeeded(): Boolean {
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt
index 3e20eab..e79d268 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/ratings/RatingsManager.kt
@@ -91,7 +91,7 @@ object RatingsManager: Runnable {
}
}
val logger = LoggerFactory.getLogger("ratings")
- val path = Path.of(WebappManager.getProperty("ratings.path") ?: "ratings").also {
+ val path = Path.of(WebappManager.properties.getProperty("ratings.path") ?: "ratings").also {
val file = it.toFile()
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.ratings.path must be a directory")
}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
deleted file mode 100644
index f3d2a09..0000000
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.jeudego.pairgoth.util
-
-import com.diogonunes.jcolor.Ansi
-import com.diogonunes.jcolor.AnsiFormat
-import com.diogonunes.jcolor.Attribute
-
-private val blue = AnsiFormat(Attribute.BRIGHT_BLUE_TEXT())
-private val green = AnsiFormat(Attribute.BRIGHT_GREEN_TEXT())
-private val red = AnsiFormat(Attribute.BRIGHT_RED_TEXT())
-private val bold = AnsiFormat(Attribute.BOLD())
-
-object Colorizer {
-
- fun blue(str: String) = Ansi.colorize(str, blue)
- fun green(str: String) = Ansi.colorize(str, green)
- fun red(str: String) = Ansi.colorize(str, red)
- fun bold(str: String) = Ansi.colorize(str, bold)
-}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
deleted file mode 100644
index 99177ea..0000000
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.jeudego.pairgoth.util
-
-import com.republicate.kson.Json
-import java.io.Reader
-import java.io.Writer
-
-
-fun Json.Companion.parse(reader: Reader) = Json.Companion.parse(object: Json.Input {
- override fun read() = reader.read().toChar()
-})
-
-fun Json.toString(writer: Writer) = toString(object: Json.Output {
- override fun writeChar(c: Char): Json.Output {
- writer.write(c.code)
- return this
- }
- override fun writeString(s: String): Json.Output {
- writer.write(s)
- return this
- }
- override fun writeString(s: String, from: Int, to: Int): Json.Output {
- writer.write(s, from, to)
- return this
- }
-})
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt
index 913e483..5a66066 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/view/ApiTool.kt
@@ -14,7 +14,7 @@ class ApiTool {
const val JSON = "application/json"
const val XML = "application/xml"
val apiRoot by lazy {
- WebappManager.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}/" }
+ WebappManager.properties.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}/" }
?: throw Error("no configured API url")
}
val logger = LoggerFactory.getLogger("api")
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
index b4159b6..eeb5c61 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
@@ -20,7 +20,7 @@ class ApiServlet : AsyncProxyServlet() {
companion object {
private val apiRoot by lazy {
- WebappManager.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}" }
+ WebappManager.properties.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}" }
?: throw Error("no configured API url")
}
}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/AuthFilter.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/AuthFilter.kt
index 18078ca..f08922b 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/AuthFilter.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/AuthFilter.kt
@@ -28,7 +28,7 @@ class AuthFilter: Filter {
val response = resp as HttpServletResponse
val uri = request.requestURI
val session: HttpSession? = request.getSession(false)
- val auth = WebappManager.getProperty("auth") ?: throw Error("authentication not configured")
+ val auth = WebappManager.properties.getProperty("auth") ?: throw Error("authentication not configured")
val forwarded = request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null
if (auth == "oauth" && uri.startsWith("/oauth/")) {
@@ -66,6 +66,5 @@ class AuthFilter: Filter {
val nolangUri = uri.replace(Regex("^/../"), "/")
return whitelist.contains(nolangUri)
}
-
}
}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LoginServlet.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LoginServlet.kt
index b57a4c1..f301b90 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LoginServlet.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/LoginServlet.kt
@@ -16,7 +16,7 @@ class LoginServlet: HttpServlet() {
val mimeType = if (sep == -1) contentType else contentType.substring(0, sep).trim { it <= ' ' }
if (!isJson(mimeType)) throw Error("expecting json")
val payload = Json.Companion.parse(req.reader.readText())?.asObject() ?: throw Error("null json")
- val user = when (WebappManager.getProperty("auth")) {
+ val user = when (WebappManager.properties.getProperty("auth")) {
"sesame" -> checkSesame(payload)
else -> checkLoginPass(payload)
} ?: throw Error("authentication failed")
@@ -33,7 +33,7 @@ class LoginServlet: HttpServlet() {
}
fun checkSesame(payload: Json.Object): Json.Object? {
- val expected = WebappManager.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
+ val expected = WebappManager.properties.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
return if (payload.getString("sesame")?.equals(expected) == true) Json.Object("logged" to true) else null
}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
index f466c68..d8cdbee 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
@@ -16,175 +16,43 @@ import javax.servlet.http.HttpSessionEvent
import javax.servlet.http.HttpSessionListener
@WebListener
-class WebappManager : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
- private fun disableSSLCertificateChecks() {
- // see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
- try {
- // Create a trust manager that does not validate certificate chains
- val trustAllCerts = arrayOf(object : X509TrustManager {
- override fun getAcceptedIssuers(): Array? {
- return null
- }
-
- @Suppress("TrustAllX509TrustManager")
- override fun checkClientTrusted(certs: Array, authType: String) {}
- @Suppress("TrustAllX509TrustManager")
- override fun checkServerTrusted(certs: Array, authType: String) {}
- }
- )
-
- // Install the all-trusting trust manager
- val sc = SSLContext.getInstance("SSL")
- sc.init(null, trustAllCerts, SecureRandom())
- HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
-
- // Create all-trusting host name verifier
- val allHostsValid = HostnameVerifier { hostname, session -> true }
-
- // Install the all-trusting host verifier
- HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
- } catch (e: Exception) {
- logger.error("could not disable SSL certificate checks", e)
- }
- }
+class WebappManager : BaseWebappManager("View Webapp", "view") {
/* ServletContextListener interface */
override fun contextInitialized(sce: ServletContextEvent) {
- context = sce.servletContext
- logger.info("---------- Starting $WEBAPP_NAME ----------")
- logger.info("info level is active")
- logger.debug("debug level is active")
- logger.trace("trace level is active")
- webappRoot = context.getRealPath("/")
- try {
- // load default properties
- properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
- // override with system properties after stripping off the 'pairgoth.' prefix
- System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
- }.forEach { (key, value) ->
- properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
- }
+ super.contextInitialized(sce)
- logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
-
- // publish some properties to the webapp context; for easy access from the template
- context.setAttribute("env", properties.getProperty("env") ?: "dev")
- context.setAttribute("version", properties.getProperty("version") ?: "?")
- val auth = properties.getProperty("auth") ?: "none"
- context.setAttribute("auth", auth)
- when (auth) {
- "none", "sesame" -> {}
- "oauth" -> {
- properties.getProperty("oauth.providers")?.let {
- val providers = it.split(Regex("\\s*,\\s*"))
- context.setAttribute("oauthProviders", providers)
- providers.forEach { provider ->
- context.setAttribute("${provider}Provider", OauthHelperFactory.getHelper(provider))
- }
+ // publish some properties to the webapp context; for easy access from the template
+ context.setAttribute("env", properties.getProperty("env") ?: "dev")
+ context.setAttribute("version", properties.getProperty("version") ?: "?")
+ val auth = properties.getProperty("auth") ?: "none"
+ context.setAttribute("auth", auth)
+ when (auth) {
+ "none", "sesame" -> {}
+ "oauth" -> {
+ properties.getProperty("oauth.providers")?.let {
+ val providers = it.split(Regex("\\s*,\\s*"))
+ context.setAttribute("oauthProviders", providers)
+ providers.forEach { provider ->
+ context.setAttribute("${provider}Provider", OauthHelperFactory.getHelper(provider))
}
}
- else -> throw Error("Unhandled auth: $auth")
}
-
- // set system user agent string to empty string
- System.setProperty("http.agent", "")
-
- // disable (for now ?) the SSL certificate checks, because many sites
- // fail to correctly implement SSL...
- disableSSLCertificateChecks()
-
- registerService("ratings", RatingsManager)
- startService("ratings")
-
- } catch (ioe: IOException) {
- logger.error("webapp initialization error", ioe)
+ else -> throw Error("Unhandled auth: $auth")
}
+
+ registerService("ratings", RatingsManager)
+ startService("ratings")
}
override fun contextDestroyed(sce: ServletContextEvent) {
- logger.info("---------- Stopping $WEBAPP_NAME ----------")
-
+ super.contextDestroyed(sce)
Translator.notifyExiting()
-
- val context = sce.servletContext
- for (service in webServices.keys) stopService(service, true)
- // ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
-
- logger.info("---------- Stopped $WEBAPP_NAME ----------")
}
- /* ServletContextAttributeListener interface */
- override fun attributeAdded(event: ServletContextAttributeEvent) {}
- override fun attributeRemoved(event: ServletContextAttributeEvent) {}
- override fun attributeReplaced(event: ServletContextAttributeEvent) {}
-
- /* HttpSessionListener interface */
- override fun sessionCreated(se: HttpSessionEvent) {}
- override fun sessionDestroyed(se: HttpSessionEvent) {}
-
companion object {
- const val WEBAPP_NAME = "Pairgoth Web Client"
- const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
- lateinit var webappRoot: String
- lateinit var context: ServletContext
- private val webServices: MutableMap> = TreeMap()
- var logger = LoggerFactory.getLogger(WebappManager::class.java)
- val properties = Properties()
- fun getProperty(prop: String): String? {
- return properties.getProperty(prop)
- }
- fun getMandatoryProperty(prop: String): String {
- return getProperty(prop) ?: throw Error("missing property: ${prop}")
- }
-
- val webappURL by lazy { getProperty("webapp.external.url") }
-
- private val services = mutableMapOf>()
-
- @JvmOverloads
- fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
- if (webServices.containsKey(name)) {
- logger.warn("service {} already registered")
- return
- }
- logger.debug("registered service {}", name)
- webServices[name] =
- Pair.of(task, null)
- }
-
- fun startService(name: String?) {
- val service = webServices[name]!!
- if (service.right != null && service.right!!.isAlive) {
- logger.warn("service {} is already running", name)
- return
- }
- logger.debug("starting service {}", name)
- val thread = Thread(service.left, name)
- thread.start()
- webServices[name] =
- Pair.of(
- service.left,
- thread
- )
- }
-
- @JvmOverloads
- fun stopService(name: String?, webappClosing: Boolean = false) {
- val service = webServices[name]!!
- val thread = service.right
- if (thread == null || !thread.isAlive) {
- logger.warn("service {} is already stopped", name)
- return
- }
- logger.debug("stopping service {}", name)
- thread.interrupt()
- try {
- thread.join()
- } catch (ie: InterruptedException) {
- }
- if (!webappClosing) {
- webServices[name] = Pair.of(service.left, null)
- }
- }
+ val properties get() = BaseWebappManager.properties
+ val context get() = BaseWebappManager.context
+ fun getMandatoryProperty(prop: String) = properties.getProperty(prop) ?: throw Error("missing property: $prop")
}
}