Engine initial commit
This commit is contained in:
3
docker/clear.sh
Executable file
3
docker/clear.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker-compose rm -fsv
|
37
docker/docker-compose.yml
Normal file
37
docker/docker-compose.yml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
pairgoth:
|
||||||
|
container_name: pairgoth-engine
|
||||||
|
image: maven:3.6.3-openjdk-11-slim
|
||||||
|
working_dir: /home/app/pairgoth
|
||||||
|
user: "${APP_UID}:${APP_GID}"
|
||||||
|
entrypoint: bash -c
|
||||||
|
command: '"mvn -e -Duser.home=/home/app clean package jetty:run"'
|
||||||
|
# command: mvn -X -e -Pdev clean verify -Dit.test=ApiTokenIT#test01NoVersionHeader -Dorg.slf4j.simpleLogger.log.level=trace jetty:run
|
||||||
|
volumes:
|
||||||
|
- ${HOME}/.m2:/home/app/.m2
|
||||||
|
- ./pairgoth.properties:/var/lib/pairgoth/pairgoth.properties
|
||||||
|
- ..:/home/app/pairgoth
|
||||||
|
- ./data/jetty:/var/lib/pairgoth/jetty
|
||||||
|
networks:
|
||||||
|
- pairgoth-network
|
||||||
|
ports:
|
||||||
|
- '5006:5006'
|
||||||
|
- '8080:8080'
|
||||||
|
environment:
|
||||||
|
HOME: "/home/app"
|
||||||
|
USER: "app"
|
||||||
|
MAVEN_CONFIG: "/home/app/.m2"
|
||||||
|
MAVEN_OPTS: "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5006"
|
||||||
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
|
SMTP_USER: ${SMTP_USER}
|
||||||
|
SMTP_PASSWORD: ${SMTP_PASSWORD}
|
||||||
|
# restart: unless-stopped
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
networks:
|
||||||
|
pairgoth-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
|
8
docker/run.sh
Executable file
8
docker/run.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
grep -r '^smtp\.host' webapp.properties | sed -r -e 's/smtp\.host/SMTP_HOST/' -e 's/ //g' > .env
|
||||||
|
grep -r '^smtp\.port' webapp.properties | sed -r -e 's/smtp\.port/SMTP_PORT/' -e 's/ //g' >> .env
|
||||||
|
grep -r '^smtp\.user' webapp.properties | sed -r -e 's/smtp\.user/SMTP_USER/' -e 's/ //g' >> .env
|
||||||
|
grep -r '^smtp\.password' webapp.properties | sed -r -e 's/smtp\.password/SMTP_PASSWORD/' -e 's/ //g' >> .env
|
||||||
|
|
||||||
|
APP_UID=$(id -u) APP_GID=$(id -g) docker-compose up
|
480
pom.xml
Normal file
480
pom.xml
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.jeudego</groupId>
|
||||||
|
<artifactId>pairgoth</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<packaging>war</packaging>
|
||||||
|
<name>${project.groupId}:${project.artifactId}</name>
|
||||||
|
<description>PairGoth pairing system</description>
|
||||||
|
<url>TODO</url>
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<junit.version>4.13.2</junit.version>
|
||||||
|
<slf4j.version>1.7.36</slf4j.version>
|
||||||
|
<servlet.version>3.1.0</servlet.version>
|
||||||
|
<kotlin.version>1.8.21</kotlin.version>
|
||||||
|
<kotlin.code.style>official</kotlin.code.style>
|
||||||
|
<kotlin.compiler.jvmTarget>10</kotlin.compiler.jvmTarget>
|
||||||
|
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
|
||||||
|
<pac4j.version>5.7.1</pac4j.version>
|
||||||
|
<jetty.version>10.0.12</jetty.version>
|
||||||
|
<jetty.files>${project.build.directory}/${project.build.finalName}/WEB-INF/jetty</jetty.files>
|
||||||
|
<jetty.port>8080</jetty.port>
|
||||||
|
</properties>
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>pairgoth</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
</activation>
|
||||||
|
<properties>
|
||||||
|
<webapp.properties>/var/lib/pairgoth/pairgoth.properties</webapp.properties>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
<build>
|
||||||
|
<defaultGoal>package</defaultGoal>
|
||||||
|
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||||
|
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-enforcer-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>enforce-maven</id>
|
||||||
|
<goals>
|
||||||
|
<goal>enforce</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<rules>
|
||||||
|
<requireMavenVersion>
|
||||||
|
<version>3.6.3</version>
|
||||||
|
</requireMavenVersion>
|
||||||
|
</rules>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0-M7</version>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>true</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
|
<version>3.0.0-M7</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>run-tests</id>
|
||||||
|
<phase>integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>integration-test</goal>
|
||||||
|
<goal>verify</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>**/Test*</include>
|
||||||
|
</includes>
|
||||||
|
<redirectTestOutputToFile>false</redirectTestOutputToFile>
|
||||||
|
<environmentVariables>
|
||||||
|
<EASYEDI_HOME>${project.build.testOutputDirectory}</EASYEDI_HOME>
|
||||||
|
<ENVIRONMENT>dev</ENVIRONMENT>
|
||||||
|
</environmentVariables>
|
||||||
|
<systemProperties>
|
||||||
|
<property>
|
||||||
|
<name>project.version</name>
|
||||||
|
<value>${project.version}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>project.dir</name>
|
||||||
|
<value>${project.basedir}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>build.dir</name>
|
||||||
|
<value>${project.build.directory}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.resources.dir</name>
|
||||||
|
<value>${project.build.testOutputDirectory}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.run.dir</name>
|
||||||
|
<value>${project.build.directory}/run</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.result.dir</name>
|
||||||
|
<value>${project.build.directory}/results</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>org.slf4j.simpleLogger.defaultLogLevel</name>
|
||||||
|
<value>debug</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>org.slf4j.simpleLogger.log.com.icegreen.greenmail.util.LineLoggingBuffer</name>
|
||||||
|
<value>info</value>
|
||||||
|
</property>
|
||||||
|
<!--
|
||||||
|
<property>
|
||||||
|
<name>org.slf4j.simpleLogger.logFile</name>
|
||||||
|
<value>${project.build.directory}/tests.log</value>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
<property>
|
||||||
|
<name>test.jdbc.driver.className</name>
|
||||||
|
<value>${test.jdbc.driver.className}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.jdbc.uri</name>
|
||||||
|
<value>${test.jdbc.uri}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.jdbc.login</name>
|
||||||
|
<value>${test.jdbc.login}</value>
|
||||||
|
</property>
|
||||||
|
<property>
|
||||||
|
<name>test.jdbc.password</name>
|
||||||
|
<value>${test.jdbc.password}</value>
|
||||||
|
</property>
|
||||||
|
-->
|
||||||
|
</systemProperties>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>properties-maven-plugin</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>initialize</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>read-project-properties</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<files>
|
||||||
|
<file>${webapp.properties}</file>
|
||||||
|
</files>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifest>
|
||||||
|
<addClasspath>true</addClasspath>
|
||||||
|
<classpathPrefix>./</classpathPrefix>
|
||||||
|
<mainClass>com.performance.easyedi.MainKt</mainClass>
|
||||||
|
</manifest>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<!-- keep version 2.7 to avoid 3.x behavior with symlinks -->
|
||||||
|
<version>2.7</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.10.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>10</source>
|
||||||
|
<target>10</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<version>3.2.2</version>
|
||||||
|
<configuration>
|
||||||
|
<archiveClasses>true</archiveClasses>
|
||||||
|
<webResources>
|
||||||
|
<resource>
|
||||||
|
<directory>${basedir}/src/main/config</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
<targetPath>WEB-INF</targetPath>
|
||||||
|
<includes>
|
||||||
|
<include>webapp.properties</include>
|
||||||
|
<include>web.xml</include>
|
||||||
|
<include>tools.xml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${basedir}/src/main/config/jetty</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
<targetPath>WEB-INF/jetty</targetPath>
|
||||||
|
</resource>
|
||||||
|
</webResources>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>default-war</id>
|
||||||
|
<phase>none</phase>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>war-exploded</id>
|
||||||
|
<!--
|
||||||
|
<phase>package</phase>
|
||||||
|
-->
|
||||||
|
<goals>
|
||||||
|
<goal>exploded</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>templating-maven-plugin</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>filter-src</id>
|
||||||
|
<goals>
|
||||||
|
<goal>filter-test-sources</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-maven-plugin</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<scan>3</scan>
|
||||||
|
<!-- <jettyXmls>${jetty.files}/jetty-http.xml,${jetty.files}/jetty-env.xml,${jetty.files}/jetty-ssl.xml,${jetty.files}/jetty-ssl-context.xml,${jetty.files}/jetty-https.xml</jettyXmls> -->
|
||||||
|
<jettyXmls>${jetty.files}/jetty-http.xml,${jetty.files}/jetty-env.xml</jettyXmls>
|
||||||
|
<stopPort>9966</stopPort>
|
||||||
|
<stopKey>STOP</stopKey>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>start-jetty</id>
|
||||||
|
<phase>pre-integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>start</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>stop-jetty</id>
|
||||||
|
<phase>post-integration-test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>stop</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.http2</groupId>
|
||||||
|
<artifactId>http2-server</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencies>
|
||||||
|
<!-- main dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit5</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>5.6.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
|
<artifactId>kotlinx-datetime-jvm</artifactId>
|
||||||
|
<version>0.3.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- servlets and mail APIs -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<version>${servlet.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>jakarta.mail</artifactId>
|
||||||
|
<version>1.6.5</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- auth -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pac4j</groupId>
|
||||||
|
<artifactId>pac4j-oauth</artifactId>
|
||||||
|
<version>${pac4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.microutils</groupId>
|
||||||
|
<artifactId>kotlin-logging-jvm</artifactId>
|
||||||
|
<version>2.1.23</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>webapp-slf4j-logger</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>jcl-over-slf4j</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.diogonunes</groupId>
|
||||||
|
<artifactId>JColor</artifactId>
|
||||||
|
<version>5.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- mailer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>simple-mailer</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate.kson</groupId>
|
||||||
|
<artifactId>essential-kson-jvm</artifactId>
|
||||||
|
<version>2.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- charset detection
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ibm.icu</groupId>
|
||||||
|
<artifactId>icu4j</artifactId>
|
||||||
|
<version>70.1</version>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
<!-- net clients -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.13</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpmime</artifactId>
|
||||||
|
<version>4.5.13</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-net</groupId>
|
||||||
|
<artifactId>commons-net</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- excel -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi</artifactId>
|
||||||
|
<version>5.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
<version>5.2.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- pdf -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.pdfbox</groupId>
|
||||||
|
<artifactId>pdfbox</artifactId>
|
||||||
|
<version>2.0.28</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- external commands -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sealwu</groupId>
|
||||||
|
<artifactId>kscript-tools</artifactId>
|
||||||
|
<version>1.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- test emails -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.icegreen</groupId>
|
||||||
|
<artifactId>greenmail</artifactId>
|
||||||
|
<version>1.6.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
5
src/main/config/jetty/jetty-env.xml
Normal file
5
src/main/config/jetty/jetty-env.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||||
|
<Configure id="wac" class="org.eclipse.jetty.webapp.WebAppContext">
|
||||||
|
</Configure>
|
||||||
|
|
26
src/main/config/jetty/jetty-http.xml
Normal file
26
src/main/config/jetty/jetty-http.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "https://www.eclipse.org/jetty/configure_10_0.dtd">
|
||||||
|
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||||
|
<Call name="addConnector">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.ServerConnector">
|
||||||
|
<Arg name="server">
|
||||||
|
<Ref refid="Server"/>
|
||||||
|
</Arg>
|
||||||
|
<Arg name="factories">
|
||||||
|
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||||
|
<Item>
|
||||||
|
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||||
|
<Arg name="config">
|
||||||
|
<New id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</New>
|
||||||
|
</Item>
|
||||||
|
</Array>
|
||||||
|
</Arg>
|
||||||
|
<Set name="port"><Property name="jetty.http.port" deprecated="jetty.port" default="${jetty.port}"/></Set>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
</Configure>
|
24
src/main/config/jetty/jetty-https.xml
Normal file
24
src/main/config/jetty/jetty-https.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||||
|
|
||||||
|
<!-- ============================================================= --><!-- Configure an HTTPS connector. --><!-- This configuration must be used in conjunction with jetty.xml --><!-- and jetty-ssl.xml. --><!-- ============================================================= -->
|
||||||
|
<Configure id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||||
|
|
||||||
|
<Call name="addIfAbsentConnectionFactory">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.SslConnectionFactory">
|
||||||
|
<Arg name="next">http/1.1</Arg>
|
||||||
|
<Arg name="sslContextFactory"><Ref refid="sslContextFactory"/></Arg>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
<Call name="addConnectionFactory">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
|
||||||
|
<Arg name="config"><Ref refid="sslHttpConfig" /></Arg>
|
||||||
|
<Arg name="compliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="valueOf"><Arg><Property name="jetty.http.compliance" default="RFC7230"/></Arg></Call></Arg>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
</Configure>
|
16
src/main/config/jetty/jetty-ssl-context.xml
Normal file
16
src/main/config/jetty/jetty-ssl-context.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||||
|
<Configure id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory$Server">
|
||||||
|
<Set name="TrustAll">true</Set>
|
||||||
|
<Set name="KeyStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.keystore" default="src/test/resources/jetty.keystore"/></Set>
|
||||||
|
<Set name="KeyStorePassword"><Property name="jetty.keystore.password" default="secret"/></Set>
|
||||||
|
<Set name="KeyManagerPassword"><Property name="jetty.keymanager.password" default="secret"/></Set>
|
||||||
|
<Set name="TrustStorePath"><Property name="jetty.base" default="." />/<Property name="jetty.truststore" default="src/test/resources/jetty.keystore"/></Set>
|
||||||
|
<Set name="TrustStorePassword"><Property name="jetty.truststore.password" default="secret"/></Set>
|
||||||
|
<Set name="EndpointIdentificationAlgorithm"></Set>
|
||||||
|
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
|
<Arg><Ref refid="httpConfig"/></Arg>
|
||||||
|
<Call name="addCustomizer">
|
||||||
|
<Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg>
|
||||||
|
</Call>
|
||||||
|
</New>
|
||||||
|
</Configure>
|
46
src/main/config/jetty/jetty-ssl.xml
Normal file
46
src/main/config/jetty/jetty-ssl.xml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0"?><!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
|
||||||
|
|
||||||
|
<Configure id="Server" class="org.eclipse.jetty.server.Server">
|
||||||
|
|
||||||
|
<Call name="addConnector">
|
||||||
|
<Arg>
|
||||||
|
<New id="sslConnector" class="org.eclipse.jetty.server.ServerConnector">
|
||||||
|
<Arg name="server"><Ref refid="Server" /></Arg>
|
||||||
|
<Arg name="acceptors" type="int"><Property name="jetty.ssl.acceptors" deprecated="ssl.acceptors" default="-1"/></Arg>
|
||||||
|
<Arg name="selectors" type="int"><Property name="jetty.ssl.selectors" deprecated="ssl.selectors" default="-1"/></Arg>
|
||||||
|
<Arg name="factories">
|
||||||
|
<Array type="org.eclipse.jetty.server.ConnectionFactory">
|
||||||
|
</Array>
|
||||||
|
</Arg>
|
||||||
|
|
||||||
|
<Set name="host"><Property name="jetty.ssl.host" deprecated="jetty.host" /></Set>
|
||||||
|
<Set name="port"><Property name="jetty.ssl.port" deprecated="ssl.port" default="${jetty.ssl.port}" /></Set>
|
||||||
|
<Set name="idleTimeout"><Property name="jetty.ssl.idleTimeout" deprecated="ssl.timeout" default="30000"/></Set>
|
||||||
|
<Set name="acceptorPriorityDelta"><Property name="jetty.ssl.acceptorPriorityDelta" deprecated="ssl.acceptorPriorityDelta" default="0"/></Set>
|
||||||
|
<Set name="acceptQueueSize"><Property name="jetty.ssl.acceptQueueSize" deprecated="ssl.acceptQueueSize" default="0"/></Set>
|
||||||
|
<Set name="reuseAddress"><Property name="jetty.ssl.reuseAddress" default="true"/></Set>
|
||||||
|
<Set name="acceptedTcpNoDelay"><Property name="jetty.ssl.acceptedTcpNoDelay" default="true"/></Set>
|
||||||
|
<Set name="acceptedReceiveBufferSize"><Property name="jetty.ssl.acceptedReceiveBufferSize" default="-1"/></Set>
|
||||||
|
<Set name="acceptedSendBufferSize"><Property name="jetty.ssl.acceptedSendBufferSize" default="-1"/></Set>
|
||||||
|
<Get name="SelectorManager">
|
||||||
|
<Set name="connectTimeout"><Property name="jetty.ssl.connectTimeout" default="15000"/></Set>
|
||||||
|
</Get>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
<New id="sslHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
|
||||||
|
<Arg><Ref refid="httpConfig"/></Arg>
|
||||||
|
<Call name="addCustomizer">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.SecureRequestCustomizer">
|
||||||
|
<Arg name="sniRequired" type="boolean"><Property name="jetty.ssl.sniRequired" default="false"/></Arg>
|
||||||
|
<Arg name="sniHostCheck" type="boolean"><Property name="jetty.ssl.sniHostCheck" default="true"/></Arg>
|
||||||
|
<Arg name="stsMaxAgeSeconds" type="int"><Property name="jetty.ssl.stsMaxAgeSeconds" default="-1"/></Arg>
|
||||||
|
<Arg name="stsIncludeSubdomains" type="boolean"><Property name="jetty.ssl.stsIncludeSubdomains" default="false"/></Arg>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
</New>
|
||||||
|
|
||||||
|
</Configure>
|
55
src/main/config/web.xml
Normal file
55
src/main/config/web.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
|
||||||
|
version="3.1">
|
||||||
|
<!-- Listeners -->
|
||||||
|
<!-- we're not using @WebListener annotations so that our manager is initialized *after* the webapp logger -->
|
||||||
|
<listener>
|
||||||
|
<listener-class>com.republicate.slf4j.impl.ServletContextLoggerListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
<listener>
|
||||||
|
<listener-class>org.jeudego.pairgoth.web.WebappManager</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
|
<!-- filters -->
|
||||||
|
<filter>
|
||||||
|
<filter-name>webapp-slf4j-logger-ip-tag-filter</filter-name>
|
||||||
|
<filter-class>com.republicate.slf4j.impl.IPTagFilter</filter-class>
|
||||||
|
<async-supported>true</async-supported>
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<!-- filters mapping -->
|
||||||
|
<filter-mapping>
|
||||||
|
<filter-name>webapp-slf4j-logger-ip-tag-filter</filter-name>
|
||||||
|
<url-pattern>/*</url-pattern>
|
||||||
|
<dispatcher>REQUEST</dispatcher>
|
||||||
|
<dispatcher>FORWARD</dispatcher>
|
||||||
|
</filter-mapping>
|
||||||
|
|
||||||
|
<!-- servlets -->
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>api</servlet-name>
|
||||||
|
<servlet-class>org.jeudego.pairgoth.web.ApiServlet</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<!-- servlet mappings -->
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>api</servlet-name>
|
||||||
|
<url-pattern>/api/*</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<!-- context params -->
|
||||||
|
<context-param>
|
||||||
|
<param-name>webapp-slf4j-logger.format</param-name>
|
||||||
|
<param-value>%logger [%level] [%ip] %message @%file:%line:%column</param-value>
|
||||||
|
</context-param>
|
||||||
|
<context-param>
|
||||||
|
<param-name>webapp-slf4j-logger.level</param-name>
|
||||||
|
<param-value>${logger.level}</param-value>
|
||||||
|
</context-param>
|
||||||
|
<context-param>
|
||||||
|
<param-name>webapp-slf4j-logger.notification</param-name>
|
||||||
|
<param-value>${logger.notification}</param-value>
|
||||||
|
</context-param>
|
||||||
|
|
||||||
|
</web-app>
|
8
src/main/config/webapp.properties
Normal file
8
src/main/config/webapp.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# webapp
|
||||||
|
webapp.env = ${webapp.env}
|
||||||
|
webapp.url = ${webapp.url}
|
||||||
|
|
||||||
|
# smtp
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
logger.level = ${logger.level}
|
54
src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt
Normal file
54
src/main/kotlin/org/jeudego/pairgoth/api/ApiHandler.kt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.web.ApiException
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
interface ApiHandler {
|
||||||
|
|
||||||
|
fun route(request: HttpServletRequest, response: HttpServletResponse) =
|
||||||
|
when (request.method) {
|
||||||
|
"GET" -> get(request)
|
||||||
|
"POST" -> post(request)
|
||||||
|
"PUT" -> put(request)
|
||||||
|
"DELETE" -> delete(request)
|
||||||
|
else -> notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun get(request: HttpServletRequest): Json {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun post(request: HttpServletRequest): Json {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun put(request: HttpServletRequest): Json {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(request: HttpServletRequest): Json {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notImplemented(): Nothing {
|
||||||
|
throw ApiException(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPayload(request: HttpServletRequest): Json {
|
||||||
|
return request.getAttribute(PAYLOAD_KEY) as Json ?: throw ApiException(HttpServletResponse.SC_BAD_REQUEST, "no payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelector(request: HttpServletRequest): String? {
|
||||||
|
return request.getAttribute(SELECTOR_KEY) as String?
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PAYLOAD_KEY = "PAYLOAD"
|
||||||
|
const val SELECTOR_KEY = "SELECTOR"
|
||||||
|
val logger = LoggerFactory.getLogger("api")
|
||||||
|
fun badRequest(msg: String = "bad request"): Nothing = throw ApiException(HttpServletResponse.SC_BAD_REQUEST, msg)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
class PlayerHandler: ApiHandler {
|
||||||
|
}
|
@@ -0,0 +1,44 @@
|
|||||||
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.model.CanadianByoyomi
|
||||||
|
import org.jeudego.pairgoth.model.FisherTime
|
||||||
|
import org.jeudego.pairgoth.model.MacMahon
|
||||||
|
import org.jeudego.pairgoth.model.Rules
|
||||||
|
import org.jeudego.pairgoth.model.StandardByoyomi
|
||||||
|
import org.jeudego.pairgoth.model.SuddenDeath
|
||||||
|
import org.jeudego.pairgoth.model.TimeSystem
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import org.jeudego.pairgoth.model.TournamentType
|
||||||
|
import org.jeudego.pairgoth.model.fromJson
|
||||||
|
import org.jeudego.pairgoth.model.toJson
|
||||||
|
import org.jeudego.pairgoth.store.Store
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
|
class TournamentHandler(): ApiHandler {
|
||||||
|
|
||||||
|
override fun post(request: HttpServletRequest): Json {
|
||||||
|
val json = getPayload(request)
|
||||||
|
if (!json.isObject) badRequest("expecting a json object")
|
||||||
|
val payload = json.asObject()
|
||||||
|
|
||||||
|
// tournament parsing
|
||||||
|
val tournament = Tournament.fromJson(payload)
|
||||||
|
|
||||||
|
Store.addTournament(tournament)
|
||||||
|
return Json.Object("success" to true, "id" to tournament.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(request: HttpServletRequest): Json {
|
||||||
|
return when (val id = getSelector(request)?.toIntOrNull()) {
|
||||||
|
null -> Json.Array(Store.getTournamentsIDs())
|
||||||
|
else -> Store.getTournament(id)?.toJson() ?: badRequest("no tournament with id #${id}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun put(request: HttpServletRequest): Json {
|
||||||
|
val id = getSelector(request)?.toIntOrNull() ?: badRequest("missing or invalid tournament selector")
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
24
src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt
Normal file
24
src/main/kotlin/org/jeudego/pairgoth/model/Pairable.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
sealed class Pairable(val id: Int, val name: String, val rating: Double, val rank: Int)
|
||||||
|
|
||||||
|
fun Pairable.displayRank(): String = when {
|
||||||
|
rank < 0 -> "${-rank}k"
|
||||||
|
rank >= 0 && rank < 10 -> "${rank + 1}d"
|
||||||
|
rank >= 10 -> "${rank - 9}p"
|
||||||
|
else -> throw Error("impossible")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val rankRegex = Regex("(\\d+)([kdp])", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
fun Pairable.setRank(rankStr: String): Int {
|
||||||
|
val (level, letter) = rankRegex.matchEntire(rankStr)?.destructured ?: throw Error("invalid rank: $rankStr")
|
||||||
|
val num = level.toInt()
|
||||||
|
if (num < 0 || num > 9) throw Error("invalid rank: $rankStr")
|
||||||
|
return when (letter.lowercase()) {
|
||||||
|
"k" -> -num
|
||||||
|
"d" -> num - 1
|
||||||
|
"p" -> num + 9
|
||||||
|
else -> throw Error("impossible")
|
||||||
|
}
|
||||||
|
}
|
48
src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt
Normal file
48
src/main/kotlin/org/jeudego/pairgoth/model/Pairing.kt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.model.Pairing.PairingType.*
|
||||||
|
|
||||||
|
// TODO - this is only an early draft
|
||||||
|
|
||||||
|
sealed class Pairing(val type: PairingType) {
|
||||||
|
companion object {}
|
||||||
|
enum class PairingType { SWISS, MACMAHON, ROUNDROBIN }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Swiss(
|
||||||
|
var method: Method,
|
||||||
|
var firstRoundMethod: Method = method
|
||||||
|
): Pairing(SWISS) {
|
||||||
|
enum class Method { FOLD, RANDOM, SLIP }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MacMahon(
|
||||||
|
var bar: Int = 0,
|
||||||
|
var minLevel: Int = -30
|
||||||
|
): Pairing(MACMAHON) {
|
||||||
|
val groups = mutableListOf<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoundRobin: Pairing(ROUNDROBIN)
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
fun Pairing.Companion.fromJson(json: Json.Object) = when (json.getString("type")?.let { Pairing.PairingType.valueOf(it) } ?: badRequest("missing pairing type")) {
|
||||||
|
SWISS -> Swiss(
|
||||||
|
method = json.getString("method")?.let { Swiss.Method.valueOf(it) } ?: badRequest("missing pairing method"),
|
||||||
|
firstRoundMethod = json.getString("firstRoundMethod")?.let { Swiss.Method.valueOf(it) } ?: json.getString("method")!!.let { Swiss.Method.valueOf(it) }
|
||||||
|
)
|
||||||
|
MACMAHON -> MacMahon(
|
||||||
|
bar = json.getInt("bar") ?: 0,
|
||||||
|
minLevel = json.getInt("minLevel") ?: -30
|
||||||
|
)
|
||||||
|
ROUNDROBIN -> RoundRobin()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Pairing.toJson() = when (this) {
|
||||||
|
is Swiss -> Json.Object("type" to type.name, "method" to method.name, "firstRoundMethod" to firstRoundMethod.name)
|
||||||
|
is MacMahon -> Json.Object("type" to type.name, "bar" to bar, "minLevel" to minLevel)
|
||||||
|
is RoundRobin -> Json.Object("type" to type.name)
|
||||||
|
}
|
15
src/main/kotlin/org/jeudego/pairgoth/model/Player.kt
Normal file
15
src/main/kotlin/org/jeudego/pairgoth/model/Player.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
class Player(
|
||||||
|
id: Int,
|
||||||
|
name: String,
|
||||||
|
var firstname: String,
|
||||||
|
rating: Double,
|
||||||
|
rank: Int,
|
||||||
|
var country: String,
|
||||||
|
var club: String
|
||||||
|
): Pairable(id, name, rating, rank) {
|
||||||
|
companion object
|
||||||
|
// used to store external IDs ("FFG" => FFG ID, "EGF" => EGF PIN, "AGA" => AGA ID ...)
|
||||||
|
val externalIds = mutableMapOf<String, String>()
|
||||||
|
}
|
8
src/main/kotlin/org/jeudego/pairgoth/model/Rules.kt
Normal file
8
src/main/kotlin/org/jeudego/pairgoth/model/Rules.kt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
enum class Rules {
|
||||||
|
FRENCH,
|
||||||
|
JAPANESE,
|
||||||
|
CHINESE
|
||||||
|
// ...
|
||||||
|
}
|
6
src/main/kotlin/org/jeudego/pairgoth/model/Team.kt
Normal file
6
src/main/kotlin/org/jeudego/pairgoth/model/Team.kt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
class Team(id: Int, name: String, rating: Double, rank: Int): Pairable(id, name, rating, rank) {
|
||||||
|
companion object {}
|
||||||
|
val players = mutableSetOf<Player>()
|
||||||
|
}
|
92
src/main/kotlin/org/jeudego/pairgoth/model/TimeSystem.kt
Normal file
92
src/main/kotlin/org/jeudego/pairgoth/model/TimeSystem.kt
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler
|
||||||
|
|
||||||
|
|
||||||
|
sealed class TimeSystem(
|
||||||
|
val type: TimeSystemType,
|
||||||
|
val mainTime: Int,
|
||||||
|
val increment: Int,
|
||||||
|
val maxTime: Int = Int.MAX_VALUE,
|
||||||
|
val byoyomi: Int,
|
||||||
|
val periods: Int,
|
||||||
|
val stones: Int
|
||||||
|
) {
|
||||||
|
companion object {}
|
||||||
|
enum class TimeSystemType { CANADIAN, STANDARD, FISHER, SUDDEN_DEATH }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CanadianByoyomi(mainTime: Int, byoyomi: Int, stones: Int):
|
||||||
|
TimeSystem(
|
||||||
|
type = TimeSystemType.CANADIAN,
|
||||||
|
mainTime = mainTime,
|
||||||
|
increment = 0,
|
||||||
|
byoyomi = byoyomi,
|
||||||
|
periods = 1,
|
||||||
|
stones = stones
|
||||||
|
)
|
||||||
|
|
||||||
|
class StandardByoyomi(mainTime: Int, byoyomi: Int, periods: Int):
|
||||||
|
TimeSystem(
|
||||||
|
type = TimeSystemType.STANDARD,
|
||||||
|
mainTime = mainTime,
|
||||||
|
increment = 0,
|
||||||
|
byoyomi = byoyomi,
|
||||||
|
periods = periods,
|
||||||
|
stones = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
class FisherTime(mainTime: Int, increment: Int, maxTime: Int = Int.MAX_VALUE):
|
||||||
|
TimeSystem(
|
||||||
|
type = TimeSystemType.FISHER,
|
||||||
|
mainTime = mainTime,
|
||||||
|
increment = increment,
|
||||||
|
maxTime = maxTime,
|
||||||
|
byoyomi = 0,
|
||||||
|
periods = 0,
|
||||||
|
stones = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
class SuddenDeath(mainTime: Int):
|
||||||
|
TimeSystem(
|
||||||
|
type = TimeSystemType.SUDDEN_DEATH,
|
||||||
|
mainTime = mainTime,
|
||||||
|
increment = 0,
|
||||||
|
byoyomi = 0,
|
||||||
|
periods = 0,
|
||||||
|
stones = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
fun TimeSystem.Companion.fromJson(json: Json.Object) =
|
||||||
|
when (json.getString("type")?.uppercase() ?: ApiHandler.badRequest("missing timeSystem type")) {
|
||||||
|
"CANADIAN" -> CanadianByoyomi(
|
||||||
|
mainTime = json.getInt("mainTime") ?: ApiHandler.badRequest("missing timeSystem mainTime"),
|
||||||
|
byoyomi = json.getInt("byoyomi") ?: ApiHandler.badRequest("missing timeSystem byoyomi"),
|
||||||
|
stones = json.getInt("stones") ?: ApiHandler.badRequest("missing timeSystem stones")
|
||||||
|
)
|
||||||
|
"STANDARD" -> StandardByoyomi(
|
||||||
|
mainTime = json.getInt("mainTime") ?: ApiHandler.badRequest("missing timeSystem mainTime"),
|
||||||
|
byoyomi = json.getInt("byoyomi") ?: ApiHandler.badRequest("missing timeSystem byoyomi"),
|
||||||
|
periods = json.getInt("periods") ?: ApiHandler.badRequest("missing timeSystem periods")
|
||||||
|
)
|
||||||
|
"FISHER" -> FisherTime(
|
||||||
|
mainTime = json.getInt("mainTime") ?: ApiHandler.badRequest("missing timeSystem mainTime"),
|
||||||
|
increment = json.getInt("increment") ?: ApiHandler.badRequest("missing timeSystem increment"),
|
||||||
|
maxTime = json.getInt("increment") ?: Integer.MAX_VALUE
|
||||||
|
)
|
||||||
|
"SUDDEN_DEATH" -> SuddenDeath(
|
||||||
|
mainTime = json.getInt("mainTime") ?: ApiHandler.badRequest("missing timeSystem mainTime"),
|
||||||
|
)
|
||||||
|
else -> ApiHandler.badRequest("invalid or missing timeSystem type")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun TimeSystem.toJson() = when (type) {
|
||||||
|
TimeSystem.TimeSystemType.CANADIAN -> Json.Object("mainTime" to mainTime, "byoyomi" to byoyomi, "stones" to stones)
|
||||||
|
TimeSystem.TimeSystemType.STANDARD -> Json.Object("mainTime" to mainTime, "byoyomi" to byoyomi, "periods" to periods)
|
||||||
|
TimeSystem.TimeSystemType.FISHER -> Json.Object("mainTime" to mainTime, "increment" to increment, "maxTime" to maxTime)
|
||||||
|
TimeSystem.TimeSystemType.SUDDEN_DEATH -> Json.Object("mainTime" to mainTime)
|
||||||
|
}
|
||||||
|
|
74
src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt
Normal file
74
src/main/kotlin/org/jeudego/pairgoth/model/Tournament.kt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
package org.jeudego.pairgoth.model
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler.Companion.badRequest
|
||||||
|
import org.jeudego.pairgoth.store.Store
|
||||||
|
|
||||||
|
enum class TournamentType(val playersNumber: Int) {
|
||||||
|
INDIVIDUAL(1),
|
||||||
|
PAIRGO(2),
|
||||||
|
RENGO2(2),
|
||||||
|
RENGO3(3),
|
||||||
|
TEAM2(2),
|
||||||
|
TEAM3(3),
|
||||||
|
TEAM4(4),
|
||||||
|
TEAM5(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Tournament(
|
||||||
|
var id: Int,
|
||||||
|
var type: TournamentType,
|
||||||
|
var name: String,
|
||||||
|
var shortName: String,
|
||||||
|
var startDate: LocalDate,
|
||||||
|
var endDate: LocalDate,
|
||||||
|
var country: String,
|
||||||
|
var location: String,
|
||||||
|
var online: Boolean,
|
||||||
|
var timeSystem: TimeSystem,
|
||||||
|
var pairing: Pairing,
|
||||||
|
var rules: Rules = Rules.FRENCH,
|
||||||
|
var gobanSize: Int = 19,
|
||||||
|
var komi: Double = 7.5
|
||||||
|
) {
|
||||||
|
companion object {}
|
||||||
|
val pairables = mutableMapOf<Int, Pairable>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialization
|
||||||
|
|
||||||
|
fun Tournament.Companion.fromJson(json: Json.Object) = Tournament(
|
||||||
|
id = json.getInt("id") ?: Store.nextTournamentId,
|
||||||
|
type = json.getString("type")?.uppercase()?.let { TournamentType.valueOf(it) } ?: badRequest("missing type"),
|
||||||
|
name = json.getString("name") ?: ApiHandler.badRequest("missing name"),
|
||||||
|
shortName = json.getString("shortName") ?: ApiHandler.badRequest("missing shortName"),
|
||||||
|
startDate = json.getLocalDate("startDate") ?: ApiHandler.badRequest("missing startDate"),
|
||||||
|
endDate = json.getLocalDate("endDate") ?: ApiHandler.badRequest("missing endDate"),
|
||||||
|
country = json.getString("country") ?: ApiHandler.badRequest("missing country"),
|
||||||
|
location = json.getString("location") ?: ApiHandler.badRequest("missing location"),
|
||||||
|
online = json.getBoolean("online") ?: false,
|
||||||
|
komi = json.getDouble("komi") ?: 7.5,
|
||||||
|
rules = json.getString("rules")?.let { Rules.valueOf(it) } ?: Rules.FRENCH,
|
||||||
|
gobanSize = json.getInt("gobanSize") ?: 19,
|
||||||
|
timeSystem = TimeSystem.fromJson(json.getObject("timeSystem") ?: badRequest("missing timeSystem")),
|
||||||
|
pairing = MacMahon()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Tournament.toJson() = Json.Object(
|
||||||
|
"id" to id,
|
||||||
|
"type" to type.name,
|
||||||
|
"name" to name,
|
||||||
|
"shortName" to shortName,
|
||||||
|
"startDate" to startDate.toString(),
|
||||||
|
"endDate" to endDate.toString(),
|
||||||
|
"country" to country,
|
||||||
|
"location" to location,
|
||||||
|
"online" to online,
|
||||||
|
"komi" to komi,
|
||||||
|
"rules" to rules.name,
|
||||||
|
"gobanSize" to gobanSize,
|
||||||
|
"timeSystem" to timeSystem.toJson(),
|
||||||
|
"pairing" to pairing.toJson()
|
||||||
|
)
|
28
src/main/kotlin/org/jeudego/pairgoth/oauth/FacebookHelper.kt
Normal file
28
src/main/kotlin/org/jeudego/pairgoth/oauth/FacebookHelper.kt
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
class FacebookHelper : OAuthHelper() {
|
||||||
|
override val name: String
|
||||||
|
get() = "facebook"
|
||||||
|
|
||||||
|
override fun getLoginURL(sessionId: String?): String {
|
||||||
|
return "https://www.facebook.com/v14.0/dialog/oauth?" +
|
||||||
|
"client_id=" + clientId +
|
||||||
|
"&redirect_uri=" + redirectURI +
|
||||||
|
"&scope=email" +
|
||||||
|
"&state=" + getState(sessionId!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccessTokenURL(code: String): String? {
|
||||||
|
return "https://graph.facebook.com/v14.0/oauth/access_token?" +
|
||||||
|
"client_id=" + clientId +
|
||||||
|
"&redirect_uri=" + redirectURI +
|
||||||
|
"&client_secret=" + secret +
|
||||||
|
"&code=" + code
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserInfosURL(accessToken: String): String? {
|
||||||
|
return "https://graph.facebook.com/me?" +
|
||||||
|
"field=email" +
|
||||||
|
"&access_token=" + accessToken
|
||||||
|
}
|
||||||
|
}
|
18
src/main/kotlin/org/jeudego/pairgoth/oauth/GoogleHelper.kt
Normal file
18
src/main/kotlin/org/jeudego/pairgoth/oauth/GoogleHelper.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
class GoogleHelper : OAuthHelper() {
|
||||||
|
override val name: String
|
||||||
|
get() = "google"
|
||||||
|
|
||||||
|
override fun getLoginURL(sessionId: String?): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccessTokenURL(code: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserInfosURL(accessToken: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
class InstagramHelper : OAuthHelper() {
|
||||||
|
override val name: String
|
||||||
|
get() = "instagram"
|
||||||
|
|
||||||
|
override fun getLoginURL(sessionId: String?): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccessTokenURL(code: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserInfosURL(accessToken: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
77
src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt
Normal file
77
src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
// In progress
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.web.WebappManager
|
||||||
|
//import com.republicate.modality.util.AESCryptograph
|
||||||
|
//import com.republicate.modality.util.Cryptograph
|
||||||
|
import org.apache.commons.codec.binary.Base64
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
abstract class OAuthHelper {
|
||||||
|
abstract val name: String
|
||||||
|
abstract fun getLoginURL(sessionId: String?): String
|
||||||
|
protected val clientId: String
|
||||||
|
protected get() = WebappManager.getProperty("oauth." + name + ".client_id")
|
||||||
|
protected val secret: String
|
||||||
|
protected get() = WebappManager.getProperty("oauth." + name + ".secret")
|
||||||
|
protected val redirectURI: String?
|
||||||
|
protected get() = try {
|
||||||
|
val uri: String = WebappManager.Companion.getProperty("webapp.url") + "/oauth.html"
|
||||||
|
URLEncoder.encode(uri, "UTF-8")
|
||||||
|
} catch (uee: UnsupportedEncodingException) {
|
||||||
|
logger.error("could not encode redirect URI", uee)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getState(sessionId: String): String {
|
||||||
|
return name + ":" + encrypt(sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkState(state: String, expectedSessionId: String): Boolean {
|
||||||
|
val foundSessionId = decrypt(state)
|
||||||
|
return expectedSessionId == foundSessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun getAccessTokenURL(code: String): String?
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getAccessToken(code: String): String {
|
||||||
|
val json: Json.Object = Json.Object() // TODO - apiClient.get(getAccessTokenURL(code))
|
||||||
|
return json.getString("access_token")!! // ?!
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract fun getUserInfosURL(accessToken: String): String?
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getUserEmail(accessToken: String): String {
|
||||||
|
val json: Json.Object = Json.Object()
|
||||||
|
// TODO
|
||||||
|
// apiClient.get(getUserInfosURL(accessToken))
|
||||||
|
return json.getString("email") ?: throw IOException("could not fetch email")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected var logger = LoggerFactory.getLogger("oauth")
|
||||||
|
private const val salt = "0efd28fb53cbac42"
|
||||||
|
// private val sessionIdCrypto: Cryptograph = AESCryptograph().apply {
|
||||||
|
// init(salt)
|
||||||
|
// }
|
||||||
|
|
||||||
|
private fun encrypt(input: String): String {
|
||||||
|
return "TODO"
|
||||||
|
// return Base64.encodeBase64URLSafeString(sessionIdCrypto.encrypt(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decrypt(input: String): String {
|
||||||
|
return "TODO"
|
||||||
|
// return sessionIdCrypto.decrypt(Base64.decodeBase64(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// private val apiClient: ApiClient = ApiClient()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,17 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
object OauthHelperFactory {
|
||||||
|
private val facebook: OAuthHelper = FacebookHelper()
|
||||||
|
private val google: OAuthHelper = GoogleHelper()
|
||||||
|
private val instagram: OAuthHelper = InstagramHelper()
|
||||||
|
private val twitter: OAuthHelper = TwitterHelper()
|
||||||
|
fun getHelper(provider: String?): OAuthHelper {
|
||||||
|
return when (provider) {
|
||||||
|
"facebook" -> facebook
|
||||||
|
"google" -> google
|
||||||
|
"instagram" -> instagram
|
||||||
|
"twitter" -> twitter
|
||||||
|
else -> throw RuntimeException("wrong provider")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/main/kotlin/org/jeudego/pairgoth/oauth/TwitterHelper.kt
Normal file
18
src/main/kotlin/org/jeudego/pairgoth/oauth/TwitterHelper.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.jeudego.pairgoth.oauth
|
||||||
|
|
||||||
|
class TwitterHelper : OAuthHelper() {
|
||||||
|
override val name: String
|
||||||
|
get() = "twitter"
|
||||||
|
|
||||||
|
override fun getLoginURL(sessionId: String?): String {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAccessTokenURL(code: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUserInfosURL(accessToken: String): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
24
src/main/kotlin/org/jeudego/pairgoth/store/Store.kt
Normal file
24
src/main/kotlin/org/jeudego/pairgoth/store/Store.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package org.jeudego.pairgoth.store
|
||||||
|
|
||||||
|
import org.jeudego.pairgoth.model.Player
|
||||||
|
import org.jeudego.pairgoth.model.Tournament
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
object Store {
|
||||||
|
private val _nextTournamentId = AtomicInteger()
|
||||||
|
private val _nextPlayerId = AtomicInteger()
|
||||||
|
val nextTournamentId get() = _nextTournamentId.incrementAndGet()
|
||||||
|
val nextPlayerId get() = _nextPlayerId.incrementAndGet()
|
||||||
|
|
||||||
|
private val tournaments = mutableMapOf<Int, Tournament>()
|
||||||
|
private val players = mutableMapOf<Int, Player>()
|
||||||
|
|
||||||
|
fun addTournament(tournament: Tournament) {
|
||||||
|
if (tournaments.containsKey(tournament.id)) throw Error("tournament id #${tournament.id} already exists")
|
||||||
|
tournaments[tournament.id] = tournament
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTournament(id: Int) = tournaments[id]
|
||||||
|
|
||||||
|
fun getTournamentsIDs(): Set<Int> = tournaments.keys
|
||||||
|
}
|
18
src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
Normal file
18
src/main/kotlin/org/jeudego/pairgoth/util/Colorizer.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
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)
|
||||||
|
}
|
25
src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
Normal file
25
src/main/kotlin/org/jeudego/pairgoth/util/JsonIO.kt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
36
src/main/kotlin/org/jeudego/pairgoth/web/ApiException.kt
Normal file
36
src/main/kotlin/org/jeudego/pairgoth/web/ApiException.kt
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package org.jeudego.pairgoth.web
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class ApiException : IOException {
|
||||||
|
var code: Int
|
||||||
|
private set
|
||||||
|
var details: Json.Object
|
||||||
|
private set
|
||||||
|
|
||||||
|
constructor(code: Int) : super("error") {
|
||||||
|
this.code = code
|
||||||
|
details = Json.Object("message" to message)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(code: Int, message: String?) : super(message) {
|
||||||
|
this.code = code
|
||||||
|
details = Json.Object("message" to message)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(code: Int, cause: Exception) : super(cause) {
|
||||||
|
this.code = code
|
||||||
|
details = Json.Object("message" to "Erreur interne du serveur : " + cause.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(code: Int, message: String, cause: Exception) : super(message, cause) {
|
||||||
|
this.code = code
|
||||||
|
details = Json.Object("message" to message + " : " + cause.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(code: Int, details: Json.Object) : super(details.getString("message")) {
|
||||||
|
this.code = code
|
||||||
|
this.details = details
|
||||||
|
}
|
||||||
|
}
|
222
src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
Normal file
222
src/main/kotlin/org/jeudego/pairgoth/web/ApiServlet.kt
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
package org.jeudego.pairgoth.web
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.api.ApiHandler
|
||||||
|
import org.jeudego.pairgoth.api.PlayerHandler
|
||||||
|
import org.jeudego.pairgoth.api.TournamentHandler
|
||||||
|
import org.jeudego.pairgoth.util.Colorizer
|
||||||
|
import org.jeudego.pairgoth.util.Colorizer.green
|
||||||
|
import org.jeudego.pairgoth.util.Colorizer.red
|
||||||
|
import org.jeudego.pairgoth.util.parse
|
||||||
|
import org.jeudego.pairgoth.util.toString
|
||||||
|
import org.jeudego.pairgoth.web.ApiException
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.util.*
|
||||||
|
import javax.servlet.ServletException
|
||||||
|
import javax.servlet.http.HttpServlet
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
import javax.servlet.http.HttpServletResponse
|
||||||
|
|
||||||
|
class ApiServlet : HttpServlet() {
|
||||||
|
|
||||||
|
val tournamentHandler = TournamentHandler()
|
||||||
|
val playerHandler = PlayerHandler()
|
||||||
|
|
||||||
|
public override fun doGet(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
doRequest(request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun doPost(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
doRequest(request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun doPut(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
doRequest(request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun doDelete(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
doRequest(request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doRequest(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
var payload: Json? = null
|
||||||
|
var reason = "OK"
|
||||||
|
try {
|
||||||
|
if ("dev" == WebappManager.getProperty("webapp.env")) {
|
||||||
|
response.addHeader("Access-Control-Allow-Origin", "*")
|
||||||
|
}
|
||||||
|
validateContentType(request)
|
||||||
|
validateAccept(request);
|
||||||
|
val uri = request.requestURI
|
||||||
|
logger.logRequest(request, !uri.contains(".") && uri.length > 1)
|
||||||
|
|
||||||
|
val parts = uri.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (parts.size < 3 || parts.size > 5) throw ApiException(HttpServletResponse.SC_BAD_REQUEST)
|
||||||
|
if (parts.size >= 4) {
|
||||||
|
request.setAttribute(ApiHandler.SELECTOR_KEY, parts[3])
|
||||||
|
}
|
||||||
|
val entity = parts[2]
|
||||||
|
val handler = when (entity) {
|
||||||
|
"tournament" -> tournamentHandler
|
||||||
|
"player" -> playerHandler
|
||||||
|
else -> ApiHandler.badRequest("unknown entity")
|
||||||
|
}
|
||||||
|
payload = handler.route(request, response)
|
||||||
|
// if payload is null, it means the handler already sent the response
|
||||||
|
if (payload != null) {
|
||||||
|
setContentType(response)
|
||||||
|
payload.toString(response.writer)
|
||||||
|
}
|
||||||
|
} catch (apiException: ApiException) {
|
||||||
|
reason = apiException.message ?: "unknown API error"
|
||||||
|
if (reason == null) error(response, apiException.code) else error(
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
apiException.code,
|
||||||
|
reason,
|
||||||
|
apiException
|
||||||
|
)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
logger.error(red("could not process call"), ioe)
|
||||||
|
reason = ioe.message ?: "unknown i/o exception"
|
||||||
|
error(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, reason, ioe)
|
||||||
|
} finally {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.append(response.status).append(' ')
|
||||||
|
.append(reason)
|
||||||
|
if (response.status == HttpServletResponse.SC_INTERNAL_SERVER_ERROR) {
|
||||||
|
logger.info(red(">> {}"), builder.toString())
|
||||||
|
} else {
|
||||||
|
logger.info(green(">> {}"), builder.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
for (header in response.headerNames) {
|
||||||
|
val value = response.getHeader(header)
|
||||||
|
logger.trace(green(">> {}: {}"), header, value)
|
||||||
|
}
|
||||||
|
if (payload != null) {
|
||||||
|
try {
|
||||||
|
logger.logPayload(green(">> "), payload, false)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(ApiException::class)
|
||||||
|
protected fun validateContentType(request: HttpServletRequest) {
|
||||||
|
// extract content type parts
|
||||||
|
val contentType = request.contentType
|
||||||
|
if (contentType == null) {
|
||||||
|
if (request.method == "GET") return
|
||||||
|
throw ApiException(
|
||||||
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"no content type header"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val sep = contentType.indexOf(';')
|
||||||
|
val mimeType: String
|
||||||
|
var charset: String? = null
|
||||||
|
if (sep == -1) mimeType = contentType else {
|
||||||
|
mimeType = contentType.substring(0, sep).trim { it <= ' ' }
|
||||||
|
val params =
|
||||||
|
contentType.substring(sep + 1).split("=".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
|
.toTypedArray()
|
||||||
|
if (params.size == 2 && params[0].lowercase(Locale.getDefault())
|
||||||
|
.trim { it <= ' ' } == "charset"
|
||||||
|
) {
|
||||||
|
charset = params[1].lowercase(Locale.getDefault()).trim { it <= ' ' }
|
||||||
|
.replace("-".toRegex(), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check charset
|
||||||
|
if (charset != null && EXPECTED_CHARSET != charset) throw ApiException(
|
||||||
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"UTF-8 content expected"
|
||||||
|
)
|
||||||
|
|
||||||
|
// check content type
|
||||||
|
if (!isJson(mimeType)) throw ApiException(
|
||||||
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"JSON content expected"
|
||||||
|
)
|
||||||
|
|
||||||
|
// put Json body as request attribute
|
||||||
|
try {
|
||||||
|
Json.parse(request.reader)?.let { payload: Json ->
|
||||||
|
request.setAttribute(ApiHandler.PAYLOAD_KEY, payload)
|
||||||
|
if (logger.isInfoEnabled) {
|
||||||
|
logger.logPayload("<< ", payload, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
throw ApiException(HttpServletResponse.SC_BAD_REQUEST, ioe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(ApiException::class)
|
||||||
|
protected fun validateAccept(request: HttpServletRequest) {
|
||||||
|
val accept = request.getHeader("Accept")
|
||||||
|
?: throw ApiException(
|
||||||
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"Missing 'Accept' header"
|
||||||
|
)
|
||||||
|
if (!isJson(accept)) throw ApiException(
|
||||||
|
HttpServletResponse.SC_BAD_REQUEST,
|
||||||
|
"Invalid 'Accept' header"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun setContentType(response: HttpServletResponse) {
|
||||||
|
response.contentType = "application/json; charset=UTF-8"
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun error(
|
||||||
|
request: HttpServletRequest,
|
||||||
|
response: HttpServletResponse,
|
||||||
|
code: Int,
|
||||||
|
message: String?,
|
||||||
|
cause: Throwable? = null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (code == 500) {
|
||||||
|
logger.error(
|
||||||
|
"Request {} {} gave error {} {}",
|
||||||
|
request.method,
|
||||||
|
request.requestURI,
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
cause
|
||||||
|
)
|
||||||
|
}
|
||||||
|
response.sendError(code, message)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
logger.error("Could not send back error", ioe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun error(response: HttpServletResponse, code: Int) {
|
||||||
|
try {
|
||||||
|
response.sendError(code)
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
logger.error("Could not send back error", ioe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
protected var logger = LoggerFactory.getLogger("api")
|
||||||
|
protected const val EXPECTED_CHARSET = "utf8"
|
||||||
|
const val AUTH_HEADER = "Authorization"
|
||||||
|
const val AUTH_PREFIX = "Bearer"
|
||||||
|
private fun isJson(mimeType: String): Boolean {
|
||||||
|
return "text/json" == mimeType || "application/json" == mimeType ||
|
||||||
|
mimeType.endsWith("+json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/main/kotlin/org/jeudego/pairgoth/web/Logging.kt
Normal file
71
src/main/kotlin/org/jeudego/pairgoth/web/Logging.kt
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
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 (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)
|
||||||
|
info(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
|
||||||
|
}
|
184
src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
Normal file
184
src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
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.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.*
|
||||||
|
import javax.net.ssl.*
|
||||||
|
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<TrustManager>(object : X509TrustManager {
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TrustAllX509TrustManager")
|
||||||
|
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
|
||||||
|
@Suppress("TrustAllX509TrustManager")
|
||||||
|
override fun checkServerTrusted(certs: Array<X509Certificate>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ServletContextListener interface */
|
||||||
|
override fun contextInitialized(sce: ServletContextEvent) {
|
||||||
|
// overcome a Jetty's bug (v9.4.10.v20180503) whereas if a @WebListener is also listed in the descriptor
|
||||||
|
// it will be instanciated twice...
|
||||||
|
context = sce.servletContext
|
||||||
|
logger.info("---------- Starting Web Application ----------")
|
||||||
|
context.setAttribute("manager", this)
|
||||||
|
webappRoot = context.getRealPath("/")
|
||||||
|
try {
|
||||||
|
properties.load(context.getResourceAsStream("/WEB-INF/webapp.properties"))
|
||||||
|
val submaps: MutableMap<String, MutableMap<String, String>> = HashMap()
|
||||||
|
for (prop in properties.stringPropertyNames()) {
|
||||||
|
val value = properties.getProperty(prop)
|
||||||
|
|
||||||
|
// filter out missing values and passwords
|
||||||
|
if (value.startsWith("\${") || prop.contains("password")) continue
|
||||||
|
context.setAttribute(prop, value)
|
||||||
|
|
||||||
|
// also support one level of submaps (TODO - more)
|
||||||
|
val dot = prop.indexOf('.')
|
||||||
|
if (dot != -1) {
|
||||||
|
val topKey = prop.substring(0, dot)
|
||||||
|
val subKey = prop.substring(dot + 1)
|
||||||
|
if ("password" == subKey) continue
|
||||||
|
var submap = submaps[topKey]
|
||||||
|
if (submap == null) {
|
||||||
|
submap = HashMap()
|
||||||
|
submaps[topKey] = submap
|
||||||
|
}
|
||||||
|
submap[subKey] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ((key, value) in submaps) {
|
||||||
|
context.setAttribute(key, value)
|
||||||
|
}
|
||||||
|
logger.info("Using profile {}", properties.getProperty("webapp.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contextDestroyed(sce: ServletContextEvent) {
|
||||||
|
logger.info("---------- Stopping Web Application ----------")
|
||||||
|
|
||||||
|
// overcome a Jetty's bug (v9.4.10.v20180503) whereas if a @WebListener is also listed in the descriptor
|
||||||
|
// it will be instanciated twice...
|
||||||
|
if (context == null) return
|
||||||
|
val context = sce.servletContext
|
||||||
|
for (service in webServices.keys) stopService(service, true)
|
||||||
|
// ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 {
|
||||||
|
lateinit var webappRoot: String
|
||||||
|
lateinit var context: ServletContext
|
||||||
|
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
||||||
|
var logger = LoggerFactory.getLogger(WebappManager::class.java)
|
||||||
|
val properties = Properties()
|
||||||
|
fun getProperty(prop: String?): String {
|
||||||
|
return properties.getProperty(prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
val webappURL by lazy { getProperty("webapp.url") }
|
||||||
|
|
||||||
|
private val services = mutableMapOf<String, Pair<Runnable, Thread>>()
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/main/webapp/WEB-INF/jetty-web.xml
Normal file
10
src/main/webapp/WEB-INF/jetty-web.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
|
||||||
|
<Configure class="org.eclipse.jetty.webapp.WebAppContext">
|
||||||
|
<!-- https://www.eclipse.org/jetty/documentation/jetty-9/index.html#file-alias-serving -->
|
||||||
|
<Call name="addAliasCheck">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker" />
|
||||||
|
</Arg>
|
||||||
|
</Call>
|
||||||
|
</Configure>
|
2
src/main/webapp/WEB-INF/logger.properties
Normal file
2
src/main/webapp/WEB-INF/logger.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
format = %date [%level] %ip [%logger] %message (@%file:%line:%column)
|
||||||
|
level = trace
|
1
src/main/webapp/WEB-INF/web.xml
Symbolic link
1
src/main/webapp/WEB-INF/web.xml
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../../target/pairgoth-1.0-SNAPSHOT/WEB-INF/web.xml
|
1
src/main/webapp/WEB-INF/webapp.properties
Symbolic link
1
src/main/webapp/WEB-INF/webapp.properties
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../../target/pairgoth-1.0-SNAPSHOT/WEB-INF/webapp.properties
|
0
src/test/kotlin/.gitkeep
Normal file
0
src/test/kotlin/.gitkeep
Normal file
Reference in New Issue
Block a user