Finished migration to Kotlin
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import org.gradle.accessors.dm.LibrariesForLibs
|
import org.gradle.accessors.dm.LibrariesForLibs
|
||||||
import java.time.LocalDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
@@ -51,7 +51,7 @@ tasks.jacocoTestReport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
|
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
|
||||||
|
|
||||||
tasks.jar {
|
tasks.jar {
|
||||||
doFirst {
|
doFirst {
|
||||||
@@ -59,7 +59,7 @@ tasks.jar {
|
|||||||
attributes["Implementation-Title"] = rootProject.name
|
attributes["Implementation-Title"] = rootProject.name
|
||||||
attributes["Implementation-Version"] = archiveVersion.get()
|
attributes["Implementation-Version"] = archiveVersion.get()
|
||||||
attributes["Implementation-Vendor"] = "Jim Martens"
|
attributes["Implementation-Vendor"] = "Jim Martens"
|
||||||
attributes["Build-Timestamp"] = LocalDateTime.now().format(formatter)
|
attributes["Build-Timestamp"] = ZonedDateTime.now().format(formatter)
|
||||||
attributes["Created-By"] = "Gradle ${gradle.gradleVersion}"
|
attributes["Created-By"] = "Gradle ${gradle.gradleVersion}"
|
||||||
attributes["Build-Jdk"] = "${providers.systemProperty("java.version").get()} (${providers.systemProperty("java.vendor").get()} ${providers.systemProperty("java.vm.version").get()})"
|
attributes["Build-Jdk"] = "${providers.systemProperty("java.version").get()} (${providers.systemProperty("java.vendor").get()} ${providers.systemProperty("java.vm.version").get()})"
|
||||||
attributes["Build-OS"] = "${providers.systemProperty("os.name").get()} ${providers.systemProperty("os.arch").get()} ${providers.systemProperty("os.version").get()}"
|
attributes["Build-OS"] = "${providers.systemProperty("os.name").get()} ${providers.systemProperty("os.arch").get()} ${providers.systemProperty("os.version").get()}"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.bundles.spring.boot)
|
implementation(libs.bundles.spring.boot)
|
||||||
testImplementation(libs.spring.boot.test)
|
testImplementation(libs.spring.boot.test)
|
||||||
annotationProcessor(libs.spring.boot.config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@@ -31,7 +30,7 @@ idea {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val integrationTestImplementation by configurations.getting {
|
val integrationTestImplementation: Configuration by configurations.getting {
|
||||||
extendsFrom(configurations.testImplementation.get())
|
extendsFrom(configurations.testImplementation.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,3 +69,15 @@ tasks.jar {
|
|||||||
enabled = true
|
enabled = true
|
||||||
archiveClassifier.set("")
|
archiveClassifier.set("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.bootDistZip {
|
||||||
|
dependsOn(tasks.jar)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.bootDistTar {
|
||||||
|
dependsOn(tasks.jar)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.bootStartScripts {
|
||||||
|
dependsOn(tasks.jar)
|
||||||
|
}
|
||||||
@@ -7,3 +7,4 @@ org.gradle.daemon=true
|
|||||||
org.gradle.welcome=never
|
org.gradle.welcome=never
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
kapt.include.compile.classpath=false
|
||||||
@@ -2,7 +2,6 @@ plugins {
|
|||||||
id("twomartens.spring-boot-cloud")
|
id("twomartens.spring-boot-cloud")
|
||||||
id("twomartens.kotlin")
|
id("twomartens.kotlin")
|
||||||
kotlin("kapt")
|
kotlin("kapt")
|
||||||
alias(libs.plugins.kotlin.lombok)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -11,7 +10,3 @@ dependencies {
|
|||||||
annotationProcessor(libs.mapstruct.processor)
|
annotationProcessor(libs.mapstruct.processor)
|
||||||
kapt(libs.mapstruct.processor)
|
kapt(libs.mapstruct.processor)
|
||||||
}
|
}
|
||||||
|
|
||||||
kapt {
|
|
||||||
keepJavacAnnotationProcessors = true
|
|
||||||
}
|
|
||||||
+1
-1
@@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration
|
|||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
open class ClockConfiguration(val serviceProperties: ServiceProperties) {
|
open class ClockConfiguration(private val serviceProperties: ServiceProperties) {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
open fun clock(): Clock {
|
open fun clock(): Clock {
|
||||||
|
|||||||
+23
-23
@@ -1,39 +1,39 @@
|
|||||||
package de.twomartens.wahlrecht.configuration;
|
package de.twomartens.wahlrecht.configuration
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
|
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType
|
||||||
import io.swagger.v3.oas.annotations.security.OAuthFlow;
|
import io.swagger.v3.oas.annotations.security.OAuthFlow
|
||||||
import io.swagger.v3.oas.annotations.security.OAuthFlows;
|
import io.swagger.v3.oas.annotations.security.OAuthFlows
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityScheme;
|
import io.swagger.v3.oas.annotations.security.SecurityScheme
|
||||||
import io.swagger.v3.oas.models.OpenAPI;
|
import io.swagger.v3.oas.models.OpenAPI
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import io.swagger.v3.oas.models.info.Info
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
@SecurityScheme(
|
@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT")
|
||||||
name = "bearerAuth",
|
|
||||||
type = SecuritySchemeType.HTTP,
|
|
||||||
scheme = "bearer",
|
|
||||||
bearerFormat = "JWT"
|
|
||||||
)
|
|
||||||
@SecurityScheme(
|
@SecurityScheme(
|
||||||
name = "oauth2",
|
name = "oauth2",
|
||||||
type = SecuritySchemeType.OAUTH2,
|
type = SecuritySchemeType.OAUTH2,
|
||||||
flows = @OAuthFlows(
|
flows = OAuthFlows(
|
||||||
implicit = @OAuthFlow(
|
implicit = OAuthFlow(
|
||||||
authorizationUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/auth",
|
authorizationUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/auth",
|
||||||
tokenUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/token"
|
tokenUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/token"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@Configuration
|
@Configuration
|
||||||
public class OpenApiConfiguration {
|
open class OpenApiConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
public OpenAPI customOpenAPI(@Value("${openapi.description}") String apiDesciption,
|
open fun customOpenAPI(
|
||||||
@Value("${openapi.version}") String apiVersion, @Value("${openapi.title}") String apiTitle) {
|
@Value("\${openapi.description}") apiDescription: String,
|
||||||
return new OpenAPI()
|
@Value("\${openapi.version}") apiVersion: String, @Value("\${openapi.title}") apiTitle: String
|
||||||
.info(new io.swagger.v3.oas.models.info.Info()
|
): OpenAPI {
|
||||||
|
return OpenAPI()
|
||||||
|
.info(
|
||||||
|
Info()
|
||||||
.title(apiTitle)
|
.title(apiTitle)
|
||||||
.version(apiVersion)
|
.version(apiVersion)
|
||||||
.description(apiDesciption));
|
.description(apiDescription)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-19
@@ -1,28 +1,25 @@
|
|||||||
package de.twomartens.wahlrecht.configuration;
|
package de.twomartens.wahlrecht.configuration
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.monitoring.statusprobe.CountBasedStatusProbe;
|
import de.twomartens.wahlrecht.monitoring.statusprobe.CountBasedStatusProbe
|
||||||
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe;
|
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe
|
||||||
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality;
|
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality
|
||||||
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeLogger;
|
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeLogger
|
||||||
import java.time.Clock;
|
import org.springframework.context.annotation.Bean
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Bean;
|
import java.time.Clock
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
open class StatusProbeConfiguration(private val clock: Clock) {
|
||||||
public class StatusProbeConfiguration {
|
|
||||||
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public StatusProbeLogger statusProbeLogger() {
|
open fun statusProbeLogger(): StatusProbeLogger {
|
||||||
return new StatusProbeLogger(clock);
|
return StatusProbeLogger(clock)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public StatusProbe testStatusProbe(StatusProbeLogger statusProbeLogger) {
|
open fun testStatusProbe(statusProbeLogger: StatusProbeLogger): StatusProbe {
|
||||||
return new CountBasedStatusProbe(1,
|
return CountBasedStatusProbe(
|
||||||
clock, StatusProbeCriticality.K1, "testStatusProbe", statusProbeLogger);
|
1,
|
||||||
|
clock, StatusProbeCriticality.K1, "testStatusProbe", statusProbeLogger
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+18
-26
@@ -1,33 +1,25 @@
|
|||||||
package de.twomartens.wahlrecht.configuration;
|
package de.twomartens.wahlrecht.configuration
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest;
|
import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.http.HttpMethod
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.web.servlet.config.annotation.CorsRegistry
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistration;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@RequiredArgsConstructor
|
open class WebConfiguration(private val headerInterceptorRest: HeaderInterceptorRest) : WebMvcConfigurer {
|
||||||
public class WebConfiguration implements WebMvcConfigurer {
|
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||||
|
registry.addInterceptor(headerInterceptorRest)
|
||||||
private final HeaderInterceptorRest headerInterceptorRest;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addInterceptors(@NonNull InterceptorRegistry registry) {
|
|
||||||
registry.addInterceptor(headerInterceptorRest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun addCorsMappings(registry: CorsRegistry) {
|
||||||
public void addCorsMappings(@NonNull CorsRegistry registry) {
|
val registration = registry.addMapping("/**")
|
||||||
CorsRegistration registration = registry.addMapping("/**");
|
registration.allowedMethods(
|
||||||
registration.allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(),
|
HttpMethod.GET.name(), HttpMethod.POST.name(),
|
||||||
HttpMethod.PUT.name(), HttpMethod.OPTIONS.name());
|
HttpMethod.PUT.name(), HttpMethod.OPTIONS.name()
|
||||||
registration.allowCredentials(true);
|
)
|
||||||
registration.allowedOrigins("http://localhost:4200");
|
registration.allowCredentials(true)
|
||||||
|
registration.allowedOrigins("http://localhost:4200")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-78
@@ -1,58 +1,41 @@
|
|||||||
package de.twomartens.wahlrecht.configuration;
|
package de.twomartens.wahlrecht.configuration
|
||||||
|
|
||||||
|
import de.twomartens.wahlrecht.security.SpringPolicyEnforcerFilter
|
||||||
import de.twomartens.wahlrecht.security.SpringPolicyEnforcerFilter;
|
import org.keycloak.adapters.authorization.spi.ConfigurationResolver
|
||||||
import java.io.IOException;
|
import org.keycloak.adapters.authorization.spi.HttpRequest
|
||||||
import java.util.ArrayList;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
|
||||||
import java.util.Collection;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode
|
||||||
import java.util.List;
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig
|
||||||
import java.util.Map;
|
import org.keycloak.util.JsonSerialization
|
||||||
import org.keycloak.adapters.authorization.spi.ConfigurationResolver;
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.keycloak.adapters.authorization.spi.HttpRequest;
|
import org.springframework.context.annotation.Bean
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
|
import org.springframework.context.annotation.Configuration
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode;
|
import org.springframework.http.HttpMethod
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.security.oauth2.jwt.JwtDecoder
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.security.web.SecurityFilterChain
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import java.io.IOException
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
|
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|
||||||
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
public class WebSecurityConfiguration {
|
open class WebSecurityConfiguration {
|
||||||
|
@Value("\${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
|
||||||
private static final Collection<String> PERMITTED_PATHS = List.of(
|
private lateinit var jwkSetUri: String
|
||||||
"/wahlrecht/v1/healthCheck",
|
|
||||||
"/actuator/**",
|
|
||||||
"/wahlrecht/v1/doc/**",
|
|
||||||
"/wahlrecht/v1/api-docs/**",
|
|
||||||
"/error");
|
|
||||||
|
|
||||||
private static final List<PathConfig> PATHS = buildPathConfigs();
|
|
||||||
|
|
||||||
|
|
||||||
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
|
|
||||||
private String jwkSetUri;
|
|
||||||
|
|
||||||
@Value("#{environment.CLIENT_SECRET}")
|
@Value("#{environment.CLIENT_SECRET}")
|
||||||
private String clientSecret;
|
private lateinit var clientSecret: String
|
||||||
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(@NonNull HttpSecurity http) throws Exception {
|
@Throws(Exception::class)
|
||||||
|
open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http
|
http
|
||||||
.csrf().disable()
|
.csrf().disable()
|
||||||
.authorizeHttpRequests()
|
.authorizeHttpRequests()
|
||||||
.requestMatchers(PERMITTED_PATHS.toArray(new String[0]))
|
.requestMatchers(*PERMITTED_PATHS.toTypedArray<String>())
|
||||||
.permitAll()
|
.permitAll()
|
||||||
.and()
|
.and()
|
||||||
.authorizeHttpRequests()
|
.authorizeHttpRequests()
|
||||||
@@ -63,50 +46,57 @@ public class WebSecurityConfiguration {
|
|||||||
.anyRequest()
|
.anyRequest()
|
||||||
.authenticated()
|
.authenticated()
|
||||||
.and()
|
.and()
|
||||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
|
.oauth2ResourceServer { obj: OAuth2ResourceServerConfigurer<HttpSecurity?> -> obj.jwt() }
|
||||||
.addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter.class);
|
.addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter::class.java)
|
||||||
return http.build();
|
return http.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
JwtDecoder jwtDecoder() {
|
open fun jwtDecoder(): JwtDecoder {
|
||||||
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
|
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private SpringPolicyEnforcerFilter createPolicyEnforcerFilter() {
|
private fun createPolicyEnforcerFilter(): SpringPolicyEnforcerFilter {
|
||||||
return new SpringPolicyEnforcerFilter(new ConfigurationResolver() {
|
return SpringPolicyEnforcerFilter(object : ConfigurationResolver {
|
||||||
@Override
|
override fun resolve(request: HttpRequest): PolicyEnforcerConfig {
|
||||||
public PolicyEnforcerConfig resolve(HttpRequest request) {
|
return try {
|
||||||
try {
|
val policyEnforcerConfig = JsonSerialization.readValue(
|
||||||
PolicyEnforcerConfig policyEnforcerConfig = JsonSerialization.readValue(
|
javaClass.getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig::class.java
|
||||||
getClass().getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig.class);
|
)
|
||||||
policyEnforcerConfig.setCredentials(Map.of("secret", clientSecret));
|
policyEnforcerConfig.credentials = mapOf(Pair("secret", clientSecret))
|
||||||
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
|
if (request.method == HttpMethod.OPTIONS.name()) {
|
||||||
// always allow options request
|
// always allow options request
|
||||||
policyEnforcerConfig.setEnforcementMode(EnforcementMode.DISABLED);
|
policyEnforcerConfig.enforcementMode = EnforcementMode.DISABLED
|
||||||
} else {
|
} else {
|
||||||
policyEnforcerConfig.setPaths(PATHS);
|
policyEnforcerConfig.paths = PATHS
|
||||||
|
}
|
||||||
|
policyEnforcerConfig
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return policyEnforcerConfig;
|
companion object {
|
||||||
} catch (IOException e) {
|
private val PERMITTED_PATHS: Collection<String> = listOf(
|
||||||
throw new RuntimeException(e);
|
"/wahlrecht/v1/healthCheck",
|
||||||
}
|
"/actuator/**",
|
||||||
}
|
"/wahlrecht/v1/doc/**",
|
||||||
});
|
"/wahlrecht/v1/api-docs/**",
|
||||||
}
|
"/error"
|
||||||
|
)
|
||||||
|
private val PATHS = buildPathConfigs()
|
||||||
|
|
||||||
@NonNull
|
private fun buildPathConfigs(): List<PathConfig> {
|
||||||
private static List<PathConfig> buildPathConfigs() {
|
val paths: MutableList<PathConfig> = mutableListOf()
|
||||||
List<PathConfig> paths = new ArrayList<>();
|
for (path in PERMITTED_PATHS) {
|
||||||
for (String path : PERMITTED_PATHS) {
|
val pathConfig = PathConfig()
|
||||||
PathConfig pathConfig = new PathConfig();
|
pathConfig.path = path.replace("**", "*")
|
||||||
pathConfig.setPath(path.replace("**", "*"));
|
pathConfig.enforcementMode = EnforcementMode.DISABLED
|
||||||
pathConfig.setEnforcementMode(EnforcementMode.DISABLED);
|
paths.add(pathConfig)
|
||||||
paths.add(pathConfig);
|
}
|
||||||
|
return paths
|
||||||
}
|
}
|
||||||
return paths;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-44
@@ -1,61 +1,58 @@
|
|||||||
package de.twomartens.wahlrecht.controller.v1;
|
package de.twomartens.wahlrecht.controller.v1
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.mapper.v1.ElectedCandidatesMapper;
|
import de.twomartens.wahlrecht.mapper.v1.ElectedCandidatesMapper
|
||||||
import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper;
|
import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates;
|
import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.ElectionResult;
|
import de.twomartens.wahlrecht.model.dto.v1.ElectionResult
|
||||||
import de.twomartens.wahlrecht.service.CalculationService;
|
import de.twomartens.wahlrecht.service.CalculationService
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.mapstruct.factory.Mappers
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.http.MediaType
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequestMapping(value = ["/wahlrecht/v1"])
|
||||||
@RequestMapping(value = "/wahlrecht/v1")
|
|
||||||
@Tag(name = "Calculations", description = "all requests relating to calculations")
|
@Tag(name = "Calculations", description = "all requests relating to calculations")
|
||||||
public class CalculationController {
|
class CalculationController(private val service: CalculationService) {
|
||||||
|
private val electedCandidatesMapper = Mappers.getMapper(
|
||||||
private final ElectedCandidatesMapper electedCandidatesMapper = Mappers.getMapper(
|
ElectedCandidatesMapper::class.java
|
||||||
ElectedCandidatesMapper.class);
|
)
|
||||||
private final ElectionResultMapper electionResultMapper = Mappers.getMapper(
|
private val electionResultMapper = Mappers.getMapper(
|
||||||
ElectionResultMapper.class);
|
ElectionResultMapper::class.java
|
||||||
private final CalculationService service;
|
)
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Calculates a provided election result",
|
summary = "Calculates a provided election result",
|
||||||
description = "This request does not store any result and is idempotent.",
|
description = "This request does not store any result and is idempotent.",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "200",
|
responseCode = "200",
|
||||||
description = "Returns all elected candidates",
|
description = "Returns all elected candidates",
|
||||||
content = {@Content(
|
content = [Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
schema = @Schema(implementation = ElectedCandidates.class))}
|
schema = Schema(implementation = ElectedCandidates::class)
|
||||||
|
)]
|
||||||
|
)]
|
||||||
)
|
)
|
||||||
}
|
@PostMapping(value = ["/calculate"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
)
|
|
||||||
@PostMapping(value = "/calculate", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<ElectedCandidates> calculateResult(
|
fun calculateResult(
|
||||||
@RequestBody ElectionResult electionResult
|
@RequestBody electionResult: ElectionResult
|
||||||
) {
|
): ResponseEntity<ElectedCandidates> {
|
||||||
ElectedCandidates result = electedCandidatesMapper.mapToExternal(
|
val result = electedCandidatesMapper.mapToExternal(
|
||||||
service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult)));
|
service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult))
|
||||||
|
)
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(result);
|
.body(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-71
@@ -1,105 +1,91 @@
|
|||||||
package de.twomartens.wahlrecht.controller.v1;
|
package de.twomartens.wahlrecht.controller.v1
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.mapper.v1.ElectionMapper;
|
import de.twomartens.wahlrecht.mapper.v1.ElectionMapper
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.Election;
|
import de.twomartens.wahlrecht.model.dto.v1.Election
|
||||||
import de.twomartens.wahlrecht.service.ElectionService;
|
import de.twomartens.wahlrecht.service.ElectionService
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
import java.util.Collection;
|
import org.mapstruct.factory.Mappers
|
||||||
import java.util.List;
|
import org.springframework.http.HttpStatus
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.http.MediaType
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.http.ResponseEntity
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequestMapping(value = ["/wahlrecht/v1"])
|
||||||
@RequestMapping(value = "/wahlrecht/v1")
|
|
||||||
@Tag(name = "Elections", description = "all requests relating to elections")
|
@Tag(name = "Elections", description = "all requests relating to elections")
|
||||||
public class ElectionController {
|
class ElectionController(private val service: ElectionService) {
|
||||||
|
private val mapper = Mappers.getMapper(ElectionMapper::class.java)
|
||||||
private final ElectionMapper mapper = Mappers.getMapper(ElectionMapper.class);
|
|
||||||
private final ElectionService service;
|
|
||||||
|
|
||||||
|
@SecurityRequirement(name = "oauth2")
|
||||||
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
|
@GetMapping(value = ["/elections"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Returns all stored elections",
|
summary = "Returns all stored elections",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "200",
|
responseCode = "200",
|
||||||
description = "Returns all stored elections",
|
description = "Returns all stored elections",
|
||||||
content = {@Content(
|
content = [Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
array = @ArraySchema(schema = @Schema(implementation = Election.class)))}
|
array = ArraySchema(
|
||||||
|
schema = Schema(
|
||||||
|
implementation = Election::class
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)
|
)
|
||||||
@GetMapping(value = "/elections", produces = MediaType.APPLICATION_JSON_VALUE)
|
)]
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
)]
|
||||||
@SecurityRequirement(name = "oauth2")
|
)
|
||||||
public ResponseEntity<Collection<Election>> getElections() {
|
fun getElections(): ResponseEntity<Collection<Election>> {
|
||||||
List<Election> elections = service.getElections().stream().map(mapper::mapToExternal).toList();
|
val elections = service.elections.asSequence()
|
||||||
|
.map { mapper.mapToExternal(it.value) }
|
||||||
|
.toList()
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(elections);
|
.body(elections)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Returns election matching provided name",
|
summary = "Returns election matching provided name",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "200",
|
responseCode = "200",
|
||||||
description = "Returns matching election",
|
description = "Returns matching election",
|
||||||
content = {@Content(
|
content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))]
|
||||||
mediaType = "application/json",
|
)]
|
||||||
schema = @Schema(implementation = Election.class))}
|
|
||||||
)
|
)
|
||||||
}
|
@GetMapping(value = ["/elections/by-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
)
|
|
||||||
@GetMapping(value = "/elections/by-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<Election> getElectionByName(
|
fun getElectionByName(
|
||||||
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) {
|
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String?
|
||||||
Election election = mapper.mapToExternal(service.getElection(electionName));
|
): ResponseEntity<Election> {
|
||||||
|
val election = mapper.mapToExternal(service.elections[electionName]!!)
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(election);
|
.body(election)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Stores a new election",
|
summary = "Stores a new election",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "201",
|
responseCode = "201",
|
||||||
description = "Election was created"),
|
description = "Election was created"
|
||||||
@ApiResponse(responseCode = "200",
|
), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse(
|
||||||
description = "Election was modified"),
|
responseCode = "401",
|
||||||
@ApiResponse(responseCode = "401",
|
|
||||||
description = "Unauthorized",
|
description = "Unauthorized",
|
||||||
content = {@Content(
|
content = [Content(mediaType = "plain/text")]
|
||||||
mediaType = "plain/text")}
|
)]
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
@PutMapping("/election")
|
@PutMapping("/election")
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<?> putElection(@RequestBody Election election) {
|
fun putElection(@RequestBody election: Election?): ResponseEntity<*> {
|
||||||
boolean createdNew = service.storeElection(mapper.mapToDB(election));
|
val createdNew = service.storeElection(mapper.mapToDB(election!!))
|
||||||
return createdNew
|
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
|
||||||
? new ResponseEntity<>(HttpStatus.CREATED)
|
|
||||||
: new ResponseEntity<>(HttpStatus.OK);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-63
@@ -1,91 +1,75 @@
|
|||||||
package de.twomartens.wahlrecht.controller.v1;
|
package de.twomartens.wahlrecht.controller.v1
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper;
|
import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper
|
||||||
import de.twomartens.wahlrecht.model.dto.ErrorMessage;
|
import de.twomartens.wahlrecht.model.dto.ErrorMessage
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.ElectionResult;
|
import de.twomartens.wahlrecht.model.dto.v1.ElectionResult
|
||||||
import de.twomartens.wahlrecht.service.ElectionResultService;
|
import de.twomartens.wahlrecht.service.ElectionResultService
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@RequestMapping(value = "/wahlrecht/v1")
|
@RequestMapping(value = ["/wahlrecht/v1"])
|
||||||
@Tag(name = "ElectionResults", description = "all requests relating to election results")
|
@Tag(name = "ElectionResults", description = "all requests relating to election results")
|
||||||
public class ElectionResultController {
|
class ElectionResultController(private val service: ElectionResultService) {
|
||||||
|
private val mapper = Mappers.getMapper(ElectionResultMapper::class.java)
|
||||||
private final ElectionResultMapper mapper = Mappers.getMapper(ElectionResultMapper.class);
|
|
||||||
private final ElectionResultService service;
|
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Returns election result for given election name",
|
summary = "Returns election result for given election name",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "200",
|
responseCode = "200",
|
||||||
description = "Returns stored election result for given election name",
|
description = "Returns stored election result for given election name",
|
||||||
content = {@Content(
|
content = [Content(mediaType = "application/json", schema = Schema(implementation = ElectionResult::class))]
|
||||||
mediaType = "application/json",
|
), ApiResponse(
|
||||||
schema = @Schema(implementation = ElectionResult.class))}
|
responseCode = "404",
|
||||||
),
|
|
||||||
@ApiResponse(responseCode = "404",
|
|
||||||
description = "No election result found",
|
description = "No election result found",
|
||||||
content = {
|
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))]
|
||||||
@Content(
|
)]
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = ErrorMessage.class)
|
|
||||||
)
|
)
|
||||||
}
|
@GetMapping(
|
||||||
|
value = ["/electionResult/by-election-name/{electionName}"],
|
||||||
|
produces = [MediaType.APPLICATION_JSON_VALUE]
|
||||||
)
|
)
|
||||||
}
|
|
||||||
)
|
|
||||||
@GetMapping(value = "/electionResult/by-election-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<ElectionResult> getElectionByName(
|
fun getElectionByName(
|
||||||
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) {
|
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String
|
||||||
ElectionResult result = mapper.mapToExternal(service.getElectionResult(electionName));
|
): ResponseEntity<ElectionResult> {
|
||||||
|
val result = mapper.mapToExternal(service.getElectionResult(electionName))
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(result);
|
.body(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Stores a new election",
|
summary = "Stores a new election",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "201",
|
responseCode = "201",
|
||||||
description = "Election was created"),
|
description = "Election was created"
|
||||||
@ApiResponse(responseCode = "200",
|
), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse(
|
||||||
description = "Election was modified"),
|
responseCode = "401",
|
||||||
@ApiResponse(responseCode = "401",
|
|
||||||
description = "Unauthorized",
|
description = "Unauthorized",
|
||||||
content = {@Content(
|
content = [Content(mediaType = "plain/text")]
|
||||||
mediaType = "plain/text")}
|
)]
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
@PutMapping("/electionResult")
|
@PutMapping("/electionResult")
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<?> putElection(@RequestBody ElectionResult electionResult) {
|
fun putElection(@RequestBody electionResult: ElectionResult?): ResponseEntity<*> {
|
||||||
boolean createdNew = service.storeResult(mapper.mapToDB(electionResult));
|
val createdNew = service.storeResult(mapper.mapToDB(electionResult!!))
|
||||||
return createdNew
|
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
|
||||||
? new ResponseEntity<>(HttpStatus.CREATED)
|
|
||||||
: new ResponseEntity<>(HttpStatus.OK);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-70
@@ -1,95 +1,77 @@
|
|||||||
package de.twomartens.wahlrecht.controller.v1;
|
package de.twomartens.wahlrecht.controller.v1
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.mapper.v1.PartyInElectionMapper;
|
import de.twomartens.wahlrecht.mapper.v1.PartyInElectionMapper
|
||||||
import de.twomartens.wahlrecht.model.dto.ErrorMessage;
|
import de.twomartens.wahlrecht.model.dto.ErrorMessage
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.PartyInElection;
|
import de.twomartens.wahlrecht.model.dto.v1.PartyInElection
|
||||||
import de.twomartens.wahlrecht.service.PartyService;
|
import de.twomartens.wahlrecht.service.PartyService
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse
|
||||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
import io.swagger.v3.oas.annotations.security.SecurityRequirement
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag
|
||||||
import java.util.Collection;
|
import org.mapstruct.factory.Mappers
|
||||||
import java.util.List;
|
import org.springframework.http.HttpStatus
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.http.MediaType
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.http.ResponseEntity
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.springframework.stereotype.Controller
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.web.bind.annotation.*
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PutMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Controller
|
@Controller
|
||||||
@RequiredArgsConstructor
|
@RequestMapping(value = ["/wahlrecht/v1"])
|
||||||
@RequestMapping(value = "/wahlrecht/v1")
|
|
||||||
@Tag(name = "Parties", description = "all requests relating to parties")
|
@Tag(name = "Parties", description = "all requests relating to parties")
|
||||||
public class PartyController {
|
class PartyController(private val service: PartyService) {
|
||||||
|
private val mapper = Mappers.getMapper(PartyInElectionMapper::class.java)
|
||||||
private final PartyInElectionMapper mapper = Mappers.getMapper(PartyInElectionMapper.class);
|
|
||||||
private final PartyService service;
|
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Returns all stored parties for given election name",
|
summary = "Returns all stored parties for given election name",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "200",
|
responseCode = "200",
|
||||||
description = "Returns all stored parties",
|
description = "Returns all stored parties",
|
||||||
content = {@Content(
|
content = [Content(
|
||||||
mediaType = "application/json",
|
mediaType = "application/json",
|
||||||
array = @ArraySchema(schema = @Schema(implementation = PartyInElection.class)))}
|
array = ArraySchema(schema = Schema(implementation = PartyInElection::class))
|
||||||
),
|
)]
|
||||||
@ApiResponse(responseCode = "404",
|
), ApiResponse(
|
||||||
|
responseCode = "404",
|
||||||
description = "No parties found",
|
description = "No parties found",
|
||||||
content = {
|
content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))]
|
||||||
@Content(
|
)]
|
||||||
mediaType = "application/json",
|
|
||||||
schema = @Schema(implementation = ErrorMessage.class)
|
|
||||||
)
|
)
|
||||||
}
|
@GetMapping(value = ["/parties/by-election-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@GetMapping(value = "/parties/by-election-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE)
|
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<Collection<PartyInElection>> getPartiesByElectionName(
|
fun getPartiesByElectionName(
|
||||||
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) {
|
@PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String
|
||||||
List<PartyInElection> parties = service.getPartiesByElectionName(electionName).stream()
|
): ResponseEntity<Collection<PartyInElection>> {
|
||||||
.map(mapper::mapToExternal)
|
val parties = service.getPartiesByElectionName(electionName).stream()
|
||||||
.toList();
|
.map { election: de.twomartens.wahlrecht.model.db.PartyInElection? ->
|
||||||
|
mapper.mapToExternal(
|
||||||
|
election!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.contentType(MediaType.APPLICATION_JSON)
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
.body(parties);
|
.body(parties)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Stores a new party in an election",
|
summary = "Stores a new party in an election",
|
||||||
responses = {
|
responses = [ApiResponse(
|
||||||
@ApiResponse(responseCode = "201",
|
responseCode = "201",
|
||||||
description = "Party was created"),
|
description = "Party was created"
|
||||||
@ApiResponse(responseCode = "200",
|
), ApiResponse(responseCode = "200", description = "Party was modified"), ApiResponse(
|
||||||
description = "Party was modified"),
|
responseCode = "401",
|
||||||
@ApiResponse(responseCode = "401",
|
|
||||||
description = "Unauthorized"
|
description = "Unauthorized"
|
||||||
)
|
)]
|
||||||
}
|
|
||||||
)
|
)
|
||||||
@PutMapping("/party")
|
@PutMapping("/party")
|
||||||
@SecurityRequirement(name = "bearerAuth")
|
@SecurityRequirement(name = "bearerAuth")
|
||||||
@SecurityRequirement(name = "oauth2")
|
@SecurityRequirement(name = "oauth2")
|
||||||
public ResponseEntity<?> putParty(@RequestBody PartyInElection party) {
|
fun putParty(@RequestBody party: PartyInElection?): ResponseEntity<*> {
|
||||||
boolean createdNew = service.storeParty(mapper.mapToDB(party));
|
val createdNew = service.storeParty(mapper.mapToDB(party!!))
|
||||||
return createdNew
|
return if (createdNew) ResponseEntity<Any>(HttpStatus.CREATED) else ResponseEntity<Any>(HttpStatus.OK)
|
||||||
? new ResponseEntity<>(HttpStatus.CREATED)
|
|
||||||
: new ResponseEntity<>(HttpStatus.OK);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -256,7 +256,7 @@ class LoggingInterceptorRest(
|
|||||||
addLogString(stringMapMessage, PARAM_BUSINESS_TYPE, logMessage.businessType)
|
addLogString(stringMapMessage, PARAM_BUSINESS_TYPE, logMessage.businessType)
|
||||||
addLogString(stringMapMessage, PARAM_REQUEST_BODY, cutToMaxLength(logMessage.requestBody))
|
addLogString(stringMapMessage, PARAM_REQUEST_BODY, cutToMaxLength(logMessage.requestBody))
|
||||||
addLogString(stringMapMessage, PARAM_RESPONSE_BODY, cutToMaxLength(logMessage.responseBody))
|
addLogString(stringMapMessage, PARAM_RESPONSE_BODY, cutToMaxLength(logMessage.responseBody))
|
||||||
log.debug(MARKER, stringMapMessage.toString(), logMessage.throwable)
|
log.debug(MARKER, stringMapMessage.asString(), logMessage.throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDurationBetweenRequestAndResponseTime(logMessage: LogMessage): Duration {
|
private fun getDurationBetweenRequestAndResponseTime(logMessage: LogMessage): Duration {
|
||||||
|
|||||||
@@ -1,44 +1,24 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import java.time.Instant;
|
import org.bson.types.ObjectId
|
||||||
import lombok.AccessLevel;
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import lombok.AllArgsConstructor;
|
import org.springframework.data.annotation.Id
|
||||||
import lombok.Builder;
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import lombok.EqualsAndHashCode;
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
import lombok.Getter;
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import lombok.NoArgsConstructor;
|
import java.time.Instant
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@Getter
|
data class Candidate(
|
||||||
@Setter
|
@Indexed var name: String,
|
||||||
@Builder
|
var profession: String
|
||||||
@EqualsAndHashCode
|
){
|
||||||
@ToString
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class Candidate {
|
|
||||||
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@Id
|
@Id
|
||||||
ObjectId id;
|
var id: ObjectId = ObjectId()
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@CreatedDate
|
|
||||||
Instant created;
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@LastModifiedDate
|
|
||||||
Instant lastModified;
|
|
||||||
|
|
||||||
@Indexed
|
@CreatedDate
|
||||||
String name;
|
lateinit var created: Instant
|
||||||
String profession;
|
|
||||||
|
@LastModifiedDate
|
||||||
|
lateinit var lastModified: Instant
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,27 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import java.time.Instant;
|
import org.bson.types.ObjectId
|
||||||
import lombok.AccessLevel;
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import lombok.AllArgsConstructor;
|
import org.springframework.data.annotation.Id
|
||||||
import lombok.Builder;
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import lombok.EqualsAndHashCode;
|
import org.springframework.data.mongodb.core.index.CompoundIndex
|
||||||
import lombok.Getter;
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import lombok.NoArgsConstructor;
|
import java.time.Instant
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@ToString
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
@CompoundIndex(def = "{'number': 1, 'electionName': 1}")
|
@CompoundIndex(def = "{'number': 1, 'electionName': 1}")
|
||||||
public class Constituency {
|
class Constituency(
|
||||||
|
var number: Int = 0,
|
||||||
@EqualsAndHashCode.Exclude
|
var electionName: String,
|
||||||
|
var name: String,
|
||||||
|
var numberOfSeats: Int = 0
|
||||||
|
) {
|
||||||
@Id
|
@Id
|
||||||
ObjectId id;
|
var id: ObjectId = ObjectId()
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@CreatedDate
|
|
||||||
Instant created;
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@LastModifiedDate
|
|
||||||
Instant lastModified;
|
|
||||||
|
|
||||||
int number;
|
@CreatedDate
|
||||||
String electionName;
|
lateinit var created: Instant
|
||||||
String name;
|
|
||||||
int numberOfSeats;
|
@LastModifiedDate
|
||||||
|
lateinit var lastModified: Instant
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +1,30 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.dto.v1.VotingThreshold;
|
import de.twomartens.wahlrecht.model.dto.v1.VotingThreshold
|
||||||
import java.time.Instant;
|
import org.bson.types.ObjectId
|
||||||
import java.time.LocalDate;
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import java.util.Collection;
|
import org.springframework.data.annotation.Id
|
||||||
import lombok.AccessLevel;
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import lombok.AllArgsConstructor;
|
import org.springframework.data.mongodb.core.index.Indexed
|
||||||
import lombok.Builder;
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import lombok.EqualsAndHashCode;
|
import java.time.Instant
|
||||||
import lombok.Getter;
|
import java.time.LocalDate
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
|
||||||
import org.springframework.data.mongodb.core.index.Indexed;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@Getter
|
data class Election(
|
||||||
@Setter
|
@Indexed(unique = true) var name: String,
|
||||||
@Builder
|
var day: LocalDate,
|
||||||
@EqualsAndHashCode
|
var votingThreshold: VotingThreshold,
|
||||||
@ToString
|
var totalNumberOfSeats: Int,
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
var constituencies: Collection<Constituency>
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
) {
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class Election {
|
|
||||||
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@Id
|
@Id
|
||||||
ObjectId id;
|
var id: ObjectId = ObjectId()
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@CreatedDate
|
@CreatedDate
|
||||||
Instant created;
|
lateinit var created: Instant
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
Instant lastModified;
|
lateinit var lastModified: Instant
|
||||||
|
|
||||||
@Indexed(unique = true)
|
|
||||||
String name;
|
|
||||||
LocalDate day;
|
|
||||||
VotingThreshold votingThreshold;
|
|
||||||
int totalNumberOfSeats;
|
|
||||||
Collection<Constituency> constituencies;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,24 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import java.time.Instant;
|
import org.bson.types.ObjectId
|
||||||
import java.util.Collection;
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import java.util.Map;
|
import org.springframework.data.annotation.Id
|
||||||
import lombok.AccessLevel;
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import lombok.AllArgsConstructor;
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import lombok.Builder;
|
import java.time.Instant
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@Getter
|
data class ElectionResult(
|
||||||
@Setter
|
var electionName: String,
|
||||||
@Builder
|
var overallResults: Collection<VotingResult>,
|
||||||
@EqualsAndHashCode
|
var constituencyResults: Map<Int, Collection<VotingResult>>
|
||||||
@ToString
|
) {
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class ElectionResult {
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@Id
|
@Id
|
||||||
ObjectId id;
|
var id: ObjectId = ObjectId()
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@CreatedDate
|
|
||||||
Instant created;
|
|
||||||
@EqualsAndHashCode.Exclude
|
|
||||||
@LastModifiedDate
|
|
||||||
Instant lastModified;
|
|
||||||
|
|
||||||
String electionName;
|
@CreatedDate
|
||||||
Collection<VotingResult> overallResults;
|
lateinit var created: Instant
|
||||||
Map<Integer, Collection<VotingResult>> constituencyResults;
|
|
||||||
|
@LastModifiedDate
|
||||||
|
lateinit var lastModified: Instant
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,31 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import java.time.Instant;
|
import lombok.EqualsAndHashCode
|
||||||
import java.util.Map;
|
import org.bson.types.ObjectId
|
||||||
import lombok.AccessLevel;
|
import org.springframework.data.annotation.CreatedDate
|
||||||
import lombok.AllArgsConstructor;
|
import org.springframework.data.annotation.Id
|
||||||
import lombok.Builder;
|
import org.springframework.data.annotation.LastModifiedDate
|
||||||
import lombok.EqualsAndHashCode;
|
import org.springframework.data.mongodb.core.index.CompoundIndex
|
||||||
import lombok.Getter;
|
import org.springframework.data.mongodb.core.mapping.Document
|
||||||
import lombok.NoArgsConstructor;
|
import java.time.Instant
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
import org.bson.types.ObjectId;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.annotation.Id;
|
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
|
||||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
|
||||||
import org.springframework.data.mongodb.core.mapping.Document;
|
|
||||||
|
|
||||||
@Document
|
@Document
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@ToString
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
@CompoundIndex(def = "{'abbreviation': 1, 'electionName': 1}")
|
@CompoundIndex(def = "{'abbreviation': 1, 'electionName': 1}")
|
||||||
public class PartyInElection {
|
data class PartyInElection(
|
||||||
|
var abbreviation: String,
|
||||||
@EqualsAndHashCode.Exclude
|
var electionName: String,
|
||||||
|
var name: String,
|
||||||
|
var overallNomination: Nomination,
|
||||||
|
var constituencyNominations: Map<Int, Nomination?>
|
||||||
|
) {
|
||||||
@Id
|
@Id
|
||||||
ObjectId id;
|
var id: ObjectId = ObjectId()
|
||||||
|
|
||||||
@EqualsAndHashCode.Exclude
|
@EqualsAndHashCode.Exclude
|
||||||
@CreatedDate
|
@CreatedDate
|
||||||
Instant created;
|
lateinit var created: Instant
|
||||||
|
|
||||||
@EqualsAndHashCode.Exclude
|
@EqualsAndHashCode.Exclude
|
||||||
@LastModifiedDate
|
@LastModifiedDate
|
||||||
Instant lastModified;
|
lateinit var lastModified: Instant
|
||||||
|
|
||||||
String abbreviation;
|
|
||||||
String electionName;
|
|
||||||
String name;
|
|
||||||
Nomination overallNomination;
|
|
||||||
Map<Integer, Nomination> constituencyNominations;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,10 @@
|
|||||||
package de.twomartens.wahlrecht.model.db;
|
package de.twomartens.wahlrecht.model.db
|
||||||
|
|
||||||
import java.util.Map;
|
data class VotingResult(
|
||||||
import lombok.AccessLevel;
|
var electionName: String,
|
||||||
import lombok.AllArgsConstructor;
|
var partyAbbreviation: String,
|
||||||
import lombok.Builder;
|
var nominationName: String,
|
||||||
import lombok.EqualsAndHashCode;
|
var votesOnNomination: Int = 0,
|
||||||
import lombok.Getter;
|
var votesThroughHealing: Int = 0,
|
||||||
import lombok.NoArgsConstructor;
|
var votesPerPosition: Map<Int, Int>
|
||||||
import lombok.Setter;
|
)
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@Builder
|
|
||||||
@EqualsAndHashCode
|
|
||||||
@ToString
|
|
||||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
|
||||||
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
|
||||||
public class VotingResult {
|
|
||||||
String electionName;
|
|
||||||
String partyAbbreviation;
|
|
||||||
String nominationName;
|
|
||||||
int votesOnNomination;
|
|
||||||
int votesThroughHealing;
|
|
||||||
Map<Integer, Integer> votesPerPosition;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
public record Candidate(String name, String profession) {
|
data class Candidate(val name: String, val profession: String)
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-6
@@ -1,9 +1,7 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
public record Constituency(String electionName, int number, String name, int numberOfSeats) {
|
data class Constituency(val electionName: String, val number: Int, val name: String, val numberOfSeats: Int) {
|
||||||
|
override fun toString(): String {
|
||||||
@Override
|
return name
|
||||||
public String toString() {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -1,5 +1,3 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
public record ConstituencyId(String electionName, Integer number) {
|
data class ConstituencyId(val electionName: String, val number: Int)
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
public enum Elected {
|
enum class Elected {
|
||||||
NOT_ELECTED,
|
NOT_ELECTED,
|
||||||
CONSTITUENCY,
|
CONSTITUENCY,
|
||||||
OVERALL_NOMINATION_ORDER,
|
OVERALL_NOMINATION_ORDER,
|
||||||
|
|||||||
+4
-11
@@ -1,13 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
|
data class ElectedCandidate(val candidate: Candidate, val elected: Elected) {
|
||||||
public record ElectedCandidate(Candidate candidate, Elected elected) {
|
val name by candidate::name
|
||||||
|
val profession by candidate::profession
|
||||||
public String name() {
|
|
||||||
return candidate.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String profession() {
|
|
||||||
return candidate.profession();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-8
@@ -1,9 +1,7 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
import java.util.Collection;
|
data class ElectionResult(
|
||||||
import java.util.Map;
|
val electionName: String,
|
||||||
|
val overallResults: Collection<VotingResult>,
|
||||||
public record ElectionResult(String electionName, Collection<VotingResult> overallResults,
|
val constituencyResults: Map<Int, Collection<VotingResult>>
|
||||||
Map<Integer, Collection<VotingResult>> constituencyResults) {
|
)
|
||||||
|
|
||||||
}
|
|
||||||
@@ -23,8 +23,8 @@ data class Nomination(val id: NominationId, val supportVotesOnNomination: Boolea
|
|||||||
return positionToCandidate.values.toList()
|
return positionToCandidate.values.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCandidate(position: Int): Candidate? {
|
fun getCandidate(position: Int): Candidate {
|
||||||
return positionToCandidate[position]
|
return positionToCandidate[position]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun supportsVotesOnNomination(): Boolean {
|
fun supportsVotesOnNomination(): Boolean {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
package de.twomartens.wahlrecht.model.internal;
|
package de.twomartens.wahlrecht.model.internal
|
||||||
|
|
||||||
public record PartyId(String electionName, String abbreviation) {
|
data class PartyId(val electionName: String, val abbreviation: String)
|
||||||
|
|
||||||
}
|
|
||||||
+57
-58
@@ -1,84 +1,83 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.actuator;
|
package de.twomartens.wahlrecht.monitoring.actuator
|
||||||
|
|
||||||
import java.io.Closeable;
|
import mu.KotlinLogging
|
||||||
import java.io.IOException;
|
import org.slf4j.MDC
|
||||||
import java.time.Clock;
|
import org.springframework.boot.actuate.health.Health
|
||||||
import java.time.Duration;
|
import org.springframework.boot.actuate.health.HealthIndicator
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import java.io.IOException
|
||||||
import org.slf4j.MDC;
|
import java.time.Clock
|
||||||
import org.slf4j.MDC.MDCCloseable;
|
import java.time.Duration
|
||||||
import org.springframework.boot.actuate.health.Health;
|
import java.util.*
|
||||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
|
||||||
import org.springframework.boot.actuate.health.Status;
|
|
||||||
|
|
||||||
@Slf4j
|
abstract class AbstractHealthIndicator(
|
||||||
@RequiredArgsConstructor
|
private val clock: Clock,
|
||||||
public abstract class AbstractHealthIndicator implements HealthIndicator {
|
private val preparable: Preparable
|
||||||
|
) : HealthIndicator {
|
||||||
|
|
||||||
public static final String HOST = "localhost";
|
private val logStatusDownMessage = "health indicator '${indicatorName()}' invoked with status '${Status.DOWN.code}'"
|
||||||
public static final String HTTP_PREFIX = "http://";
|
private val logStatusUpMessage = "health indicator '${indicatorName()}' invoked with status '${Status.UP.code}'"
|
||||||
public static final String HOST_PORT_SEPERATOR = ":";
|
|
||||||
public static final String PATH_SEPERATOR = "/";
|
|
||||||
public static final String PARAMETER_SEPERATOR = "?";
|
|
||||||
public static final String DETAIL_ENDPOINT_KEY = "endpoint";
|
|
||||||
|
|
||||||
private final String logStatusDownMessage = String.format("health indicator '%s' invoked with status '%s'",
|
private var firstTime = true
|
||||||
indicatorName(), Status.DOWN.getCode());
|
|
||||||
private final String logStatusUpMessage = String.format("health indicator '%s' invoked with status '%s'",
|
|
||||||
indicatorName(), Status.UP.getCode());
|
|
||||||
private final Clock clock;
|
|
||||||
private final Preparable preparable;
|
|
||||||
private boolean firstTime = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main method that determines the health of the service
|
* main method that determines the health of the service
|
||||||
*/
|
*/
|
||||||
protected abstract Health determineHealth();
|
protected abstract fun determineHealth(): Health
|
||||||
|
|
||||||
@Override
|
override fun health(): Health {
|
||||||
public Health health() {
|
|
||||||
try (Closeable ignored = preparable.prepare()) {
|
|
||||||
Health result = null;
|
|
||||||
Exception exception = null;
|
|
||||||
long start = clock.millis();
|
|
||||||
try {
|
try {
|
||||||
result = determineHealth();
|
preparable.prepare().use {
|
||||||
} catch (RuntimeException e) {
|
var result: Health? = null
|
||||||
exception = e;
|
var exception: Exception? = null
|
||||||
result = Health.down().withException(e).build();
|
val start = clock.millis()
|
||||||
|
try {
|
||||||
|
result = determineHealth()
|
||||||
|
} catch (e: RuntimeException) {
|
||||||
|
exception = e
|
||||||
|
result = Health.down().withException(e).build()
|
||||||
} finally {
|
} finally {
|
||||||
logInvocation(result, exception, start, clock.millis());
|
logInvocation(result, exception, start, clock.millis())
|
||||||
}
|
}
|
||||||
return result;
|
return result!!
|
||||||
} catch (IOException e) {
|
}
|
||||||
log.error("unexpected exception occurred", e);
|
} catch (e: IOException) {
|
||||||
return Health.down(e).build();
|
log.error("unexpected exception occurred", e)
|
||||||
|
return Health.down(e).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logInvocation(Health health, Exception exception, long start, long end) {
|
private fun logInvocation(health: Health?, exception: Exception?, start: Long, end: Long) {
|
||||||
Duration duration = Duration.ofMillis(end - start);
|
val duration = Duration.ofMillis(end - start)
|
||||||
try (MDCCloseable ignored = MDC.putCloseable("event.duration", Long.toString(duration.toNanos()))) {
|
MDC.putCloseable("event.duration", duration.toNanos().toString()).use {
|
||||||
if (exception != null || health == null) {
|
if (exception != null || health == null) {
|
||||||
log.error(logStatusDownMessage, exception);
|
log.error(logStatusDownMessage, exception)
|
||||||
firstTime = true;
|
firstTime = true
|
||||||
} else if (health.getStatus() == Status.DOWN) {
|
} else if (health.status === Status.DOWN) {
|
||||||
log.warn(logStatusDownMessage);
|
log.warn(logStatusDownMessage)
|
||||||
firstTime = true;
|
firstTime = true
|
||||||
} else if (firstTime) {
|
} else if (firstTime) {
|
||||||
log.info(logStatusUpMessage);
|
log.info(logStatusUpMessage)
|
||||||
firstTime = false;
|
firstTime = false
|
||||||
} else {
|
} else {
|
||||||
log.trace(logStatusUpMessage);
|
log.trace(logStatusUpMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String indicatorName() {
|
private fun indicatorName(): String {
|
||||||
return this.getClass().getSimpleName()
|
return this.javaClass.getSimpleName()
|
||||||
.replace("HealthIndicator", "")
|
.replace("HealthIndicator", "")
|
||||||
.toLowerCase();
|
.lowercase(Locale.getDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val HOST = "localhost"
|
||||||
|
const val HTTP_PREFIX = "http://"
|
||||||
|
const val HOST_PORT_SEPARATOR = ":"
|
||||||
|
const val PATH_SEPARATOR = "/"
|
||||||
|
const val PARAMETER_SEPARATOR = "?"
|
||||||
|
const val DETAIL_ENDPOINT_KEY = "endpoint"
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-31
@@ -1,37 +1,30 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.actuator;
|
package de.twomartens.wahlrecht.monitoring.actuator
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe;
|
import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe
|
||||||
import java.time.Clock;
|
import org.springframework.boot.actuate.health.Health
|
||||||
import java.util.Optional;
|
import org.springframework.boot.actuate.health.HealthIndicator
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import java.time.Clock
|
||||||
import org.springframework.boot.actuate.health.Health;
|
|
||||||
import org.springframework.boot.actuate.health.Health.Builder;
|
|
||||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
|
||||||
|
|
||||||
@Slf4j
|
abstract class AbstractStatusProbeHealthIndicator(
|
||||||
public abstract class AbstractStatusProbeHealthIndicator extends AbstractHealthIndicator
|
timeProvider: Clock, headerInterceptor: Preparable,
|
||||||
implements HealthIndicator {
|
private val statusProbe: StatusProbe
|
||||||
|
) : AbstractHealthIndicator(timeProvider, headerInterceptor), HealthIndicator {
|
||||||
public static final String MESSAGE_KEY = "message";
|
override fun determineHealth(): Health {
|
||||||
public static final String LAST_STATUS_CHANGE_KEY = "lastStatusChange";
|
val healthBuilder = Health.status(statusProbe.status)
|
||||||
|
if (statusProbe.lastStatusChange != null) {
|
||||||
private final StatusProbe statusProbe;
|
healthBuilder.withDetail(LAST_STATUS_CHANGE_KEY, statusProbe.lastStatusChange)
|
||||||
|
}
|
||||||
public AbstractStatusProbeHealthIndicator(Clock timeProvider, Preparable headerInterceptor,
|
if (statusProbe.throwable != null) {
|
||||||
StatusProbe statusProbe) {
|
healthBuilder.withException(statusProbe.throwable)
|
||||||
super(timeProvider, headerInterceptor);
|
}
|
||||||
this.statusProbe = statusProbe;
|
if (statusProbe.message != null) {
|
||||||
|
healthBuilder.withDetail(MESSAGE_KEY, statusProbe.message)
|
||||||
|
}
|
||||||
|
return healthBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
companion object {
|
||||||
protected Health determineHealth() {
|
const val MESSAGE_KEY = "message"
|
||||||
Builder healthBuilder = Health.status(statusProbe.getStatus());
|
const val LAST_STATUS_CHANGE_KEY = "lastStatusChange"
|
||||||
Optional.ofNullable(statusProbe.getLastStatusChange())
|
|
||||||
.ifPresent(l -> healthBuilder.withDetail(LAST_STATUS_CHANGE_KEY, l));
|
|
||||||
Optional.ofNullable(statusProbe.getThrowable()).ifPresent(healthBuilder::withException);
|
|
||||||
Optional.ofNullable(statusProbe.getMessage())
|
|
||||||
.ifPresent(m -> healthBuilder.withDetail(MESSAGE_KEY, m));
|
|
||||||
return healthBuilder.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-7
@@ -1,10 +1,7 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.actuator;
|
package de.twomartens.wahlrecht.monitoring.actuator
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface Preparable {
|
|
||||||
|
|
||||||
Closeable prepare();
|
|
||||||
|
|
||||||
|
fun interface Preparable {
|
||||||
|
fun prepare(): Closeable
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-44
@@ -1,66 +1,56 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.actuator;
|
package de.twomartens.wahlrecht.monitoring.actuator
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest;
|
import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.boot.actuate.health.Health
|
||||||
import org.springframework.boot.actuate.health.Health;
|
import org.springframework.boot.actuate.health.HealthIndicator
|
||||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import org.springframework.boot.actuate.health.Status;
|
import org.springframework.boot.autoconfigure.web.ServerProperties
|
||||||
import org.springframework.boot.autoconfigure.web.ServerProperties;
|
import org.springframework.stereotype.Component
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.web.client.RestTemplate
|
||||||
import org.springframework.stereotype.Component;
|
import java.security.SecureRandom
|
||||||
import org.springframework.web.client.RestTemplate;
|
import java.time.Clock
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.time.Clock;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Health check which checks if the rest services are working.
|
* A Health check which checks if the rest services are working.
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* If you have a complex service, you should think about an easy greeting or echo service, which
|
* If you have a complex service, you should think about an easy greeting or echo service, which
|
||||||
* only tests the
|
* only tests the
|
||||||
* network/service stack and not the full application.
|
* network/service stack and not the full application.
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* The health check will be called by kubernetes to check if the container/pod should be in load
|
* The health check will be called by kubernetes to check if the container/pod should be in load
|
||||||
* balancing. It is possible
|
* balancing. It is possible
|
||||||
* to have as much health checks as you like.
|
* to have as much health checks as you like.
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* There should be a health check which is ok not before all data is loaded.
|
* There should be a health check which is ok not before all data is loaded.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
@Component
|
@Component
|
||||||
public class RestHealthIndicator extends AbstractHealthIndicator implements HealthIndicator {
|
class RestHealthIndicator(
|
||||||
|
clock: Clock, interceptor: HeaderInterceptorRest,
|
||||||
private static final String URL_PATH = "/wahlrecht/v1/healthCheck";
|
serverProperties: ServerProperties, private val restTemplateRestHealthIndicator: RestTemplate
|
||||||
private static final String GET_PARAMETER = "message=";
|
) : AbstractHealthIndicator(clock, Preparable { interceptor.markAsHealthCheck() }), HealthIndicator {
|
||||||
|
private val randomizer = SecureRandom()
|
||||||
private final SecureRandom randomizer = new SecureRandom();
|
private val urlPrefix: String = (HTTP_PREFIX + HOST + HOST_PORT_SEPARATOR
|
||||||
private final RestTemplate restTemplateRestHealthIndicator;
|
+ serverProperties.port
|
||||||
private final String urlPrefix;
|
+ URL_PATH + PARAMETER_SEPARATOR + GET_PARAMETER)
|
||||||
|
|
||||||
public RestHealthIndicator(Clock clock, HeaderInterceptorRest interceptor,
|
|
||||||
ServerProperties serverProperties, RestTemplate restTemplateRestHealthIndicator) {
|
|
||||||
super(clock, interceptor::markAsHealthCheck);
|
|
||||||
this.restTemplateRestHealthIndicator = restTemplateRestHealthIndicator;
|
|
||||||
urlPrefix = HTTP_PREFIX + HOST + HOST_PORT_SEPERATOR + serverProperties.getPort()
|
|
||||||
+ URL_PATH + PARAMETER_SEPERATOR + GET_PARAMETER;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main method that determines the health of the service
|
* main method that determines the health of the service
|
||||||
*/
|
*/
|
||||||
@Override
|
override fun determineHealth(): Health {
|
||||||
protected Health determineHealth() {
|
val random = randomizer.nextInt(100000, 999999).toString()
|
||||||
String random = Integer.toString(randomizer.nextInt(100000, 999999));
|
val url = "$urlPrefix{random}"
|
||||||
String url = urlPrefix + "{random}";
|
val response = restTemplateRestHealthIndicator.getForEntity(url, String::class.java, random)
|
||||||
ResponseEntity<String> response = restTemplateRestHealthIndicator.getForEntity(url, String.class, random);
|
val status = if (response.body == random) Status.UP else Status.DOWN
|
||||||
Status status = Optional.ofNullable(response.getBody())
|
|
||||||
.filter(random::equals)
|
|
||||||
.map(m -> Status.UP)
|
|
||||||
.orElse(Status.DOWN);
|
|
||||||
return Health.status(status)
|
return Health.status(status)
|
||||||
.withDetail(DETAIL_ENDPOINT_KEY, url)
|
.withDetail(DETAIL_ENDPOINT_KEY, url)
|
||||||
.build();
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val URL_PATH = "/wahlrecht/v1/healthCheck"
|
||||||
|
private const val GET_PARAMETER = "message="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-25
@@ -1,33 +1,25 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import java.time.Clock;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.time.Clock
|
||||||
import org.springframework.boot.actuate.health.Status;
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
public class CountBasedStatusProbe extends StatusProbe {
|
class CountBasedStatusProbe(
|
||||||
|
private val maxFailureCount: Int, clock: Clock, criticality: StatusProbeCriticality, name: String,
|
||||||
|
statusProbeLogger: StatusProbeLogger
|
||||||
|
) : StatusProbe(clock, criticality, name, statusProbeLogger) {
|
||||||
|
private val failureCount = AtomicInteger(0)
|
||||||
|
|
||||||
private final AtomicInteger failureCount = new AtomicInteger(0);
|
@Synchronized
|
||||||
|
override fun setStatus(status: Status, throwable: Throwable?, message: String?) {
|
||||||
private final int maxFailureCount;
|
if (status === Status.DOWN) {
|
||||||
|
val failureCount = failureCount.incrementAndGet()
|
||||||
public CountBasedStatusProbe(int maxFailureCount, Clock clock, StatusProbeCriticality criticality, String name,
|
|
||||||
StatusProbeLogger statusProbeLogger) {
|
|
||||||
super(clock, criticality, name, statusProbeLogger);
|
|
||||||
this.maxFailureCount = maxFailureCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized void setStatus(Status status, Throwable throwable, String message) {
|
|
||||||
if (status == Status.DOWN) {
|
|
||||||
int failureCount = this.failureCount.incrementAndGet();
|
|
||||||
if (failureCount > maxFailureCount) {
|
if (failureCount > maxFailureCount) {
|
||||||
super.setStatus(status, throwable, message);
|
super.setStatus(status, throwable, message)
|
||||||
}
|
}
|
||||||
} else if (status == Status.UP) {
|
} else if (status === Status.UP) {
|
||||||
this.failureCount.set(0);
|
failureCount.set(0)
|
||||||
super.setStatus(status, throwable, message);
|
super.setStatus(status, throwable, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-45
@@ -1,63 +1,51 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import java.time.Clock;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import java.time.Duration;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
import org.springframework.boot.actuate.health.Status;
|
import java.time.Clock
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
import java.time.Duration
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* uses the percentage of down statuses within a given period (default: 1 min) to determine if status of probe is down.
|
* uses the percentage of down statuses within a given period (default: 1 min) to determine if status of probe is down.
|
||||||
* this is meant to be used to avoid flickering status probes on services that have lots of status updates. When there
|
* this is meant to be used to avoid flickering status probes on services that have lots of status updates. When there
|
||||||
* is no significant amount of requests during one scheduling period, the behavior may be arbitrary.
|
* is no significant amount of requests during one scheduling period, the behavior may be arbitrary.
|
||||||
*/
|
*/
|
||||||
public class PercentageBasedStatusProbe extends StatusProbe implements ScheduledStatusProbe {
|
class PercentageBasedStatusProbe(
|
||||||
|
private val maxFailurePercent: Int, clock: Clock,
|
||||||
|
threadPoolTaskScheduler: ThreadPoolTaskScheduler, schedulePeriod: Duration, criticality: StatusProbeCriticality,
|
||||||
|
name: String, statusProbeLogger: StatusProbeLogger
|
||||||
|
) : StatusProbe(clock, criticality, name, statusProbeLogger), ScheduledStatusProbe {
|
||||||
|
private var requestCount = 0
|
||||||
|
private var downCount = 0
|
||||||
|
private var temporaryMessage: String? = null
|
||||||
|
private var temporaryThrowable: Throwable? = null
|
||||||
|
|
||||||
private final int maxFailurePercent;
|
init {
|
||||||
|
scheduleTask(threadPoolTaskScheduler, schedulePeriod)
|
||||||
private int requestCount = 0;
|
|
||||||
private int downCount = 0;
|
|
||||||
private Throwable throwable;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
public PercentageBasedStatusProbe(int maxFailurePercent, Clock clock,
|
|
||||||
ThreadPoolTaskScheduler threadPoolTaskScheduler, StatusProbeCriticality criticality, String name,
|
|
||||||
StatusProbeLogger statusProbeLogger) {
|
|
||||||
this(maxFailurePercent, clock, threadPoolTaskScheduler, Duration.ofMinutes(1), criticality, name,
|
|
||||||
statusProbeLogger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PercentageBasedStatusProbe(int maxFailurePercent, Clock clock,
|
@Synchronized
|
||||||
ThreadPoolTaskScheduler threadPoolTaskScheduler, Duration schedulePeriod, StatusProbeCriticality criticality,
|
override fun setStatus(status: Status, throwable: Throwable?, message: String?) {
|
||||||
String name, StatusProbeLogger statusProbeLogger) {
|
if (status === Status.DOWN) {
|
||||||
super(clock, criticality, name, statusProbeLogger);
|
downCount++
|
||||||
this.maxFailurePercent = maxFailurePercent;
|
this.temporaryThrowable = throwable
|
||||||
scheduleTask(threadPoolTaskScheduler, schedulePeriod);
|
this.temporaryMessage = message
|
||||||
|
}
|
||||||
|
requestCount++
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private fun reset() {
|
||||||
protected synchronized void setStatus(Status status, Throwable throwable, String message) {
|
requestCount = 0
|
||||||
if (status == Status.DOWN) {
|
downCount = 0
|
||||||
downCount++;
|
|
||||||
this.throwable = throwable;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
requestCount++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset() {
|
@Synchronized
|
||||||
requestCount = 0;
|
override fun runScheduledTask() {
|
||||||
downCount = 0;
|
if (requestCount > 0 && downCount * 100.0 / requestCount > maxFailurePercent) {
|
||||||
throwable = null;
|
super.setStatus(Status.DOWN, temporaryThrowable, temporaryMessage)
|
||||||
message = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void runScheduledTask() {
|
|
||||||
if (requestCount > 0 && (downCount * 100.0 / requestCount) > maxFailurePercent) {
|
|
||||||
super.setStatus(Status.DOWN, throwable, message);
|
|
||||||
} else {
|
} else {
|
||||||
super.setStatus(Status.UP, null, null);
|
super.setStatus(Status.UP, null, null)
|
||||||
}
|
}
|
||||||
reset();
|
reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-13
@@ -1,18 +1,22 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import java.time.Duration;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
import org.springframework.scheduling.support.PeriodicTrigger
|
||||||
import org.springframework.scheduling.support.PeriodicTrigger;
|
import java.time.Duration
|
||||||
|
|
||||||
public interface ScheduledStatusProbe {
|
interface ScheduledStatusProbe {
|
||||||
|
fun runScheduledTask()
|
||||||
void runScheduledTask();
|
fun scheduleTask(
|
||||||
|
threadPoolTaskScheduler: ThreadPoolTaskScheduler,
|
||||||
default void scheduleTask(ThreadPoolTaskScheduler threadPoolTaskScheduler,
|
schedulePeriod: Duration
|
||||||
Duration schedulePeriod) {
|
) {
|
||||||
PeriodicTrigger periodicTrigger = new PeriodicTrigger(
|
val periodicTrigger = PeriodicTrigger(
|
||||||
Duration.ofSeconds(schedulePeriod.toSeconds()));
|
Duration.ofSeconds(schedulePeriod.toSeconds())
|
||||||
threadPoolTaskScheduler.schedule(this::runScheduledTask, periodicTrigger);
|
)
|
||||||
|
threadPoolTaskScheduler.schedule(periodicTrigger) { runScheduledTask() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ThreadPoolTaskScheduler.schedule(periodicTrigger: PeriodicTrigger, task: Runnable) {
|
||||||
|
this.schedule(task, periodicTrigger)
|
||||||
}
|
}
|
||||||
+38
-41
@@ -1,62 +1,59 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import java.time.Clock;
|
import lombok.Getter
|
||||||
import java.time.ZonedDateTime;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import lombok.Getter;
|
import java.time.Clock
|
||||||
import org.springframework.boot.actuate.health.Status;
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
public class StatusProbe {
|
open class StatusProbe(
|
||||||
|
private val clock: Clock, criticality: StatusProbeCriticality, private val name: String,
|
||||||
|
private val statusProbeLogger: StatusProbeLogger
|
||||||
|
) {
|
||||||
|
var status: Status = Status.UP
|
||||||
|
private set
|
||||||
|
var throwable: Throwable? = null
|
||||||
|
private set
|
||||||
|
var message: String? = null
|
||||||
|
private set
|
||||||
|
var lastStatusChange: ZonedDateTime? = null
|
||||||
|
private set
|
||||||
|
|
||||||
private final Clock clock;
|
init {
|
||||||
|
statusProbeLogger.registerStatusProbe(name, criticality)
|
||||||
private Status status = Status.UP;
|
|
||||||
private Throwable throwable = null;
|
|
||||||
private String message = null;
|
|
||||||
private ZonedDateTime lastStatusChange;
|
|
||||||
private final StatusProbeLogger statusProbeLogger;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public StatusProbe(Clock clock, StatusProbeCriticality criticality, String name,
|
|
||||||
StatusProbeLogger statusProbeLogger) {
|
|
||||||
this.clock = clock;
|
|
||||||
this.name = name;
|
|
||||||
this.statusProbeLogger = statusProbeLogger;
|
|
||||||
statusProbeLogger.registerStatusProbe(name, criticality);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setStatus(Status status, Throwable throwable, String message) {
|
protected open fun setStatus(status: Status, throwable: Throwable?, message: String?) {
|
||||||
if (status != this.status) {
|
if (status !== this.status) {
|
||||||
lastStatusChange = ZonedDateTime.now(clock);
|
lastStatusChange = ZonedDateTime.now(clock)
|
||||||
statusProbeLogger.logStatusChange(name, message, status, lastStatusChange, throwable);
|
statusProbeLogger.logStatusChange(name, message, status, lastStatusChange, throwable)
|
||||||
}
|
}
|
||||||
this.status = status;
|
this.status = status
|
||||||
this.throwable = throwable;
|
this.throwable = throwable
|
||||||
this.message = message;
|
this.message = message
|
||||||
}
|
}
|
||||||
|
|
||||||
public void up() {
|
fun up() {
|
||||||
setStatus(Status.UP, null, null);
|
setStatus(Status.UP, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void up(String message) {
|
fun up(message: String) {
|
||||||
setStatus(Status.UP, null, message);
|
setStatus(Status.UP, null, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void down() {
|
fun down() {
|
||||||
setStatus(Status.DOWN, null, null);
|
setStatus(Status.DOWN, null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void down(Throwable throwable) {
|
fun down(throwable: Throwable) {
|
||||||
setStatus(Status.DOWN, throwable, null);
|
setStatus(Status.DOWN, throwable, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void down(String message) {
|
fun down(message: String) {
|
||||||
setStatus(Status.DOWN, null, message);
|
setStatus(Status.DOWN, null, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void down(Throwable throwable, String message) {
|
protected fun down(throwable: Throwable, message: String) {
|
||||||
setStatus(Status.DOWN, throwable, message);
|
setStatus(Status.DOWN, throwable, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -1,7 +1,7 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
|
enum class StatusProbeCriticality {
|
||||||
public enum StatusProbeCriticality {
|
K1,
|
||||||
K1, K2, K3
|
K2,
|
||||||
|
K3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+80
-95
@@ -1,124 +1,109 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import static de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality.K1;
|
import org.apache.logging.log4j.LogManager
|
||||||
import static de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality.K2;
|
import org.apache.logging.log4j.Logger
|
||||||
import static de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality.K3;
|
import org.apache.logging.log4j.MarkerManager
|
||||||
|
import org.apache.logging.log4j.message.StringMapMessage
|
||||||
|
import org.springframework.boot.actuate.health.Status
|
||||||
|
import java.time.Clock
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
import java.time.Clock;
|
class StatusProbeLogger internal constructor(private val clock: Clock, private val commLog: Logger) {
|
||||||
import java.time.ZonedDateTime;
|
private val statusProbeToStatus: MutableMap<ProbeIdent, Status> = mutableMapOf()
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.logging.log4j.Marker;
|
|
||||||
import org.apache.logging.log4j.MarkerManager;
|
|
||||||
import org.apache.logging.log4j.message.StringMapMessage;
|
|
||||||
import org.springframework.boot.actuate.health.Status;
|
|
||||||
|
|
||||||
public class StatusProbeLogger {
|
constructor(clock: Clock) : this(clock, LogManager.getLogger("statusprobe"))
|
||||||
|
|
||||||
private static final Marker MARKER = MarkerManager.getMarker("statusprobe");
|
fun registerStatusProbe(name: String, criticality: StatusProbeCriticality) {
|
||||||
|
statusProbeToStatus[ProbeIdent(name, criticality)] = Status.UP
|
||||||
private static final String LABEL_CRITICALITY = "label.status.criticality";
|
logStatusChange(name, "Startup", Status.UP, ZonedDateTime.now(clock), null)
|
||||||
private static final String LABEL_STATUS = "label.status.status";
|
|
||||||
private static final String LABEL_REASON = "label.status.reason";
|
|
||||||
private static final String LABEL_MESSAGE = "label.status.description";
|
|
||||||
private static final String LABEL_LAST_STATUS_CHANGE = "label.status.last_change";
|
|
||||||
|
|
||||||
private final Clock clock;
|
|
||||||
private final Logger commLog;
|
|
||||||
private final Map<ProbeIdent, Status> statusProbeToStatus = new HashMap<>();
|
|
||||||
|
|
||||||
public StatusProbeLogger(Clock clock) {
|
|
||||||
this(clock, LogManager.getLogger("statusprobe"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun logStatusChange(
|
||||||
* only for testing purposes
|
name: String, message: String?, status: Status, lastStatusChange: ZonedDateTime?,
|
||||||
*/
|
throwable: Throwable?
|
||||||
StatusProbeLogger(Clock clock, Logger commLog) {
|
) {
|
||||||
this.clock = clock;
|
var probeIdent = getProbeIdent(name)
|
||||||
this.commLog = commLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerStatusProbe(String name, StatusProbeCriticality criticality) {
|
|
||||||
statusProbeToStatus.put(new ProbeIdent(name, criticality), Status.UP);
|
|
||||||
logStatusChange(name, "Startup", Status.UP, ZonedDateTime.now(clock), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logStatusChange(String name, String message, Status status, ZonedDateTime lastStatusChange,
|
|
||||||
Throwable throwable) {
|
|
||||||
ProbeIdent probeIdent = getProbeIdent(name);
|
|
||||||
if (probeIdent == null) {
|
if (probeIdent == null) {
|
||||||
probeIdent = new ProbeIdent(name, K1);
|
probeIdent = ProbeIdent(name, StatusProbeCriticality.K1)
|
||||||
}
|
}
|
||||||
statusProbeToStatus.put(probeIdent, status);
|
statusProbeToStatus[probeIdent] = status
|
||||||
createLog(message, lastStatusChange, throwable);
|
createLog(message, lastStatusChange, throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProbeIdent getProbeIdent(String name) {
|
private fun getProbeIdent(name: String): ProbeIdent? {
|
||||||
return statusProbeToStatus.keySet().stream().filter(key -> key.name.equals(name)).findFirst().orElse(null);
|
return statusProbeToStatus.keys.asSequence()
|
||||||
|
.filter { it.name == name }
|
||||||
|
.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createLog(String message, ZonedDateTime lastStatusChange, Throwable throwable) {
|
private fun createLog(message: String?, lastStatusChange: ZonedDateTime?, throwable: Throwable?) {
|
||||||
Status overallStatus = getOverallStatus();
|
val cleanedMessage = message ?: ""
|
||||||
StatusProbeCriticality criticality = getOverallCriticality();
|
val overallStatus = overallStatus
|
||||||
if (message == null) {
|
val criticality = overallCriticality
|
||||||
message = "";
|
if (Status.UP == overallStatus) {
|
||||||
}
|
commLog.info(
|
||||||
if (Status.UP.equals(overallStatus)) {
|
MARKER, StringMapMessage()
|
||||||
commLog.info(MARKER, new StringMapMessage()
|
|
||||||
.with(LABEL_CRITICALITY, criticality)
|
.with(LABEL_CRITICALITY, criticality)
|
||||||
.with(LABEL_STATUS, overallStatus)
|
.with(LABEL_STATUS, overallStatus)
|
||||||
.with(LABEL_MESSAGE, message)
|
.with(LABEL_MESSAGE, cleanedMessage)
|
||||||
.with(LABEL_LAST_STATUS_CHANGE, lastStatusChange));
|
.with(LABEL_LAST_STATUS_CHANGE, lastStatusChange)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
commLog.error(MARKER, new StringMapMessage()
|
commLog.error(
|
||||||
|
MARKER, StringMapMessage()
|
||||||
.with(LABEL_CRITICALITY, criticality)
|
.with(LABEL_CRITICALITY, criticality)
|
||||||
.with(LABEL_STATUS, overallStatus)
|
.with(LABEL_STATUS, overallStatus)
|
||||||
.with(LABEL_MESSAGE, message)
|
.with(LABEL_MESSAGE, cleanedMessage)
|
||||||
.with(LABEL_REASON, getReason())
|
.with(LABEL_REASON, reason)
|
||||||
.with(LABEL_LAST_STATUS_CHANGE, lastStatusChange), throwable);
|
.with(LABEL_LAST_STATUS_CHANGE, lastStatusChange), throwable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private StatusProbeCriticality getOverallCriticality() {
|
private val overallCriticality: StatusProbeCriticality
|
||||||
List<StatusProbeCriticality> crits = statusProbeToStatus.keySet().stream().map(key -> key.criticality).toList();
|
get() {
|
||||||
return crits.contains(K1) ? K1 : crits.contains(K2) ? K2 : K3;
|
val crits = statusProbeToStatus.keys.asSequence()
|
||||||
|
.map { it.criticality }
|
||||||
|
.toList()
|
||||||
|
return if (crits.contains(StatusProbeCriticality.K1)) StatusProbeCriticality.K1
|
||||||
|
else if (crits.contains(StatusProbeCriticality.K2)) StatusProbeCriticality.K2
|
||||||
|
else StatusProbeCriticality.K3
|
||||||
}
|
}
|
||||||
|
|
||||||
private Status getOverallStatus() {
|
private val overallStatus: Status
|
||||||
if (statusProbeToStatus.containsValue(Status.DOWN)) {
|
get() = if (statusProbeToStatus.containsValue(Status.DOWN)) Status.DOWN
|
||||||
return Status.DOWN;
|
else Status.UP
|
||||||
}
|
|
||||||
return Status.UP;
|
private val reason: String
|
||||||
|
get() {
|
||||||
|
val probesDown = statusProbeToStatus.entries.asSequence()
|
||||||
|
.filter { it.value == Status.DOWN }
|
||||||
|
.map { it.key }
|
||||||
|
.toList()
|
||||||
|
val reasonK1 = getDownStatusProbes(probesDown, StatusProbeCriticality.K1)
|
||||||
|
val reasonK2 = getDownStatusProbes(probesDown, StatusProbeCriticality.K2)
|
||||||
|
val reasonK3 = getDownStatusProbes(probesDown, StatusProbeCriticality.K3)
|
||||||
|
return "$reasonK1$reasonK2$reasonK3".trim { it <= ' ' }
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getReason() {
|
private fun getDownStatusProbes(probesDown: List<ProbeIdent>, criticality: StatusProbeCriticality): String {
|
||||||
|
val downProbeNames = probesDown.asSequence()
|
||||||
List<ProbeIdent> probesDown = statusProbeToStatus.entrySet().stream()
|
.filter { it.criticality == criticality }
|
||||||
.filter(entry -> Status.DOWN.equals(entry.getValue()))
|
.map { it.name }
|
||||||
.map(Map.Entry::getKey)
|
.toList()
|
||||||
.toList();
|
return if (downProbeNames.isNotEmpty()) {
|
||||||
|
"$criticality failed: ${downProbeNames.joinToString(separator = ",")}\n"
|
||||||
String reasonK1 = getDownStatusProbes(probesDown, K1);
|
} else ""
|
||||||
String reasonK2 = getDownStatusProbes(probesDown, K2);
|
|
||||||
String reasonK3 = getDownStatusProbes(probesDown, K3);
|
|
||||||
|
|
||||||
return "%s%s%s".formatted(reasonK1, reasonK2, reasonK3).trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDownStatusProbes(List<ProbeIdent> probesDown, StatusProbeCriticality criticality) {
|
data class ProbeIdent(val name: String, val criticality: StatusProbeCriticality)
|
||||||
List<String> downProbeNames = probesDown.stream().filter(probe -> probe.criticality.equals(criticality))
|
|
||||||
.map(probe -> probe.name).toList();
|
|
||||||
if (downProbeNames.size() > 0) {
|
|
||||||
return criticality + " failed: " + String.join(",", downProbeNames) + "\n";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public record ProbeIdent(String name, StatusProbeCriticality criticality) {
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val MARKER = MarkerManager.getMarker("statusprobe")
|
||||||
|
private const val LABEL_CRITICALITY = "label.status.criticality"
|
||||||
|
private const val LABEL_STATUS = "label.status.status"
|
||||||
|
private const val LABEL_REASON = "label.status.reason"
|
||||||
|
private const val LABEL_MESSAGE = "label.status.description"
|
||||||
|
private const val LABEL_LAST_STATUS_CHANGE = "label.status.last_change"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-49
@@ -1,64 +1,48 @@
|
|||||||
package de.twomartens.wahlrecht.monitoring.statusprobe;
|
package de.twomartens.wahlrecht.monitoring.statusprobe
|
||||||
|
|
||||||
import java.time.Clock;
|
import org.springframework.boot.actuate.health.Status
|
||||||
import java.time.Duration;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler
|
||||||
import java.time.ZonedDateTime;
|
import java.time.Clock
|
||||||
import org.springframework.boot.actuate.health.Status;
|
import java.time.Duration
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
public class TimeBasedStatusProbe extends StatusProbe implements ScheduledStatusProbe {
|
class TimeBasedStatusProbe(
|
||||||
|
private val maxFailureDuration: Duration, private val clock: Clock,
|
||||||
|
threadPoolTaskScheduler: ThreadPoolTaskScheduler, schedulePeriod: Duration, criticality: StatusProbeCriticality,
|
||||||
|
name: String, statusProbeLogger: StatusProbeLogger
|
||||||
|
) : StatusProbe(clock, criticality, name, statusProbeLogger), ScheduledStatusProbe {
|
||||||
|
private var lastSuccess: ZonedDateTime? = null
|
||||||
|
private var temporaryThrowable: Throwable? = null
|
||||||
|
private var temporaryMessage: String? = null
|
||||||
|
|
||||||
private final Clock clock;
|
init {
|
||||||
|
scheduleTask(threadPoolTaskScheduler, schedulePeriod)
|
||||||
private final Duration maxFailureDuration;
|
|
||||||
|
|
||||||
private ZonedDateTime lastSuccess;
|
|
||||||
|
|
||||||
private Throwable throwable = null;
|
|
||||||
|
|
||||||
private String message = null;
|
|
||||||
|
|
||||||
public TimeBasedStatusProbe(Duration maxFailureDuration, Clock clock,
|
|
||||||
ThreadPoolTaskScheduler threadPoolTaskScheduler, StatusProbeCriticality criticality, String name,
|
|
||||||
StatusProbeLogger statusProbeLogger) {
|
|
||||||
this(maxFailureDuration, clock, threadPoolTaskScheduler, Duration.ofMinutes(1), criticality, name,
|
|
||||||
statusProbeLogger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeBasedStatusProbe(Duration maxFailureDuration, Clock clock,
|
@Synchronized
|
||||||
ThreadPoolTaskScheduler threadPoolTaskScheduler, Duration schedulePeriod, StatusProbeCriticality criticality,
|
override fun setStatus(status: Status, throwable: Throwable?, message: String?) {
|
||||||
String name, StatusProbeLogger statusProbeLogger) {
|
if (status === Status.DOWN) {
|
||||||
super(clock, criticality, name, statusProbeLogger);
|
this.temporaryThrowable = throwable
|
||||||
this.clock = clock;
|
this.temporaryMessage = message
|
||||||
this.maxFailureDuration = maxFailureDuration;
|
} else if (status === Status.UP) {
|
||||||
this.lastSuccess = null;
|
lastSuccess = ZonedDateTime.now(clock)
|
||||||
scheduleTask(threadPoolTaskScheduler, schedulePeriod);
|
super.setStatus(status, throwable, message)
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected synchronized void setStatus(Status status, Throwable throwable, String message) {
|
|
||||||
if (status == Status.DOWN) {
|
|
||||||
this.throwable = throwable;
|
|
||||||
this.message = message;
|
|
||||||
} else if (status == Status.UP) {
|
|
||||||
lastSuccess = ZonedDateTime.now(clock);
|
|
||||||
super.setStatus(status, throwable, message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isOverdue() {
|
private val isOverdue: Boolean
|
||||||
|
get() {
|
||||||
if (lastSuccess == null) {
|
if (lastSuccess == null) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
Duration timeSinceLastSuccess = Duration.between(lastSuccess, ZonedDateTime.now(clock));
|
val timeSinceLastSuccess = Duration.between(lastSuccess, ZonedDateTime.now(clock))
|
||||||
return maxFailureDuration.minus(timeSinceLastSuccess).isNegative();
|
return maxFailureDuration.minus(timeSinceLastSuccess).isNegative
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void runScheduledTask() {
|
@Synchronized
|
||||||
if (isOverdue()) {
|
override fun runScheduledTask() {
|
||||||
super.setStatus(Status.DOWN, throwable, message);
|
if (isOverdue) {
|
||||||
|
super.setStatus(Status.DOWN, temporaryThrowable, temporaryMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -1,10 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Candidate;
|
import de.twomartens.wahlrecht.model.db.Candidate
|
||||||
import java.util.Optional;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
|
||||||
|
|
||||||
public interface CandidateRepository extends MongoRepository<Candidate, String> {
|
interface CandidateRepository : MongoRepository<Candidate, String>
|
||||||
|
|
||||||
Optional<Candidate> findByName(String name);
|
|
||||||
}
|
|
||||||
+4
-11
@@ -1,13 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Constituency;
|
import de.twomartens.wahlrecht.model.db.Constituency
|
||||||
import java.util.Collection;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
|
||||||
|
|
||||||
public interface ConstituencyRepository extends MongoRepository<Constituency, String> {
|
interface ConstituencyRepository : MongoRepository<Constituency, String>
|
||||||
|
|
||||||
Optional<Constituency> findByElectionNameAndNumber(String electionName, int number);
|
|
||||||
|
|
||||||
Collection<Constituency> findByElectionName(String electionName);
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-8
@@ -1,10 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Election;
|
import de.twomartens.wahlrecht.model.db.Election
|
||||||
import java.util.Optional;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
|
||||||
|
|
||||||
public interface ElectionRepository extends MongoRepository<Election, String> {
|
interface ElectionRepository : MongoRepository<Election, String>
|
||||||
|
|
||||||
Optional<Election> findByName(String name);
|
|
||||||
}
|
|
||||||
|
|||||||
+4
-5
@@ -1,7 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.ElectionResult;
|
import de.twomartens.wahlrecht.model.db.ElectionResult
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
|
|
||||||
public interface ElectionResultRepository extends MongoRepository<ElectionResult, String> {
|
interface ElectionResultRepository : MongoRepository<ElectionResult, String>
|
||||||
}
|
|
||||||
|
|||||||
+4
-10
@@ -1,12 +1,6 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Nomination;
|
import de.twomartens.wahlrecht.model.db.Nomination
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
|
|
||||||
import java.util.Optional;
|
interface NominationRepository : MongoRepository<Nomination, String>
|
||||||
|
|
||||||
public interface NominationRepository extends MongoRepository<Nomination, String> {
|
|
||||||
|
|
||||||
Optional<Nomination> findByNameAndElectionNameAndPartyAbbreviation(String name,
|
|
||||||
String electionName, String partyAbbreviation);
|
|
||||||
}
|
|
||||||
|
|||||||
+5
-11
@@ -1,14 +1,8 @@
|
|||||||
package de.twomartens.wahlrecht.repository;
|
package de.twomartens.wahlrecht.repository
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.PartyInElection;
|
import de.twomartens.wahlrecht.model.db.PartyInElection
|
||||||
import java.util.Collection;
|
import org.springframework.data.mongodb.repository.MongoRepository
|
||||||
import java.util.Optional;
|
|
||||||
import org.springframework.data.mongodb.repository.MongoRepository;
|
|
||||||
|
|
||||||
public interface PartyRepository extends MongoRepository<PartyInElection, String> {
|
interface PartyRepository : MongoRepository<PartyInElection, String> {
|
||||||
|
fun findByElectionName(name: String): Collection<PartyInElection>
|
||||||
Optional<PartyInElection> findByAbbreviationAndElectionName(String abbreviation,
|
|
||||||
String electionName);
|
|
||||||
|
|
||||||
Collection<PartyInElection> findByElectionName(String name);
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-8
@@ -12,18 +12,16 @@ import org.keycloak.adapters.authorization.spi.ConfigurationResolver
|
|||||||
import org.keycloak.adapters.authorization.spi.HttpRequest
|
import org.keycloak.adapters.authorization.spi.HttpRequest
|
||||||
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
|
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
|
||||||
class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolver?) : Filter {
|
class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolver) : Filter {
|
||||||
private val policyEnforcer: MutableMap<PolicyEnforcerConfig, SpringPolicyEnforcer> = Collections.synchronizedMap(HashMap())
|
private val policyEnforcer: MutableMap<PolicyEnforcerConfig, SpringPolicyEnforcer> = mutableMapOf()
|
||||||
|
|
||||||
@Throws(IOException::class, ServletException::class)
|
@Throws(IOException::class, ServletException::class)
|
||||||
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse?, filterChain: FilterChain) {
|
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse?, filterChain: FilterChain) {
|
||||||
val request = servletRequest as HttpServletRequest
|
val request = servletRequest as HttpServletRequest
|
||||||
val response = servletResponse as HttpServletResponse?
|
val response = servletResponse as HttpServletResponse?
|
||||||
val httpRequest = ServletHttpRequest(request
|
val httpRequest = ServletHttpRequest(request) { extractBearerToken(request) }
|
||||||
) { extractBearerToken(request) }
|
|
||||||
val policyEnforcer = getOrCreatePolicyEnforcer(httpRequest)
|
val policyEnforcer = getOrCreatePolicyEnforcer(httpRequest)
|
||||||
val authzContext = policyEnforcer.enforce(httpRequest, ServletHttpResponse(response))
|
val authzContext = policyEnforcer.enforce(httpRequest, ServletHttpResponse(response))
|
||||||
request.setAttribute(AuthorizationContext::class.java.name, authzContext)
|
request.setAttribute(AuthorizationContext::class.java.name, authzContext)
|
||||||
@@ -55,9 +53,7 @@ class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolv
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getOrCreatePolicyEnforcer(request: HttpRequest): SpringPolicyEnforcer {
|
private fun getOrCreatePolicyEnforcer(request: HttpRequest): SpringPolicyEnforcer {
|
||||||
return policyEnforcer.computeIfAbsent(configResolver!!.resolve(request)) { enforcerConfig: PolicyEnforcerConfig ->
|
return policyEnforcer.computeIfAbsent(configResolver.resolve(request)) { createPolicyEnforcer(it) }
|
||||||
createPolicyEnforcer(enforcerConfig)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPolicyEnforcer(enforcerConfig: PolicyEnforcerConfig): SpringPolicyEnforcer {
|
private fun createPolicyEnforcer(enforcerConfig: PolicyEnforcerConfig): SpringPolicyEnforcer {
|
||||||
|
|||||||
@@ -1,43 +1,35 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Candidate;
|
import de.twomartens.wahlrecht.model.db.Candidate
|
||||||
import de.twomartens.wahlrecht.repository.CandidateRepository;
|
import de.twomartens.wahlrecht.repository.CandidateRepository
|
||||||
import java.util.Map;
|
import org.springframework.stereotype.Service
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Service
|
@Service
|
||||||
public class CandidateService {
|
class CandidateService(private val repository: CandidateRepository) {
|
||||||
|
private val candidates: MutableMap<String, Candidate> by lazy {
|
||||||
private final CandidateRepository repository;
|
fetchCandidates()
|
||||||
private Map<String, Candidate> candidates;
|
|
||||||
|
|
||||||
public Candidate storeCandidate(@NonNull Candidate candidate) {
|
|
||||||
if (candidates == null) {
|
|
||||||
fetchCandidates();
|
|
||||||
}
|
|
||||||
Candidate existing = candidates.get(candidate.getName());
|
|
||||||
boolean needsUpdate = !candidate.equals(existing);
|
|
||||||
if (!needsUpdate) {
|
|
||||||
return existing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun storeCandidate(candidate: Candidate): Candidate {
|
||||||
|
var result = candidate
|
||||||
|
val existing = candidates[candidate.name]
|
||||||
|
val needsUpdate = candidate != existing
|
||||||
|
if (!needsUpdate && existing != null) {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.setProfession(candidate.getProfession());
|
existing.profession = candidate.profession
|
||||||
candidate = existing;
|
result = existing
|
||||||
|
}
|
||||||
|
result = repository.save(result)
|
||||||
|
candidates[candidate.name] = candidate
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
candidate = repository.save(candidate);
|
private fun fetchCandidates(): MutableMap<String, Candidate> {
|
||||||
candidates.put(candidate.getName(), candidate);
|
return repository.findAll().asSequence()
|
||||||
return candidate;
|
.map { Pair(it.name, it) }
|
||||||
}
|
.toMap()
|
||||||
|
.toMutableMap()
|
||||||
private void fetchCandidates() {
|
|
||||||
candidates = repository.findAll().stream()
|
|
||||||
.collect(Collectors.toMap(Candidate::getName, Function.identity()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-40
@@ -1,52 +1,40 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Constituency;
|
import de.twomartens.wahlrecht.model.db.Constituency
|
||||||
import de.twomartens.wahlrecht.model.internal.ConstituencyId;
|
import de.twomartens.wahlrecht.model.internal.ConstituencyId
|
||||||
import de.twomartens.wahlrecht.repository.ConstituencyRepository;
|
import de.twomartens.wahlrecht.repository.ConstituencyRepository
|
||||||
import java.util.Map;
|
import org.springframework.stereotype.Service
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
class ConstituencyService(private val repository: ConstituencyRepository) {
|
||||||
public class ConstituencyService {
|
private val constituencies: MutableMap<ConstituencyId, Constituency> by lazy {
|
||||||
|
fetchConstituencies()
|
||||||
private final ConstituencyRepository repository;
|
|
||||||
|
|
||||||
private Map<ConstituencyId, Constituency> constituencies;
|
|
||||||
|
|
||||||
public Constituency storeConstituency(@NonNull Constituency constituency) {
|
|
||||||
if (constituencies == null) {
|
|
||||||
fetchConstituencies();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConstituencyId constituencyId = new ConstituencyId(constituency.getElectionName(),
|
fun storeConstituency(constituency: Constituency): Constituency {
|
||||||
constituency.getNumber());
|
var result = constituency
|
||||||
Constituency existing = constituencies.get(constituencyId);
|
val constituencyId = ConstituencyId(
|
||||||
|
constituency.electionName,
|
||||||
if (constituency.equals(existing)) {
|
constituency.number
|
||||||
return existing;
|
)
|
||||||
|
val existing = constituencies[constituencyId]
|
||||||
|
if (constituency == existing) {
|
||||||
|
return existing
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.setNumberOfSeats(constituency.getNumberOfSeats());
|
existing.numberOfSeats = constituency.numberOfSeats
|
||||||
existing.setName(constituency.getName());
|
existing.name = constituency.name
|
||||||
constituency = existing;
|
result = existing
|
||||||
}
|
}
|
||||||
Constituency stored = repository.save(constituency);
|
val stored = repository.save(result)
|
||||||
constituencies.put(constituencyId, stored);
|
constituencies[constituencyId] = stored
|
||||||
|
return stored
|
||||||
return stored;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchConstituencies() {
|
private fun fetchConstituencies(): MutableMap<ConstituencyId, Constituency> {
|
||||||
constituencies = repository.findAll().stream()
|
return repository.findAll().asSequence()
|
||||||
.collect(Collectors.toMap(
|
.map { Pair(ConstituencyId(electionName = it.electionName, number = it.number), it) }
|
||||||
constituency -> new ConstituencyId(constituency.getElectionName(),
|
.toMap()
|
||||||
constituency.getNumber()),
|
.toMutableMap()
|
||||||
Function.identity()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-52
@@ -1,66 +1,49 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.ElectionResult;
|
import de.twomartens.wahlrecht.model.db.ElectionResult
|
||||||
import de.twomartens.wahlrecht.repository.ElectionResultRepository;
|
import de.twomartens.wahlrecht.repository.ElectionResultRepository
|
||||||
import java.util.Map;
|
import org.springframework.http.HttpStatus
|
||||||
import java.util.function.Function;
|
import org.springframework.stereotype.Service
|
||||||
import java.util.stream.Collectors;
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Service
|
@Service
|
||||||
public class ElectionResultService {
|
class ElectionResultService(private val repository: ElectionResultRepository) {
|
||||||
|
private val electionResults: MutableMap<String, ElectionResult> by lazy {
|
||||||
private final ElectionResultRepository repository;
|
fetchResults()
|
||||||
|
|
||||||
private Map<String, ElectionResult> electionResults;
|
|
||||||
|
|
||||||
public ElectionResult getElectionResult(String electionName) {
|
|
||||||
if (electionResults == null) {
|
|
||||||
fetchResults();
|
|
||||||
}
|
}
|
||||||
ElectionResult electionResult = electionResults.get(electionName);
|
fun getElectionResult(electionName: String): ElectionResult {
|
||||||
if (electionResult == null) {
|
return electionResults[electionName]
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
|
?: throw ResponseStatusException(
|
||||||
"no election result found for %s".formatted(electionName));
|
HttpStatus.NOT_FOUND,
|
||||||
}
|
"no election result found for $electionName"
|
||||||
return electionResult;
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean storeResult(ElectionResult result) {
|
fun storeResult(electionResult: ElectionResult): Boolean {
|
||||||
if (electionResults == null) {
|
var result = electionResult
|
||||||
fetchResults();
|
|
||||||
}
|
|
||||||
|
|
||||||
String id = result.getElectionName();
|
|
||||||
ElectionResult existing = electionResults.get(id);
|
|
||||||
boolean needsUpdate = !result.equals(existing);
|
|
||||||
|
|
||||||
|
val id = result.electionName
|
||||||
|
val existing = electionResults[id]
|
||||||
|
val needsUpdate = result != existing
|
||||||
if (!needsUpdate) {
|
if (!needsUpdate) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
var createdNew = true
|
||||||
boolean createdNew = true;
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.setOverallResults(result.getOverallResults());
|
existing.overallResults = result.overallResults
|
||||||
existing.setConstituencyResults(result.getConstituencyResults());
|
existing.constituencyResults = result.constituencyResults
|
||||||
result = existing;
|
result = existing
|
||||||
createdNew = false;
|
createdNew = false
|
||||||
|
}
|
||||||
|
val stored = repository.save(result)
|
||||||
|
electionResults[id] = stored
|
||||||
|
return createdNew
|
||||||
}
|
}
|
||||||
|
|
||||||
ElectionResult stored = repository.save(result);
|
private fun fetchResults(): MutableMap<String, ElectionResult> {
|
||||||
electionResults.put(id, stored);
|
return repository.findAll().asSequence()
|
||||||
|
.map { Pair(it.electionName, it) }
|
||||||
return createdNew;
|
.toMap()
|
||||||
}
|
.toMutableMap()
|
||||||
|
|
||||||
private void fetchResults() {
|
|
||||||
electionResults = repository.findAll().stream()
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
ElectionResult::getElectionName,
|
|
||||||
Function.identity()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,78 +1,60 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.mapper.v1.ElectionMapper;
|
import de.twomartens.wahlrecht.mapper.v1.ElectionMapper
|
||||||
import de.twomartens.wahlrecht.model.db.Constituency;
|
import de.twomartens.wahlrecht.model.db.Constituency
|
||||||
import de.twomartens.wahlrecht.model.db.Election;
|
import de.twomartens.wahlrecht.model.db.Election
|
||||||
import de.twomartens.wahlrecht.repository.ElectionRepository;
|
import de.twomartens.wahlrecht.repository.ElectionRepository
|
||||||
import java.util.ArrayList;
|
import org.mapstruct.factory.Mappers
|
||||||
import java.util.Collection;
|
import org.springframework.stereotype.Service
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.mapstruct.factory.Mappers;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Service
|
@Service
|
||||||
public class ElectionService {
|
class ElectionService(
|
||||||
|
private val electionRepository: ElectionRepository,
|
||||||
private final ElectionRepository electionRepository;
|
private val constituencyService: ConstituencyService
|
||||||
private final ConstituencyService constituencyService;
|
) {
|
||||||
private final ElectionMapper electionMapper = Mappers.getMapper(ElectionMapper.class);
|
private val electionMapper = Mappers.getMapper(ElectionMapper::class.java)
|
||||||
|
private val _elections: MutableMap<String, Election> by lazy {
|
||||||
private Map<String, Election> elections;
|
fetchElections()
|
||||||
|
|
||||||
public Collection<Election> getElections() {
|
|
||||||
return electionRepository.findAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Election getElection(String electionName) {
|
val elections: Map<String, Election> = _elections
|
||||||
if (elections == null) {
|
|
||||||
fetchElections();
|
private fun getElection(electionName: String): Election? {
|
||||||
}
|
return elections[electionName]
|
||||||
return elections.get(electionName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public de.twomartens.wahlrecht.model.internal.Election getElectionInternal(String electionName) {
|
fun getElectionInternal(electionName: String): de.twomartens.wahlrecht.model.internal.Election {
|
||||||
return electionMapper.mapToInternal(getElection(electionName));
|
return electionMapper.mapToInternal(getElection(electionName)!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean storeElection(@NonNull Election election) {
|
fun storeElection(election: Election): Boolean {
|
||||||
if (elections == null) {
|
var result = election
|
||||||
fetchElections();
|
var createdNew = true
|
||||||
|
val electionName = election.name
|
||||||
|
val existing = elections[electionName]
|
||||||
|
if (election == existing) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
boolean createdNew = true;
|
val constituencies: MutableCollection<Constituency> = mutableListOf()
|
||||||
String electionName = election.getName();
|
result.constituencies
|
||||||
Election existing = elections.get(electionName);
|
.forEach { constituencies.add(constituencyService.storeConstituency(it)) }
|
||||||
|
|
||||||
if (election.equals(existing)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Collection<Constituency> constituencies = new ArrayList<>();
|
|
||||||
election.getConstituencies()
|
|
||||||
.forEach(constituency -> constituencies.add(
|
|
||||||
constituencyService.storeConstituency(constituency)));
|
|
||||||
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.setDay(election.getDay());
|
existing.day = election.day
|
||||||
existing.setTotalNumberOfSeats(election.getTotalNumberOfSeats());
|
existing.totalNumberOfSeats = election.totalNumberOfSeats
|
||||||
existing.setVotingThreshold(election.getVotingThreshold());
|
existing.votingThreshold = election.votingThreshold
|
||||||
election = existing;
|
result = existing
|
||||||
createdNew = false;
|
createdNew = false
|
||||||
|
}
|
||||||
|
result.constituencies = constituencies
|
||||||
|
val stored = electionRepository.save(result)
|
||||||
|
_elections[electionName] = stored
|
||||||
|
return createdNew
|
||||||
}
|
}
|
||||||
|
|
||||||
election.setConstituencies(constituencies);
|
private fun fetchElections(): MutableMap<String, Election> {
|
||||||
Election stored = electionRepository.save(election);
|
return electionRepository.findAll().asSequence()
|
||||||
elections.put(electionName, stored);
|
.map { Pair(it.name, it) }
|
||||||
|
.toMap()
|
||||||
return createdNew;
|
.toMutableMap()
|
||||||
}
|
|
||||||
|
|
||||||
private void fetchElections() {
|
|
||||||
elections = electionRepository.findAll().stream()
|
|
||||||
.collect(Collectors.toMap(Election::getName, Function.identity()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,66 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import de.twomartens.wahlrecht.model.db.Nomination;
|
import de.twomartens.wahlrecht.model.db.Nomination
|
||||||
import de.twomartens.wahlrecht.model.db.PartyInElection;
|
import de.twomartens.wahlrecht.model.db.PartyInElection
|
||||||
import de.twomartens.wahlrecht.model.internal.PartyId;
|
import de.twomartens.wahlrecht.model.internal.PartyId
|
||||||
import de.twomartens.wahlrecht.repository.PartyRepository;
|
import de.twomartens.wahlrecht.repository.PartyRepository
|
||||||
import java.util.Collection;
|
import org.springframework.http.HttpStatus
|
||||||
import java.util.HashMap;
|
import org.springframework.stereotype.Service
|
||||||
import java.util.List;
|
import org.springframework.web.server.ResponseStatusException
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Service
|
@Service
|
||||||
public class PartyService {
|
class PartyService(
|
||||||
|
private val partyRepository: PartyRepository,
|
||||||
|
private val nominationService: NominationService
|
||||||
|
) {
|
||||||
|
|
||||||
private final PartyRepository partyRepository;
|
private val parties: MutableMap<PartyId, PartyInElection> by lazy {
|
||||||
private final NominationService nominationService;
|
fetchParties()
|
||||||
private Map<PartyId, PartyInElection> parties = null;
|
}
|
||||||
|
|
||||||
public Collection<PartyInElection> getPartiesByElectionName(String electionName) {
|
fun getPartiesByElectionName(electionName: String): Collection<PartyInElection> {
|
||||||
Collection<PartyInElection> parties = partyRepository.findByElectionName(electionName);
|
val parties = partyRepository.findByElectionName(electionName)
|
||||||
if (parties.isEmpty()) {
|
if (parties.isEmpty()) {
|
||||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for " + electionName);
|
throw ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for $electionName")
|
||||||
|
}
|
||||||
|
return parties
|
||||||
}
|
}
|
||||||
|
|
||||||
return parties;
|
fun storeParty(partyInElection: PartyInElection): Boolean {
|
||||||
}
|
var result = partyInElection
|
||||||
|
var createdNew = true
|
||||||
public boolean storeParty(PartyInElection partyInElection) {
|
val partyId = PartyId(
|
||||||
if (parties == null) {
|
partyInElection.electionName,
|
||||||
fetchParties();
|
partyInElection.abbreviation
|
||||||
}
|
)
|
||||||
|
val existing = parties[partyId]
|
||||||
boolean createdNew = true;
|
val needsUpdate = partyInElection != existing
|
||||||
PartyId partyId = new PartyId(partyInElection.getElectionName(),
|
|
||||||
partyInElection.getAbbreviation());
|
|
||||||
PartyInElection existing = parties.get(partyId);
|
|
||||||
boolean needsUpdate = !partyInElection.equals(existing);
|
|
||||||
if (!needsUpdate) {
|
if (!needsUpdate) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
val constituencyNominations: MutableMap<Int, Nomination?> = mutableMapOf()
|
||||||
Map<Integer, Nomination> constituencyNominations = new HashMap<>();
|
result.constituencyNominations
|
||||||
partyInElection.getConstituencyNominations()
|
.forEach { constituencyNominations[it.key] = nominationService.storeNomination(it.value) }
|
||||||
.forEach((key, value) -> constituencyNominations.put(key,
|
|
||||||
nominationService.storeNomination(value)));
|
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.setName(partyInElection.getName());
|
existing.name = partyInElection.name
|
||||||
if (constituencyNominations.isEmpty()) {
|
if (constituencyNominations.isEmpty()) {
|
||||||
constituencyNominations.putAll(existing.getConstituencyNominations());
|
constituencyNominations.putAll(existing.constituencyNominations)
|
||||||
}
|
}
|
||||||
partyInElection = existing;
|
result = existing
|
||||||
createdNew = false;
|
createdNew = false
|
||||||
}
|
}
|
||||||
partyInElection.setConstituencyNominations(constituencyNominations);
|
partyInElection.constituencyNominations = constituencyNominations
|
||||||
partyInElection.setOverallNomination(
|
partyInElection.overallNomination = nominationService.storeNomination(partyInElection.overallNomination)!!
|
||||||
nominationService.storeNomination(partyInElection.getOverallNomination()));
|
result = partyRepository.save(result)
|
||||||
partyInElection = partyRepository.save(partyInElection);
|
parties[partyId] = result
|
||||||
parties.put(partyId, partyInElection);
|
return createdNew
|
||||||
|
|
||||||
return createdNew;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchParties() {
|
private fun fetchParties(): MutableMap<PartyId, PartyInElection> {
|
||||||
List<PartyInElection> allParties = partyRepository.findAll();
|
val allParties = partyRepository.findAll()
|
||||||
parties = allParties.stream()
|
return allParties.asSequence()
|
||||||
.collect(Collectors.toMap(
|
.map { Pair(PartyId(electionName = it.electionName, abbreviation = it.abbreviation), it) }
|
||||||
p -> new PartyId(p.getElectionName(), p.getAbbreviation()),
|
.toMap()
|
||||||
Function.identity()));
|
.toMutableMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-25
@@ -1,37 +1,41 @@
|
|||||||
package de.twomartens.wahlrecht.service;
|
package de.twomartens.wahlrecht.service
|
||||||
|
|
||||||
import java.util.Set;
|
import mu.KotlinLogging
|
||||||
import java.util.concurrent.TimeUnit;
|
import org.apache.logging.log4j.message.StringMapMessage
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
import lombok.extern.log4j.Log4j2;
|
import org.springframework.cloud.context.refresh.ContextRefresher
|
||||||
import org.apache.logging.log4j.message.StringMapMessage;
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.cloud.context.refresh.ContextRefresher;
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import java.util.concurrent.TimeUnit
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* automatically reloads application*.yaml. reload can also be triggered manually by doing a post
|
* automatically reloads application*.yaml. reload can also be triggered manually by doing a post
|
||||||
* request on http://localhost:12001/actuator/refresh
|
* request on http://localhost:12001/actuator/refresh
|
||||||
*/
|
*/
|
||||||
@Log4j2
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
class PropertyReloadService(@Qualifier("configDataContextRefresher") private val contextRefresher: ContextRefresher) {
|
||||||
public class PropertyReloadService {
|
@Scheduled(
|
||||||
|
fixedDelay = REFRESH_SECONDS.toLong(),
|
||||||
public static final int REFRESH_SECONDS = 60;
|
initialDelay = REFRESH_SECONDS.toLong(),
|
||||||
public static final String PARAM_MESSAGE = "message";
|
timeUnit = TimeUnit.SECONDS
|
||||||
public static final String PARAM_PROPERTIES = "labels.properties";
|
)
|
||||||
|
fun refresh() {
|
||||||
private final ContextRefresher contextRefresher;
|
val properties = contextRefresher.refresh()
|
||||||
|
if (properties.isNotEmpty()) {
|
||||||
@Scheduled(fixedDelay = REFRESH_SECONDS, initialDelay = REFRESH_SECONDS, timeUnit = TimeUnit.SECONDS)
|
log.info(
|
||||||
public void refresh() {
|
StringMapMessage()
|
||||||
Set<String> properties = contextRefresher.refresh();
|
|
||||||
if (!properties.isEmpty()) {
|
|
||||||
log.info(new StringMapMessage()
|
|
||||||
.with(PARAM_MESSAGE, "properties changed")
|
.with(PARAM_MESSAGE, "properties changed")
|
||||||
.with(PARAM_PROPERTIES, String.join("\n", properties)));
|
.with(PARAM_PROPERTIES, properties.joinToString("\n"))
|
||||||
|
.asString()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val REFRESH_SECONDS = 60
|
||||||
|
const val PARAM_MESSAGE = "message"
|
||||||
|
const val PARAM_PROPERTIES = "labels.properties"
|
||||||
|
|
||||||
|
private val log = KotlinLogging.logger {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ spring:
|
|||||||
de.twomartens.wahlrecht:
|
de.twomartens.wahlrecht:
|
||||||
defaultTimeZone: Europe/Berlin
|
defaultTimeZone: Europe/Berlin
|
||||||
greeting: "Hello %s"
|
greeting: "Hello %s"
|
||||||
|
statusprobe:
|
||||||
|
scheduleDuration: 60
|
||||||
|
maxKafkaFailureDuration: 5
|
||||||
|
maxBlobFailureCount: 5
|
||||||
|
maxFailurePercent: 50
|
||||||
|
|
||||||
springdoc:
|
springdoc:
|
||||||
swagger-ui:
|
swagger-ui:
|
||||||
|
|||||||
+55
-60
@@ -11,7 +11,6 @@ import org.mockito.Mockito
|
|||||||
import org.mockito.junit.jupiter.MockitoExtension
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.Month
|
import java.time.Month
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension::class)
|
@ExtendWith(MockitoExtension::class)
|
||||||
@@ -261,12 +260,9 @@ class CalculationServiceTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCandidates(vararg names: String): Collection<Candidate> {
|
private fun buildCandidates(vararg names: String): Collection<Candidate> {
|
||||||
return Arrays.stream(names).map { name: String? ->
|
return names
|
||||||
Candidate(
|
.map { Candidate(it, "") }
|
||||||
name,
|
.toList()
|
||||||
""
|
|
||||||
)
|
|
||||||
}.toList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildVotesPerPosition(vararg votes: Int): Map<Int, Int> {
|
private fun buildVotesPerPosition(vararg votes: Int): Map<Int, Int> {
|
||||||
@@ -339,13 +335,13 @@ class CalculationServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun shouldCalculateOverallCandidatesCorrectly() {
|
fun shouldCalculateOverallCandidatesCorrectly() {
|
||||||
val seatsPerVotingResult = java.util.Map.of(
|
val seatsPerVotingResult = mapOf(
|
||||||
SPD_BEZIRK_RESULT, 2,
|
Pair(SPD_BEZIRK_RESULT, 2),
|
||||||
CDU_BEZIRK_RESULT, 2,
|
Pair(CDU_BEZIRK_RESULT, 2),
|
||||||
LINKE_BEZIRK_RESULT, 3,
|
Pair(LINKE_BEZIRK_RESULT, 3),
|
||||||
GRUENE_BEZIRK_RESULT, 8,
|
Pair(GRUENE_BEZIRK_RESULT, 8),
|
||||||
FDP_BEZIRK_RESULT, 3,
|
Pair(FDP_BEZIRK_RESULT, 3),
|
||||||
AFD_BEZIRK_RESULT, 3
|
Pair(AFD_BEZIRK_RESULT, 3)
|
||||||
)
|
)
|
||||||
val electedCandidates = setUpConstituencyResults()
|
val electedCandidates = setUpConstituencyResults()
|
||||||
Mockito.`when`(nominationService.getNominationInternal(SPD_BEZIRK.id)).thenReturn(SPD_BEZIRK)
|
Mockito.`when`(nominationService.getNominationInternal(SPD_BEZIRK.id)).thenReturn(SPD_BEZIRK)
|
||||||
@@ -363,13 +359,13 @@ class CalculationServiceTest {
|
|||||||
Assertions.assertThat(id).isEqualTo(SPD_BEZIRK.id)
|
Assertions.assertThat(id).isEqualTo(SPD_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(GOTTLIEB_GABOR)
|
(candidate.name == GOTTLIEB_GABOR
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(RIEGEL_ANN_KATHRIN)
|
(candidate.name == RIEGEL_ANN_KATHRIN
|
||||||
&& candidate.elected === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.anySatisfy { id, candidates ->
|
.anySatisfy { id, candidates ->
|
||||||
@@ -377,14 +373,13 @@ class CalculationServiceTest {
|
|||||||
.isEqualTo(CDU_BEZIRK.id)
|
.isEqualTo(CDU_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(HOEFLICH_JUTTA)
|
(candidate.name == HOEFLICH_JUTTA
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name()
|
(candidate.name == DR_LANGHEIN_A_W_HEINRICH
|
||||||
.equals(DR_LANGHEIN_A_W_HEINRICH)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.anySatisfy { id, candidates ->
|
.anySatisfy { id, candidates ->
|
||||||
@@ -392,18 +387,18 @@ class CalculationServiceTest {
|
|||||||
.isEqualTo(LINKE_BEZIRK.id)
|
.isEqualTo(LINKE_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(PAGELS_MANUELA)
|
(candidate.name == PAGELS_MANUELA
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(WIEGMANN_ROLAND)
|
(candidate.name == WIEGMANN_ROLAND
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(KLEINERT_MIKEY)
|
(candidate.name == KLEINERT_MIKEY
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.anySatisfy { id, candidates ->
|
.anySatisfy { id, candidates ->
|
||||||
@@ -411,19 +406,18 @@ class CalculationServiceTest {
|
|||||||
.isEqualTo(FDP_BEZIRK.id)
|
.isEqualTo(FDP_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(KRUEGER_KLAUS)
|
(candidate.name == KRUEGER_KLAUS
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(SCHWANKE_BENJAMIN)
|
(candidate.name == SCHWANKE_BENJAMIN
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name()
|
(candidate.name == MUELLER_SOENKSEN_BURKHARDT
|
||||||
.equals(MUELLER_SOENKSEN_BURKHARDT)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.anySatisfy { id, candidates ->
|
.anySatisfy { id, candidates ->
|
||||||
@@ -431,18 +425,18 @@ class CalculationServiceTest {
|
|||||||
.isEqualTo(AFD_BEZIRK.id)
|
.isEqualTo(AFD_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(SCHOEMER_DIRK)
|
(candidate.name == SCHOEMER_DIRK
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(ZIMMERMANN_ELKE)
|
(candidate.name == ZIMMERMANN_ELKE
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(PILLATZKE_JOERG)
|
(candidate.name == PILLATZKE_JOERG
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.anySatisfy { id, candidates ->
|
.anySatisfy { id, candidates ->
|
||||||
@@ -450,50 +444,50 @@ class CalculationServiceTest {
|
|||||||
.isEqualTo(GRUENE_BEZIRK.id)
|
.isEqualTo(GRUENE_BEZIRK.id)
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(KUHLMANN_DIETMAR)
|
(candidate.name == KUHLMANN_DIETMAR
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(KLEIN_ROBERT)
|
(candidate.name == KLEIN_ROBERT
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(ERK_ARAMAK)
|
(candidate.name == ERK_ARAMAK
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(MARTENS_JIM)
|
(candidate.name == MARTENS_JIM
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name()
|
(candidate.name == DR_FISCHER_JOST_LEONHARDT
|
||||||
.equals(DR_FISCHER_JOST_LEONHARDT)
|
&& candidate.elected == Elected.OVERALL_NOMINATION_ORDER)
|
||||||
&& candidate.elected() === Elected.OVERALL_NOMINATION_ORDER)
|
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(KUELL_GABRIELA)
|
(candidate.name == KUELL_GABRIELA
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(BOHNY_CARL_MARIA)
|
(candidate.name == BOHNY_CARL_MARIA
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
Assertions.assertThat(candidates)
|
Assertions.assertThat(candidates)
|
||||||
.anyMatch { candidate ->
|
.anyMatch { candidate ->
|
||||||
(candidate.name().equals(DORSCH_SEBASTIAN)
|
(candidate.name == DORSCH_SEBASTIAN
|
||||||
&& candidate.elected() === Elected.OVERALL_VOTE_ORDER)
|
&& candidate.elected == Elected.OVERALL_VOTE_ORDER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpConstituencyResults(): Map<String, Collection<ElectedCandidate>> {
|
private fun setUpConstituencyResults(): Map<String, Collection<ElectedCandidate>> {
|
||||||
return java.util.Map.of(
|
return mapOf(
|
||||||
|
Pair(
|
||||||
GRUENE_BEZIRK_RESULT.nominationId.partyAbbreviation, listOf(
|
GRUENE_BEZIRK_RESULT.nominationId.partyAbbreviation, listOf(
|
||||||
ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY),
|
ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY),
|
||||||
ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY),
|
ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY),
|
||||||
@@ -508,5 +502,6 @@ class CalculationServiceTest {
|
|||||||
ElectedCandidate(GRUENE_BEZIRK.getCandidate(22), Elected.CONSTITUENCY)
|
ElectedCandidate(GRUENE_BEZIRK.getCandidate(22), Elected.CONSTITUENCY)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user