From 910108f88de2e6a389ac5bf73b8a833eb645214b Mon Sep 17 00:00:00 2001 From: Jim Martens Date: Sun, 30 Jul 2023 18:06:26 +0200 Subject: [PATCH] Finished migration to Kotlin --- .../main/kotlin/twomartens.java.gradle.kts | 6 +- .../kotlin/twomartens.spring-boot.gradle.kts | 15 +- gradle.properties | 3 +- module-server/server.gradle.kts | 5 - .../configuration/ClockConfiguration.kt | 2 +- .../configuration/OpenApiConfiguration.kt | 54 ++--- .../configuration/StatusProbeConfiguration.kt | 43 ++-- .../configuration/WebConfiguration.kt | 48 ++--- .../configuration/WebSecurityConfiguration.kt | 196 +++++++++-------- .../controller/v1/CalculationController.kt | 107 +++++----- .../controller/v1/ElectionController.kt | 180 ++++++++-------- .../controller/v1/ElectionResultController.kt | 148 ++++++------- .../controller/v1/PartyController.kt | 160 +++++++------- .../interceptor/LoggingInterceptorRest.kt | 2 +- .../wahlrecht/model/db/Candidate.kt | 56 ++--- .../wahlrecht/model/db/Constituency.kt | 59 ++---- .../twomartens/wahlrecht/model/db/Election.kt | 68 +++--- .../wahlrecht/model/db/ElectionResult.kt | 58 ++---- .../wahlrecht/model/db/PartyInElection.kt | 65 +++--- .../wahlrecht/model/db/VotingResult.kt | 37 +--- .../wahlrecht/model/internal/Candidate.kt | 6 +- .../wahlrecht/model/internal/Constituency.kt | 12 +- .../model/internal/ConstituencyId.kt | 6 +- .../wahlrecht/model/internal/Elected.kt | 12 +- .../model/internal/ElectedCandidate.kt | 15 +- .../model/internal/ElectionResult.kt | 14 +- .../wahlrecht/model/internal/Nomination.kt | 4 +- .../wahlrecht/model/internal/PartyId.kt | 6 +- .../actuator/AbstractHealthIndicator.kt | 143 +++++++------ .../AbstractStatusProbeHealthIndicator.kt | 59 +++--- .../monitoring/actuator/Preparable.kt | 11 +- .../actuator/RestHealthIndicator.kt | 90 ++++---- .../statusprobe/CountBasedStatusProbe.kt | 48 ++--- .../statusprobe/PercentageBasedStatusProbe.kt | 88 ++++---- .../statusprobe/ScheduledStatusProbe.kt | 34 +-- .../monitoring/statusprobe/StatusProbe.kt | 95 ++++----- .../statusprobe/StatusProbeCriticality.kt | 10 +- .../statusprobe/StatusProbeLogger.kt | 197 ++++++++---------- .../statusprobe/TimeBasedStatusProbe.kt | 94 ++++----- .../repository/CandidateRepository.kt | 12 +- .../repository/ConstituencyRepository.kt | 15 +- .../repository/ElectionRepository.kt | 12 +- .../repository/ElectionResultRepository.kt | 9 +- .../repository/NominationRepository.kt | 14 +- .../wahlrecht/repository/PartyRepository.kt | 16 +- .../security/SpringPolicyEnforcerFilter.kt | 12 +- .../wahlrecht/service/CandidateService.kt | 62 +++--- .../wahlrecht/service/ConstituencyService.kt | 74 +++---- .../service/ElectionResultService.kt | 93 ++++----- .../wahlrecht/service/ElectionService.kt | 116 +++++------ .../wahlrecht/service/PartyService.kt | 119 +++++------ .../service/PropertyReloadService.kt | 58 +++--- .../src/main/resources/application.yaml | 5 + .../service/CalculationServiceTest.kt | 139 ++++++------ 54 files changed, 1325 insertions(+), 1687 deletions(-) diff --git a/buildSrc/src/main/kotlin/twomartens.java.gradle.kts b/buildSrc/src/main/kotlin/twomartens.java.gradle.kts index b6408e3..682fa8c 100644 --- a/buildSrc/src/main/kotlin/twomartens.java.gradle.kts +++ b/buildSrc/src/main/kotlin/twomartens.java.gradle.kts @@ -1,5 +1,5 @@ import org.gradle.accessors.dm.LibrariesForLibs -import java.time.LocalDateTime +import java.time.ZonedDateTime import java.time.format.DateTimeFormatter 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 { doFirst { @@ -59,7 +59,7 @@ tasks.jar { attributes["Implementation-Title"] = rootProject.name attributes["Implementation-Version"] = archiveVersion.get() 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["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()}" diff --git a/buildSrc/src/main/kotlin/twomartens.spring-boot.gradle.kts b/buildSrc/src/main/kotlin/twomartens.spring-boot.gradle.kts index 77787a3..f4d6b00 100644 --- a/buildSrc/src/main/kotlin/twomartens.spring-boot.gradle.kts +++ b/buildSrc/src/main/kotlin/twomartens.spring-boot.gradle.kts @@ -12,7 +12,6 @@ dependencies { implementation(libs.bundles.spring.boot) testImplementation(libs.spring.boot.test) - annotationProcessor(libs.spring.boot.config) } sourceSets { @@ -31,7 +30,7 @@ idea { } } -val integrationTestImplementation by configurations.getting { +val integrationTestImplementation: Configuration by configurations.getting { extendsFrom(configurations.testImplementation.get()) } @@ -69,4 +68,16 @@ tasks.bootJar { tasks.jar { enabled = true archiveClassifier.set("") +} + +tasks.bootDistZip { + dependsOn(tasks.jar) +} + +tasks.bootDistTar { + dependsOn(tasks.jar) +} + +tasks.bootStartScripts { + dependsOn(tasks.jar) } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3ad5cc5..50fe38d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,5 @@ org.gradle.parallel=true org.gradle.daemon=true org.gradle.welcome=never org.gradle.caching=true -org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 \ No newline at end of file +org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +kapt.include.compile.classpath=false \ No newline at end of file diff --git a/module-server/server.gradle.kts b/module-server/server.gradle.kts index 5750494..3a4ad4f 100644 --- a/module-server/server.gradle.kts +++ b/module-server/server.gradle.kts @@ -2,7 +2,6 @@ plugins { id("twomartens.spring-boot-cloud") id("twomartens.kotlin") kotlin("kapt") - alias(libs.plugins.kotlin.lombok) } dependencies { @@ -10,8 +9,4 @@ dependencies { implementation(libs.bundles.spring.boot.security) annotationProcessor(libs.mapstruct.processor) kapt(libs.mapstruct.processor) -} - -kapt { - keepJavacAnnotationProcessors = true } \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/ClockConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/ClockConfiguration.kt index 9f1ba72..8aa2587 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/ClockConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/ClockConfiguration.kt @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration import java.time.Clock @Configuration -open class ClockConfiguration(val serviceProperties: ServiceProperties) { +open class ClockConfiguration(private val serviceProperties: ServiceProperties) { @Bean open fun clock(): Clock { diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/OpenApiConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/OpenApiConfiguration.kt index 96ac1b0..4c22c7a 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/OpenApiConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/OpenApiConfiguration.kt @@ -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.security.OAuthFlow; -import io.swagger.v3.oas.annotations.security.OAuthFlows; -import io.swagger.v3.oas.annotations.security.SecurityScheme; -import io.swagger.v3.oas.models.OpenAPI; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType +import io.swagger.v3.oas.annotations.security.OAuthFlow +import io.swagger.v3.oas.annotations.security.OAuthFlows +import io.swagger.v3.oas.annotations.security.SecurityScheme +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration -@SecurityScheme( - name = "bearerAuth", - type = SecuritySchemeType.HTTP, - scheme = "bearer", - bearerFormat = "JWT" -) +@SecurityScheme(name = "bearerAuth", type = SecuritySchemeType.HTTP, scheme = "bearer", bearerFormat = "JWT") @SecurityScheme( name = "oauth2", type = SecuritySchemeType.OAUTH2, - flows = @OAuthFlows( - implicit = @OAuthFlow( + flows = OAuthFlows( + implicit = OAuthFlow( authorizationUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/auth", tokenUrl = "https://id.2martens.de/realms/2martens/protocol/openid-connect/token" ) ) ) @Configuration -public class OpenApiConfiguration { - @Bean - public OpenAPI customOpenAPI(@Value("${openapi.description}") String apiDesciption, - @Value("${openapi.version}") String apiVersion, @Value("${openapi.title}") String apiTitle) { - return new OpenAPI() - .info(new io.swagger.v3.oas.models.info.Info() - .title(apiTitle) - .version(apiVersion) - .description(apiDesciption)); - } +open class OpenApiConfiguration { + @Bean + open fun customOpenAPI( + @Value("\${openapi.description}") apiDescription: String, + @Value("\${openapi.version}") apiVersion: String, @Value("\${openapi.title}") apiTitle: String + ): OpenAPI { + return OpenAPI() + .info( + Info() + .title(apiTitle) + .version(apiVersion) + .description(apiDescription) + ) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/StatusProbeConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/StatusProbeConfiguration.kt index ab95be0..34bebed 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/StatusProbeConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/StatusProbeConfiguration.kt @@ -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.StatusProbe; -import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality; -import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeLogger; -import java.time.Clock; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +import de.twomartens.wahlrecht.monitoring.statusprobe.CountBasedStatusProbe +import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe +import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality +import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeLogger +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import java.time.Clock @Configuration -@RequiredArgsConstructor -public class StatusProbeConfiguration { +open class StatusProbeConfiguration(private val clock: Clock) { + @Bean + open fun statusProbeLogger(): StatusProbeLogger { + return StatusProbeLogger(clock) + } - private final Clock clock; - - @Bean - public StatusProbeLogger statusProbeLogger() { - return new StatusProbeLogger(clock); - } - - @Bean - public StatusProbe testStatusProbe(StatusProbeLogger statusProbeLogger) { - return new CountBasedStatusProbe(1, - clock, StatusProbeCriticality.K1, "testStatusProbe", statusProbeLogger); - } + @Bean + open fun testStatusProbe(statusProbeLogger: StatusProbeLogger): StatusProbe { + return CountBasedStatusProbe( + 1, + clock, StatusProbeCriticality.K1, "testStatusProbe", statusProbeLogger + ) + } } \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebConfiguration.kt index 2becedf..c01b255 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebConfiguration.kt @@ -1,33 +1,25 @@ -package de.twomartens.wahlrecht.configuration; +package de.twomartens.wahlrecht.configuration -import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.lang.NonNull; -import org.springframework.web.servlet.config.annotation.CorsRegistration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +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 -@RequiredArgsConstructor -public class WebConfiguration implements WebMvcConfigurer { - - private final HeaderInterceptorRest headerInterceptorRest; - - @Override - public void addInterceptors(@NonNull InterceptorRegistry registry) { - registry.addInterceptor(headerInterceptorRest); - } - - @Override - public void addCorsMappings(@NonNull CorsRegistry registry) { - CorsRegistration registration = registry.addMapping("/**"); - registration.allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), - HttpMethod.PUT.name(), HttpMethod.OPTIONS.name()); - registration.allowCredentials(true); - registration.allowedOrigins("http://localhost:4200"); - } +open class WebConfiguration(private val headerInterceptorRest: HeaderInterceptorRest) : WebMvcConfigurer { + override fun addInterceptors(registry: InterceptorRegistry) { + registry.addInterceptor(headerInterceptorRest) + } + override fun addCorsMappings(registry: CorsRegistry) { + val registration = registry.addMapping("/**") + registration.allowedMethods( + HttpMethod.GET.name(), HttpMethod.POST.name(), + HttpMethod.PUT.name(), HttpMethod.OPTIONS.name() + ) + registration.allowCredentials(true) + registration.allowedOrigins("http://localhost:4200") + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebSecurityConfiguration.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebSecurityConfiguration.kt index 18836a9..330f897 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebSecurityConfiguration.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/configuration/WebSecurityConfiguration.kt @@ -1,112 +1,102 @@ -package de.twomartens.wahlrecht.configuration; +package de.twomartens.wahlrecht.configuration - -import de.twomartens.wahlrecht.security.SpringPolicyEnforcerFilter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import org.keycloak.adapters.authorization.spi.ConfigurationResolver; -import org.keycloak.adapters.authorization.spi.HttpRequest; -import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; -import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode; -import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig; -import org.keycloak.util.JsonSerialization; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.lang.NonNull; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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; +import de.twomartens.wahlrecht.security.SpringPolicyEnforcerFilter +import org.keycloak.adapters.authorization.spi.ConfigurationResolver +import org.keycloak.adapters.authorization.spi.HttpRequest +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.EnforcementMode +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathConfig +import org.keycloak.util.JsonSerialization +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.http.HttpMethod +import org.springframework.security.config.annotation.web.builders.HttpSecurity +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 +import java.io.IOException @Configuration @EnableWebSecurity -public class WebSecurityConfiguration { +open class WebSecurityConfiguration { + @Value("\${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") + private lateinit var jwkSetUri: String - private static final Collection PERMITTED_PATHS = List.of( - "/wahlrecht/v1/healthCheck", - "/actuator/**", - "/wahlrecht/v1/doc/**", - "/wahlrecht/v1/api-docs/**", - "/error"); + @Value("#{environment.CLIENT_SECRET}") + private lateinit var clientSecret: String - private static final List PATHS = buildPathConfigs(); - - - @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}") - private String jwkSetUri; - - @Value("#{environment.CLIENT_SECRET}") - private String clientSecret; - - - @Bean - public SecurityFilterChain securityFilterChain(@NonNull HttpSecurity http) throws Exception { - http - .csrf().disable() - .authorizeHttpRequests() - .requestMatchers(PERMITTED_PATHS.toArray(new String[0])) - .permitAll() - .and() - .authorizeHttpRequests() - .requestMatchers(HttpMethod.OPTIONS) - .permitAll() - .and() - .authorizeHttpRequests() - .anyRequest() - .authenticated() - .and() - .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) - .addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter.class); - return http.build(); - } - - @Bean - JwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build(); - } - - private SpringPolicyEnforcerFilter createPolicyEnforcerFilter() { - return new SpringPolicyEnforcerFilter(new ConfigurationResolver() { - @Override - public PolicyEnforcerConfig resolve(HttpRequest request) { - try { - PolicyEnforcerConfig policyEnforcerConfig = JsonSerialization.readValue( - getClass().getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig.class); - policyEnforcerConfig.setCredentials(Map.of("secret", clientSecret)); - if (request.getMethod().equals(HttpMethod.OPTIONS.name())) { - // always allow options request - policyEnforcerConfig.setEnforcementMode(EnforcementMode.DISABLED); - } else { - policyEnforcerConfig.setPaths(PATHS); - } - - return policyEnforcerConfig; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }); - } - - @NonNull - private static List buildPathConfigs() { - List paths = new ArrayList<>(); - for (String path : PERMITTED_PATHS) { - PathConfig pathConfig = new PathConfig(); - pathConfig.setPath(path.replace("**", "*")); - pathConfig.setEnforcementMode(EnforcementMode.DISABLED); - paths.add(pathConfig); + @Bean + @Throws(Exception::class) + open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http + .csrf().disable() + .authorizeHttpRequests() + .requestMatchers(*PERMITTED_PATHS.toTypedArray()) + .permitAll() + .and() + .authorizeHttpRequests() + .requestMatchers(HttpMethod.OPTIONS) + .permitAll() + .and() + .authorizeHttpRequests() + .anyRequest() + .authenticated() + .and() + .oauth2ResourceServer { obj: OAuth2ResourceServerConfigurer -> obj.jwt() } + .addFilterAfter(createPolicyEnforcerFilter(), BearerTokenAuthenticationFilter::class.java) + return http.build() } - return paths; - } + @Bean + open fun jwtDecoder(): JwtDecoder { + return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build() + } + + private fun createPolicyEnforcerFilter(): SpringPolicyEnforcerFilter { + return SpringPolicyEnforcerFilter(object : ConfigurationResolver { + override fun resolve(request: HttpRequest): PolicyEnforcerConfig { + return try { + val policyEnforcerConfig = JsonSerialization.readValue( + javaClass.getResourceAsStream("/policy-enforcer.json"), PolicyEnforcerConfig::class.java + ) + policyEnforcerConfig.credentials = mapOf(Pair("secret", clientSecret)) + if (request.method == HttpMethod.OPTIONS.name()) { + // always allow options request + policyEnforcerConfig.enforcementMode = EnforcementMode.DISABLED + } else { + policyEnforcerConfig.paths = PATHS + } + policyEnforcerConfig + } catch (e: IOException) { + throw RuntimeException(e) + } + } + }) + } + + companion object { + private val PERMITTED_PATHS: Collection = listOf( + "/wahlrecht/v1/healthCheck", + "/actuator/**", + "/wahlrecht/v1/doc/**", + "/wahlrecht/v1/api-docs/**", + "/error" + ) + private val PATHS = buildPathConfigs() + + private fun buildPathConfigs(): List { + val paths: MutableList = mutableListOf() + for (path in PERMITTED_PATHS) { + val pathConfig = PathConfig() + pathConfig.path = path.replace("**", "*") + pathConfig.enforcementMode = EnforcementMode.DISABLED + paths.add(pathConfig) + } + return paths + } + } } - diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt index 975a842..059a8ff 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/CalculationController.kt @@ -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.ElectionResultMapper; -import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates; -import de.twomartens.wahlrecht.model.dto.v1.ElectionResult; -import de.twomartens.wahlrecht.service.CalculationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.mapstruct.factory.Mappers; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import de.twomartens.wahlrecht.mapper.v1.ElectedCandidatesMapper +import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper +import de.twomartens.wahlrecht.model.dto.v1.ElectedCandidates +import de.twomartens.wahlrecht.model.dto.v1.ElectionResult +import de.twomartens.wahlrecht.service.CalculationService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement +import io.swagger.v3.oas.annotations.tags.Tag +import org.mapstruct.factory.Mappers +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController -@Slf4j @RestController -@RequiredArgsConstructor -@RequestMapping(value = "/wahlrecht/v1") +@RequestMapping(value = ["/wahlrecht/v1"]) @Tag(name = "Calculations", description = "all requests relating to calculations") -public class CalculationController { +class CalculationController(private val service: CalculationService) { + private val electedCandidatesMapper = Mappers.getMapper( + ElectedCandidatesMapper::class.java + ) + private val electionResultMapper = Mappers.getMapper( + ElectionResultMapper::class.java + ) - private final ElectedCandidatesMapper electedCandidatesMapper = Mappers.getMapper( - ElectedCandidatesMapper.class); - private final ElectionResultMapper electionResultMapper = Mappers.getMapper( - ElectionResultMapper.class); - private final CalculationService service; - - @Operation( - summary = "Calculates a provided election result", - description = "This request does not store any result and is idempotent.", - responses = { - @ApiResponse(responseCode = "200", - description = "Returns all elected candidates", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ElectedCandidates.class))} - ) - } - ) - @PostMapping(value = "/calculate", produces = MediaType.APPLICATION_JSON_VALUE) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity calculateResult( - @RequestBody ElectionResult electionResult - ) { - ElectedCandidates result = electedCandidatesMapper.mapToExternal( - service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult))); - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(result); - } + @Operation( + summary = "Calculates a provided election result", + description = "This request does not store any result and is idempotent.", + responses = [ApiResponse( + responseCode = "200", + description = "Returns all elected candidates", + content = [Content( + mediaType = "application/json", + schema = Schema(implementation = ElectedCandidates::class) + )] + )] + ) + @PostMapping(value = ["/calculate"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun calculateResult( + @RequestBody electionResult: ElectionResult + ): ResponseEntity { + val result = electedCandidatesMapper.mapToExternal( + service.determineElectedCandidates(electionResultMapper.mapToInternal(electionResult)) + ) + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(result) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt index d7e691e..97f0791 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionController.kt @@ -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.model.dto.v1.Election; -import de.twomartens.wahlrecht.service.ElectionService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.Collection; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.mapstruct.factory.Mappers; -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; +import de.twomartens.wahlrecht.mapper.v1.ElectionMapper +import de.twomartens.wahlrecht.model.dto.v1.Election +import de.twomartens.wahlrecht.service.ElectionService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.ArraySchema +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement +import io.swagger.v3.oas.annotations.tags.Tag +import org.mapstruct.factory.Mappers +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* -@Slf4j @RestController -@RequiredArgsConstructor -@RequestMapping(value = "/wahlrecht/v1") +@RequestMapping(value = ["/wahlrecht/v1"]) @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( + summary = "Returns all stored elections", + responses = [ApiResponse( + responseCode = "200", + description = "Returns all stored elections", + content = [Content( + mediaType = "application/json", + array = ArraySchema( + schema = Schema( + implementation = Election::class + ) + ) + )] + )] + ) + fun getElections(): ResponseEntity> { + val elections = service.elections.asSequence() + .map { mapper.mapToExternal(it.value) } + .toList() + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(elections) + } - @Operation( - summary = "Returns all stored elections", - responses = { - @ApiResponse(responseCode = "200", - description = "Returns all stored elections", - content = {@Content( - mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = Election.class)))} - ) - } - ) - @GetMapping(value = "/elections", produces = MediaType.APPLICATION_JSON_VALUE) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity> getElections() { - List elections = service.getElections().stream().map(mapper::mapToExternal).toList(); - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(elections); - } + @Operation( + summary = "Returns election matching provided name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns matching election", + content = [Content(mediaType = "application/json", schema = Schema(implementation = Election::class))] + )] + ) + @GetMapping(value = ["/elections/by-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getElectionByName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String? + ): ResponseEntity { + val election = mapper.mapToExternal(service.elections[electionName]!!) + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(election) + } - @Operation( - summary = "Returns election matching provided name", - responses = { - @ApiResponse(responseCode = "200", - description = "Returns matching election", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = Election.class))} - ) - } - ) - @GetMapping(value = "/elections/by-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity getElectionByName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) { - Election election = mapper.mapToExternal(service.getElection(electionName)); - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(election); - } - - @Operation( - summary = "Stores a new election", - responses = { - @ApiResponse(responseCode = "201", - description = "Election was created"), - @ApiResponse(responseCode = "200", - description = "Election was modified"), - @ApiResponse(responseCode = "401", - description = "Unauthorized", - content = {@Content( - mediaType = "plain/text")} - ) - } - ) - @PutMapping("/election") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity putElection(@RequestBody Election election) { - boolean createdNew = service.storeElection(mapper.mapToDB(election)); - return createdNew - ? new ResponseEntity<>(HttpStatus.CREATED) - : new ResponseEntity<>(HttpStatus.OK); - } + @Operation( + summary = "Stores a new election", + responses = [ApiResponse( + responseCode = "201", + description = "Election was created" + ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(mediaType = "plain/text")] + )] + ) + @PutMapping("/election") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putElection(@RequestBody election: Election?): ResponseEntity<*> { + val createdNew = service.storeElection(mapper.mapToDB(election!!)) + return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt index d545d6e..091ce84 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/ElectionResultController.kt @@ -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.model.dto.ErrorMessage; -import de.twomartens.wahlrecht.model.dto.v1.ElectionResult; -import de.twomartens.wahlrecht.service.ElectionResultService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.mapstruct.factory.Mappers; -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; +import de.twomartens.wahlrecht.mapper.v1.ElectionResultMapper +import de.twomartens.wahlrecht.model.dto.ErrorMessage +import de.twomartens.wahlrecht.model.dto.v1.ElectionResult +import de.twomartens.wahlrecht.service.ElectionResultService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement +import io.swagger.v3.oas.annotations.tags.Tag +import lombok.RequiredArgsConstructor +import lombok.extern.slf4j.Slf4j +import org.mapstruct.factory.Mappers +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* @Slf4j @RestController @RequiredArgsConstructor -@RequestMapping(value = "/wahlrecht/v1") +@RequestMapping(value = ["/wahlrecht/v1"]) @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( + summary = "Returns election result for given election name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns stored election result for given election name", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ElectionResult::class))] + ), ApiResponse( + responseCode = "404", + description = "No election result found", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] + )] + ) + @GetMapping( + value = ["/electionResult/by-election-name/{electionName}"], + produces = [MediaType.APPLICATION_JSON_VALUE] + ) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getElectionByName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String + ): ResponseEntity { + val result = mapper.mapToExternal(service.getElectionResult(electionName)) + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(result) + } - @Operation( - summary = "Returns election result for given election name", - responses = { - @ApiResponse(responseCode = "200", - description = "Returns stored election result for given election name", - content = {@Content( - mediaType = "application/json", - schema = @Schema(implementation = ElectionResult.class))} - ), - @ApiResponse(responseCode = "404", - description = "No election result found", - content = { - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorMessage.class) - ) - } - ) - } - ) - @GetMapping(value = "/electionResult/by-election-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity getElectionByName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) { - ElectionResult result = mapper.mapToExternal(service.getElectionResult(electionName)); - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(result); - } - - @Operation( - summary = "Stores a new election", - responses = { - @ApiResponse(responseCode = "201", - description = "Election was created"), - @ApiResponse(responseCode = "200", - description = "Election was modified"), - @ApiResponse(responseCode = "401", - description = "Unauthorized", - content = {@Content( - mediaType = "plain/text")} - ) - } - ) - @PutMapping("/electionResult") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity putElection(@RequestBody ElectionResult electionResult) { - boolean createdNew = service.storeResult(mapper.mapToDB(electionResult)); - return createdNew - ? new ResponseEntity<>(HttpStatus.CREATED) - : new ResponseEntity<>(HttpStatus.OK); - } + @Operation( + summary = "Stores a new election", + responses = [ApiResponse( + responseCode = "201", + description = "Election was created" + ), ApiResponse(responseCode = "200", description = "Election was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized", + content = [Content(mediaType = "plain/text")] + )] + ) + @PutMapping("/electionResult") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putElection(@RequestBody electionResult: ElectionResult?): ResponseEntity<*> { + val createdNew = service.storeResult(mapper.mapToDB(electionResult!!)) + return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt index 5897f20..bea793b 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/controller/v1/PartyController.kt @@ -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.model.dto.ErrorMessage; -import de.twomartens.wahlrecht.model.dto.v1.PartyInElection; -import de.twomartens.wahlrecht.service.PartyService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import java.util.Collection; -import java.util.List; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.mapstruct.factory.Mappers; -import org.springframework.http.HttpStatus; -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; +import de.twomartens.wahlrecht.mapper.v1.PartyInElectionMapper +import de.twomartens.wahlrecht.model.dto.ErrorMessage +import de.twomartens.wahlrecht.model.dto.v1.PartyInElection +import de.twomartens.wahlrecht.service.PartyService +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.media.ArraySchema +import io.swagger.v3.oas.annotations.media.Content +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.security.SecurityRequirement +import io.swagger.v3.oas.annotations.tags.Tag +import org.mapstruct.factory.Mappers +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.* -@Slf4j @Controller -@RequiredArgsConstructor -@RequestMapping(value = "/wahlrecht/v1") +@RequestMapping(value = ["/wahlrecht/v1"]) @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) + @Operation( + summary = "Returns all stored parties for given election name", + responses = [ApiResponse( + responseCode = "200", + description = "Returns all stored parties", + content = [Content( + mediaType = "application/json", + array = ArraySchema(schema = Schema(implementation = PartyInElection::class)) + )] + ), ApiResponse( + responseCode = "404", + description = "No parties found", + content = [Content(mediaType = "application/json", schema = Schema(implementation = ErrorMessage::class))] + )] + ) + @GetMapping(value = ["/parties/by-election-name/{electionName}"], produces = [MediaType.APPLICATION_JSON_VALUE]) + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun getPartiesByElectionName( + @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") electionName: String + ): ResponseEntity> { + val parties = service.getPartiesByElectionName(electionName).stream() + .map { election: de.twomartens.wahlrecht.model.db.PartyInElection? -> + mapper.mapToExternal( + election!! + ) + } + .toList() + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(parties) + } - private final PartyInElectionMapper mapper = Mappers.getMapper(PartyInElectionMapper.class); - private final PartyService service; - - @Operation( - summary = "Returns all stored parties for given election name", - responses = { - @ApiResponse(responseCode = "200", - description = "Returns all stored parties", - content = {@Content( - mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = PartyInElection.class)))} - ), - @ApiResponse(responseCode = "404", - description = "No parties found", - content = { - @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorMessage.class) - ) - } - ) - } - ) - @GetMapping(value = "/parties/by-election-name/{electionName}", produces = MediaType.APPLICATION_JSON_VALUE) - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity> getPartiesByElectionName( - @PathVariable @Parameter(description = "the election name", example = "Bezirkswahl 2019") String electionName) { - List parties = service.getPartiesByElectionName(electionName).stream() - .map(mapper::mapToExternal) - .toList(); - - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(parties); - } - - @Operation( - summary = "Stores a new party in an election", - responses = { - @ApiResponse(responseCode = "201", - description = "Party was created"), - @ApiResponse(responseCode = "200", - description = "Party was modified"), - @ApiResponse(responseCode = "401", - description = "Unauthorized" - ) - } - ) - @PutMapping("/party") - @SecurityRequirement(name = "bearerAuth") - @SecurityRequirement(name = "oauth2") - public ResponseEntity putParty(@RequestBody PartyInElection party) { - boolean createdNew = service.storeParty(mapper.mapToDB(party)); - return createdNew - ? new ResponseEntity<>(HttpStatus.CREATED) - : new ResponseEntity<>(HttpStatus.OK); - } + @Operation( + summary = "Stores a new party in an election", + responses = [ApiResponse( + responseCode = "201", + description = "Party was created" + ), ApiResponse(responseCode = "200", description = "Party was modified"), ApiResponse( + responseCode = "401", + description = "Unauthorized" + )] + ) + @PutMapping("/party") + @SecurityRequirement(name = "bearerAuth") + @SecurityRequirement(name = "oauth2") + fun putParty(@RequestBody party: PartyInElection?): ResponseEntity<*> { + val createdNew = service.storeParty(mapper.mapToDB(party!!)) + return if (createdNew) ResponseEntity(HttpStatus.CREATED) else ResponseEntity(HttpStatus.OK) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/interceptor/LoggingInterceptorRest.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/interceptor/LoggingInterceptorRest.kt index c120616..1686c6d 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/interceptor/LoggingInterceptorRest.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/interceptor/LoggingInterceptorRest.kt @@ -256,7 +256,7 @@ class LoggingInterceptorRest( addLogString(stringMapMessage, PARAM_BUSINESS_TYPE, logMessage.businessType) addLogString(stringMapMessage, PARAM_REQUEST_BODY, cutToMaxLength(logMessage.requestBody)) 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 { diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Candidate.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Candidate.kt index bf67741..f175b8e 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Candidate.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Candidate.kt @@ -1,44 +1,24 @@ -package de.twomartens.wahlrecht.model.db; +package de.twomartens.wahlrecht.model.db -import java.time.Instant; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -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.index.Indexed; -import org.springframework.data.mongodb.core.mapping.Document; +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 +import java.time.Instant @Document -@Getter -@Setter -@Builder -@EqualsAndHashCode -@ToString -@FieldDefaults(level = AccessLevel.PRIVATE) -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class Candidate { +data class Candidate( + @Indexed var name: String, + var profession: String +){ + @Id + var id: ObjectId = ObjectId() - @EqualsAndHashCode.Exclude - @Id - ObjectId id; - @EqualsAndHashCode.Exclude - @CreatedDate - Instant created; - @EqualsAndHashCode.Exclude - @LastModifiedDate - Instant lastModified; + @CreatedDate + lateinit var created: Instant - @Indexed - String name; - String profession; + @LastModifiedDate + lateinit var lastModified: Instant } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Constituency.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Constituency.kt index 9ddc7e8..554e199 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Constituency.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Constituency.kt @@ -1,46 +1,27 @@ -package de.twomartens.wahlrecht.model.db; +package de.twomartens.wahlrecht.model.db -import java.time.Instant; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -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.index.CompoundIndex; -import org.springframework.data.mongodb.core.mapping.Document; +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 +import java.time.Instant @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}") -public class Constituency { +class Constituency( + var number: Int = 0, + var electionName: String, + var name: String, + var numberOfSeats: Int = 0 +) { + @Id + var id: ObjectId = ObjectId() - @EqualsAndHashCode.Exclude - @Id - ObjectId id; - @EqualsAndHashCode.Exclude - @CreatedDate - Instant created; - @EqualsAndHashCode.Exclude - @LastModifiedDate - Instant lastModified; + @CreatedDate + lateinit var created: Instant - int number; - String electionName; - String name; - int numberOfSeats; + @LastModifiedDate + lateinit var lastModified: Instant } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Election.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Election.kt index d0d1be2..45b76b3 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Election.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/Election.kt @@ -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 java.time.Instant; -import java.time.LocalDate; -import java.util.Collection; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -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.index.Indexed; -import org.springframework.data.mongodb.core.mapping.Document; +import de.twomartens.wahlrecht.model.dto.v1.VotingThreshold +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 +import java.time.Instant +import java.time.LocalDate @Document -@Getter -@Setter -@Builder -@EqualsAndHashCode -@ToString -@FieldDefaults(level = AccessLevel.PRIVATE) -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class Election { +data class Election( + @Indexed(unique = true) var name: String, + var day: LocalDate, + var votingThreshold: VotingThreshold, + var totalNumberOfSeats: Int, + var constituencies: Collection +) { + @Id + var id: ObjectId = ObjectId() - @EqualsAndHashCode.Exclude - @Id - ObjectId id; - @EqualsAndHashCode.Exclude - @CreatedDate - Instant created; - @EqualsAndHashCode.Exclude - @LastModifiedDate - Instant lastModified; + @CreatedDate + lateinit var created: Instant + + @LastModifiedDate + lateinit var lastModified: Instant - @Indexed(unique = true) - String name; - LocalDate day; - VotingThreshold votingThreshold; - int totalNumberOfSeats; - Collection constituencies; } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/ElectionResult.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/ElectionResult.kt index 2ff58bf..90dce11 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/ElectionResult.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/ElectionResult.kt @@ -1,44 +1,24 @@ -package de.twomartens.wahlrecht.model.db; +package de.twomartens.wahlrecht.model.db -import java.time.Instant; -import java.util.Collection; -import java.util.Map; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -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; +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 +import java.time.Instant @Document -@Getter -@Setter -@Builder -@EqualsAndHashCode -@ToString -@FieldDefaults(level = AccessLevel.PRIVATE) -@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ElectionResult { - @EqualsAndHashCode.Exclude - @Id - ObjectId id; - @EqualsAndHashCode.Exclude - @CreatedDate - Instant created; - @EqualsAndHashCode.Exclude - @LastModifiedDate - Instant lastModified; +data class ElectionResult( + var electionName: String, + var overallResults: Collection, + var constituencyResults: Map> +) { + @Id + var id: ObjectId = ObjectId() - String electionName; - Collection overallResults; - Map> constituencyResults; + @CreatedDate + lateinit var created: Instant + + @LastModifiedDate + lateinit var lastModified: Instant } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/PartyInElection.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/PartyInElection.kt index 3e667df..70a54bf 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/PartyInElection.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/PartyInElection.kt @@ -1,48 +1,31 @@ -package de.twomartens.wahlrecht.model.db; +package de.twomartens.wahlrecht.model.db -import java.time.Instant; -import java.util.Map; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -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.index.CompoundIndex; -import org.springframework.data.mongodb.core.mapping.Document; +import lombok.EqualsAndHashCode +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 +import java.time.Instant @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}") -public class PartyInElection { +data class PartyInElection( + var abbreviation: String, + var electionName: String, + var name: String, + var overallNomination: Nomination, + var constituencyNominations: Map +) { + @Id + var id: ObjectId = ObjectId() - @EqualsAndHashCode.Exclude - @Id - ObjectId id; - @EqualsAndHashCode.Exclude - @CreatedDate - Instant created; - @EqualsAndHashCode.Exclude - @LastModifiedDate - Instant lastModified; + @EqualsAndHashCode.Exclude + @CreatedDate + lateinit var created: Instant - String abbreviation; - String electionName; - String name; - Nomination overallNomination; - Map constituencyNominations; + @EqualsAndHashCode.Exclude + @LastModifiedDate + lateinit var lastModified: Instant } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/VotingResult.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/VotingResult.kt index b3052f6..b11fdea 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/VotingResult.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/db/VotingResult.kt @@ -1,29 +1,10 @@ -package de.twomartens.wahlrecht.model.db; +package de.twomartens.wahlrecht.model.db -import java.util.Map; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -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 votesPerPosition; -} +data class VotingResult( + var electionName: String, + var partyAbbreviation: String, + var nominationName: String, + var votesOnNomination: Int = 0, + var votesThroughHealing: Int = 0, + var votesPerPosition: Map +) diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Candidate.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Candidate.kt index 587b64b..49d3876 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Candidate.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Candidate.kt @@ -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) diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Constituency.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Constituency.kt index 81440d3..9a0750e 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Constituency.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Constituency.kt @@ -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) { - - @Override - public String toString() { - return name; - } +data class Constituency(val electionName: String, val number: Int, val name: String, val numberOfSeats: Int) { + override fun toString(): String { + return name + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ConstituencyId.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ConstituencyId.kt index 377af15..8e5f875 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ConstituencyId.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ConstituencyId.kt @@ -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) diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Elected.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Elected.kt index 672e1c3..0dac177 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Elected.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Elected.kt @@ -1,8 +1,8 @@ -package de.twomartens.wahlrecht.model.internal; +package de.twomartens.wahlrecht.model.internal -public enum Elected { - NOT_ELECTED, - CONSTITUENCY, - OVERALL_NOMINATION_ORDER, - OVERALL_VOTE_ORDER +enum class Elected { + NOT_ELECTED, + CONSTITUENCY, + OVERALL_NOMINATION_ORDER, + OVERALL_VOTE_ORDER } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectedCandidate.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectedCandidate.kt index 217cc41..6aaa61f 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectedCandidate.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectedCandidate.kt @@ -1,13 +1,6 @@ -package de.twomartens.wahlrecht.model.internal; +package de.twomartens.wahlrecht.model.internal - -public record ElectedCandidate(Candidate candidate, Elected elected) { - - public String name() { - return candidate.name(); - } - - public String profession() { - return candidate.profession(); - } +data class ElectedCandidate(val candidate: Candidate, val elected: Elected) { + val name by candidate::name + val profession by candidate::profession } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectionResult.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectionResult.kt index 791c9ad..d2eb7ea 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectionResult.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/ElectionResult.kt @@ -1,9 +1,7 @@ -package de.twomartens.wahlrecht.model.internal; +package de.twomartens.wahlrecht.model.internal -import java.util.Collection; -import java.util.Map; - -public record ElectionResult(String electionName, Collection overallResults, - Map> constituencyResults) { - -} +data class ElectionResult( + val electionName: String, + val overallResults: Collection, + val constituencyResults: Map> +) \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Nomination.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Nomination.kt index cb92bab..8f8d913 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Nomination.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/Nomination.kt @@ -23,8 +23,8 @@ data class Nomination(val id: NominationId, val supportVotesOnNomination: Boolea return positionToCandidate.values.toList() } - fun getCandidate(position: Int): Candidate? { - return positionToCandidate[position] + fun getCandidate(position: Int): Candidate { + return positionToCandidate[position]!! } fun supportsVotesOnNomination(): Boolean { diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/PartyId.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/PartyId.kt index d991119..affe8a1 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/PartyId.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/model/internal/PartyId.kt @@ -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) \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractHealthIndicator.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractHealthIndicator.kt index 2e6fbc2..4e56541 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractHealthIndicator.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractHealthIndicator.kt @@ -1,84 +1,83 @@ -package de.twomartens.wahlrecht.monitoring.actuator; +package de.twomartens.wahlrecht.monitoring.actuator -import java.io.Closeable; -import java.io.IOException; -import java.time.Clock; -import java.time.Duration; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.slf4j.MDC; -import org.slf4j.MDC.MDCCloseable; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.Status; +import mu.KotlinLogging +import org.slf4j.MDC +import org.springframework.boot.actuate.health.Health +import org.springframework.boot.actuate.health.HealthIndicator +import org.springframework.boot.actuate.health.Status +import java.io.IOException +import java.time.Clock +import java.time.Duration +import java.util.* -@Slf4j -@RequiredArgsConstructor -public abstract class AbstractHealthIndicator implements HealthIndicator { +abstract class AbstractHealthIndicator( + private val clock: Clock, + private val preparable: Preparable +) : HealthIndicator { - public static final String HOST = "localhost"; - public static final String HTTP_PREFIX = "http://"; - 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 val logStatusDownMessage = "health indicator '${indicatorName()}' invoked with status '${Status.DOWN.code}'" + private val logStatusUpMessage = "health indicator '${indicatorName()}' invoked with status '${Status.UP.code}'" - private final String logStatusDownMessage = String.format("health indicator '%s' invoked with status '%s'", - 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; + private var firstTime = true - /** - * main method that determines the health of the service - */ - protected abstract Health determineHealth(); + /** + * main method that determines the health of the service + */ + protected abstract fun determineHealth(): Health - @Override - public Health health() { - try (Closeable ignored = preparable.prepare()) { - Health result = null; - Exception exception = null; - long start = clock.millis(); - try { - result = determineHealth(); - } catch (RuntimeException e) { - exception = e; - result = Health.down().withException(e).build(); - } finally { - logInvocation(result, exception, start, clock.millis()); - } - return result; - } catch (IOException e) { - log.error("unexpected exception occurred", e); - return Health.down(e).build(); + override fun health(): Health { + try { + preparable.prepare().use { + var result: Health? = null + var exception: Exception? = null + val start = clock.millis() + try { + result = determineHealth() + } catch (e: RuntimeException) { + exception = e + result = Health.down().withException(e).build() + } finally { + logInvocation(result, exception, start, clock.millis()) + } + return result!! + } + } catch (e: IOException) { + log.error("unexpected exception occurred", e) + return Health.down(e).build() + } } - } - private void logInvocation(Health health, Exception exception, long start, long end) { - Duration duration = Duration.ofMillis(end - start); - try (MDCCloseable ignored = MDC.putCloseable("event.duration", Long.toString(duration.toNanos()))) { - if (exception != null || health == null) { - log.error(logStatusDownMessage, exception); - firstTime = true; - } else if (health.getStatus() == Status.DOWN) { - log.warn(logStatusDownMessage); - firstTime = true; - } else if (firstTime) { - log.info(logStatusUpMessage); - firstTime = false; - } else { - log.trace(logStatusUpMessage); - } + private fun logInvocation(health: Health?, exception: Exception?, start: Long, end: Long) { + val duration = Duration.ofMillis(end - start) + MDC.putCloseable("event.duration", duration.toNanos().toString()).use { + if (exception != null || health == null) { + log.error(logStatusDownMessage, exception) + firstTime = true + } else if (health.status === Status.DOWN) { + log.warn(logStatusDownMessage) + firstTime = true + } else if (firstTime) { + log.info(logStatusUpMessage) + firstTime = false + } else { + log.trace(logStatusUpMessage) + } + } } - } - private String indicatorName() { - return this.getClass().getSimpleName() - .replace("HealthIndicator", "") - .toLowerCase(); - } + private fun indicatorName(): String { + return this.javaClass.getSimpleName() + .replace("HealthIndicator", "") + .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 {} + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractStatusProbeHealthIndicator.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractStatusProbeHealthIndicator.kt index 0cd131e..78a063c 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractStatusProbeHealthIndicator.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/AbstractStatusProbeHealthIndicator.kt @@ -1,37 +1,30 @@ -package de.twomartens.wahlrecht.monitoring.actuator; +package de.twomartens.wahlrecht.monitoring.actuator -import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe; -import java.time.Clock; -import java.util.Optional; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.Health.Builder; -import org.springframework.boot.actuate.health.HealthIndicator; +import de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbe +import org.springframework.boot.actuate.health.Health +import org.springframework.boot.actuate.health.HealthIndicator +import java.time.Clock -@Slf4j -public abstract class AbstractStatusProbeHealthIndicator extends AbstractHealthIndicator - implements HealthIndicator { - - public static final String MESSAGE_KEY = "message"; - public static final String LAST_STATUS_CHANGE_KEY = "lastStatusChange"; - - private final StatusProbe statusProbe; - - public AbstractStatusProbeHealthIndicator(Clock timeProvider, Preparable headerInterceptor, - StatusProbe statusProbe) { - super(timeProvider, headerInterceptor); - this.statusProbe = statusProbe; - } - - @Override - protected Health determineHealth() { - Builder healthBuilder = Health.status(statusProbe.getStatus()); - 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(); - } +abstract class AbstractStatusProbeHealthIndicator( + timeProvider: Clock, headerInterceptor: Preparable, + private val statusProbe: StatusProbe +) : AbstractHealthIndicator(timeProvider, headerInterceptor), HealthIndicator { + override fun determineHealth(): Health { + val healthBuilder = Health.status(statusProbe.status) + if (statusProbe.lastStatusChange != null) { + healthBuilder.withDetail(LAST_STATUS_CHANGE_KEY, statusProbe.lastStatusChange) + } + if (statusProbe.throwable != null) { + healthBuilder.withException(statusProbe.throwable) + } + if (statusProbe.message != null) { + healthBuilder.withDetail(MESSAGE_KEY, statusProbe.message) + } + return healthBuilder.build() + } + companion object { + const val MESSAGE_KEY = "message" + const val LAST_STATUS_CHANGE_KEY = "lastStatusChange" + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/Preparable.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/Preparable.kt index 993b3b8..dc44f1b 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/Preparable.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/Preparable.kt @@ -1,10 +1,7 @@ -package de.twomartens.wahlrecht.monitoring.actuator; +package de.twomartens.wahlrecht.monitoring.actuator -import java.io.Closeable; - -@FunctionalInterface -public interface Preparable { - - Closeable prepare(); +import java.io.Closeable +fun interface Preparable { + fun prepare(): Closeable } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/RestHealthIndicator.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/RestHealthIndicator.kt index 04da789..bef5c5c 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/RestHealthIndicator.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/actuator/RestHealthIndicator.kt @@ -1,66 +1,56 @@ -package de.twomartens.wahlrecht.monitoring.actuator; +package de.twomartens.wahlrecht.monitoring.actuator -import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.actuate.health.HealthIndicator; -import org.springframework.boot.actuate.health.Status; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; - -import java.security.SecureRandom; -import java.time.Clock; -import java.util.Optional; +import de.twomartens.wahlrecht.interceptor.HeaderInterceptorRest +import org.springframework.boot.actuate.health.Health +import org.springframework.boot.actuate.health.HealthIndicator +import org.springframework.boot.actuate.health.Status +import org.springframework.boot.autoconfigure.web.ServerProperties +import org.springframework.stereotype.Component +import org.springframework.web.client.RestTemplate +import java.security.SecureRandom +import java.time.Clock /** * A Health check which checks if the rest services are working. - *

+ * + * * If you have a complex service, you should think about an easy greeting or echo service, which * only tests the * network/service stack and not the full application. - *

+ * + * * The health check will be called by kubernetes to check if the container/pod should be in load * balancing. It is possible * to have as much health checks as you like. - *

+ * + * * There should be a health check which is ok not before all data is loaded. */ -@Slf4j @Component -public class RestHealthIndicator extends AbstractHealthIndicator implements HealthIndicator { +class RestHealthIndicator( + clock: Clock, interceptor: HeaderInterceptorRest, + serverProperties: ServerProperties, private val restTemplateRestHealthIndicator: RestTemplate +) : AbstractHealthIndicator(clock, Preparable { interceptor.markAsHealthCheck() }), HealthIndicator { + private val randomizer = SecureRandom() + private val urlPrefix: String = (HTTP_PREFIX + HOST + HOST_PORT_SEPARATOR + + serverProperties.port + + URL_PATH + PARAMETER_SEPARATOR + GET_PARAMETER) - private static final String URL_PATH = "/wahlrecht/v1/healthCheck"; - private static final String GET_PARAMETER = "message="; - - private final SecureRandom randomizer = new SecureRandom(); - private final RestTemplate restTemplateRestHealthIndicator; - private final String urlPrefix; - - 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 - */ - @Override - protected Health determineHealth() { - String random = Integer.toString(randomizer.nextInt(100000, 999999)); - String url = urlPrefix + "{random}"; - ResponseEntity response = restTemplateRestHealthIndicator.getForEntity(url, String.class, random); - Status status = Optional.ofNullable(response.getBody()) - .filter(random::equals) - .map(m -> Status.UP) - .orElse(Status.DOWN); - return Health.status(status) - .withDetail(DETAIL_ENDPOINT_KEY, url) - .build(); - } + /** + * main method that determines the health of the service + */ + override fun determineHealth(): Health { + val random = randomizer.nextInt(100000, 999999).toString() + val url = "$urlPrefix{random}" + val response = restTemplateRestHealthIndicator.getForEntity(url, String::class.java, random) + val status = if (response.body == random) Status.UP else Status.DOWN + return Health.status(status) + .withDetail(DETAIL_ENDPOINT_KEY, url) + .build() + } + companion object { + private const val URL_PATH = "/wahlrecht/v1/healthCheck" + private const val GET_PARAMETER = "message=" + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/CountBasedStatusProbe.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/CountBasedStatusProbe.kt index 3ebf5d4..f2440f5 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/CountBasedStatusProbe.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/CountBasedStatusProbe.kt @@ -1,33 +1,25 @@ -package de.twomartens.wahlrecht.monitoring.statusprobe; +package de.twomartens.wahlrecht.monitoring.statusprobe -import java.time.Clock; -import java.util.concurrent.atomic.AtomicInteger; -import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.health.Status +import java.time.Clock +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); - - private final int maxFailureCount; - - 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) { - super.setStatus(status, throwable, message); - } - } else if (status == Status.UP) { - this.failureCount.set(0); - super.setStatus(status, throwable, message); + @Synchronized + override fun setStatus(status: Status, throwable: Throwable?, message: String?) { + if (status === Status.DOWN) { + val failureCount = failureCount.incrementAndGet() + if (failureCount > maxFailureCount) { + super.setStatus(status, throwable, message) + } + } else if (status === Status.UP) { + failureCount.set(0) + super.setStatus(status, throwable, message) + } } - } - - } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/PercentageBasedStatusProbe.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/PercentageBasedStatusProbe.kt index fac49f6..19c886f 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/PercentageBasedStatusProbe.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/PercentageBasedStatusProbe.kt @@ -1,63 +1,51 @@ -package de.twomartens.wahlrecht.monitoring.statusprobe; +package de.twomartens.wahlrecht.monitoring.statusprobe -import java.time.Clock; -import java.time.Duration; -import org.springframework.boot.actuate.health.Status; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.boot.actuate.health.Status +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler +import java.time.Clock +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. * 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. */ -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; - - 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, - ThreadPoolTaskScheduler threadPoolTaskScheduler, Duration schedulePeriod, StatusProbeCriticality criticality, - String name, StatusProbeLogger statusProbeLogger) { - super(clock, criticality, name, statusProbeLogger); - this.maxFailurePercent = maxFailurePercent; - scheduleTask(threadPoolTaskScheduler, schedulePeriod); - } - - @Override - protected synchronized void setStatus(Status status, Throwable throwable, String message) { - if (status == Status.DOWN) { - downCount++; - this.throwable = throwable; - this.message = message; + init { + scheduleTask(threadPoolTaskScheduler, schedulePeriod) } - requestCount++; - } - private void reset() { - requestCount = 0; - downCount = 0; - throwable = null; - message = null; - } - - public synchronized void runScheduledTask() { - if (requestCount > 0 && (downCount * 100.0 / requestCount) > maxFailurePercent) { - super.setStatus(Status.DOWN, throwable, message); - } else { - super.setStatus(Status.UP, null, null); + @Synchronized + override fun setStatus(status: Status, throwable: Throwable?, message: String?) { + if (status === Status.DOWN) { + downCount++ + this.temporaryThrowable = throwable + this.temporaryMessage = message + } + requestCount++ } - reset(); - } + private fun reset() { + requestCount = 0 + downCount = 0 + } + + @Synchronized + override fun runScheduledTask() { + if (requestCount > 0 && downCount * 100.0 / requestCount > maxFailurePercent) { + super.setStatus(Status.DOWN, temporaryThrowable, temporaryMessage) + } else { + super.setStatus(Status.UP, null, null) + } + reset() + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/ScheduledStatusProbe.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/ScheduledStatusProbe.kt index da13115..4f28b12 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/ScheduledStatusProbe.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/ScheduledStatusProbe.kt @@ -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.support.PeriodicTrigger; - -public interface ScheduledStatusProbe { - - void runScheduledTask(); - - default void scheduleTask(ThreadPoolTaskScheduler threadPoolTaskScheduler, - Duration schedulePeriod) { - PeriodicTrigger periodicTrigger = new PeriodicTrigger( - Duration.ofSeconds(schedulePeriod.toSeconds())); - threadPoolTaskScheduler.schedule(this::runScheduledTask, periodicTrigger); - } +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler +import org.springframework.scheduling.support.PeriodicTrigger +import java.time.Duration +interface ScheduledStatusProbe { + fun runScheduledTask() + fun scheduleTask( + threadPoolTaskScheduler: ThreadPoolTaskScheduler, + schedulePeriod: Duration + ) { + val periodicTrigger = PeriodicTrigger( + Duration.ofSeconds(schedulePeriod.toSeconds()) + ) + threadPoolTaskScheduler.schedule(periodicTrigger) { runScheduledTask() } + } } + +fun ThreadPoolTaskScheduler.schedule(periodicTrigger: PeriodicTrigger, task: Runnable) { + this.schedule(task, periodicTrigger) +} \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbe.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbe.kt index 1b00e4e..64b5439 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbe.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbe.kt @@ -1,62 +1,59 @@ -package de.twomartens.wahlrecht.monitoring.statusprobe; +package de.twomartens.wahlrecht.monitoring.statusprobe -import java.time.Clock; -import java.time.ZonedDateTime; -import lombok.Getter; -import org.springframework.boot.actuate.health.Status; +import lombok.Getter +import org.springframework.boot.actuate.health.Status +import java.time.Clock +import java.time.ZonedDateTime @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; - - 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) { - if (status != this.status) { - lastStatusChange = ZonedDateTime.now(clock); - statusProbeLogger.logStatusChange(name, message, status, lastStatusChange, throwable); + init { + statusProbeLogger.registerStatusProbe(name, criticality) } - this.status = status; - this.throwable = throwable; - this.message = message; - } - public void up() { - setStatus(Status.UP, null, null); - } + protected open fun setStatus(status: Status, throwable: Throwable?, message: String?) { + if (status !== this.status) { + lastStatusChange = ZonedDateTime.now(clock) + statusProbeLogger.logStatusChange(name, message, status, lastStatusChange, throwable) + } + this.status = status + this.throwable = throwable + this.message = message + } - public void up(String message) { - setStatus(Status.UP, null, message); - } + fun up() { + setStatus(Status.UP, null, null) + } - public void down() { - setStatus(Status.DOWN, null, null); - } + fun up(message: String) { + setStatus(Status.UP, null, message) + } - public void down(Throwable throwable) { - setStatus(Status.DOWN, throwable, null); - } + fun down() { + setStatus(Status.DOWN, null, null) + } - public void down(String message) { - setStatus(Status.DOWN, null, message); - } + fun down(throwable: Throwable) { + setStatus(Status.DOWN, throwable, null) + } - protected void down(Throwable throwable, String message) { - setStatus(Status.DOWN, throwable, message); - } + fun down(message: String) { + setStatus(Status.DOWN, null, message) + } + protected fun down(throwable: Throwable, message: String) { + setStatus(Status.DOWN, throwable, message) + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeCriticality.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeCriticality.kt index 885cddd..36bedca 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeCriticality.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeCriticality.kt @@ -1,7 +1,7 @@ -package de.twomartens.wahlrecht.monitoring.statusprobe; +package de.twomartens.wahlrecht.monitoring.statusprobe - -public enum StatusProbeCriticality { - K1, K2, K3 +enum class StatusProbeCriticality { + K1, + K2, + K3 } - diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeLogger.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeLogger.kt index 8ac5045..11a2e48 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeLogger.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/StatusProbeLogger.kt @@ -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 static de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality.K2; -import static de.twomartens.wahlrecht.monitoring.statusprobe.StatusProbeCriticality.K3; +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +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; -import java.time.ZonedDateTime; -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; +class StatusProbeLogger internal constructor(private val clock: Clock, private val commLog: Logger) { + private val statusProbeToStatus: MutableMap = mutableMapOf() -public class StatusProbeLogger { + constructor(clock: Clock) : this(clock, LogManager.getLogger("statusprobe")) - private static final Marker MARKER = MarkerManager.getMarker("statusprobe"); - - private static final String LABEL_CRITICALITY = "label.status.criticality"; - 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 statusProbeToStatus = new HashMap<>(); - - public StatusProbeLogger(Clock clock) { - this(clock, LogManager.getLogger("statusprobe")); - } - - /** - * only for testing purposes - */ - StatusProbeLogger(Clock clock, Logger commLog) { - this.clock = clock; - 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) { - probeIdent = new ProbeIdent(name, K1); + fun registerStatusProbe(name: String, criticality: StatusProbeCriticality) { + statusProbeToStatus[ProbeIdent(name, criticality)] = Status.UP + logStatusChange(name, "Startup", Status.UP, ZonedDateTime.now(clock), null) } - statusProbeToStatus.put(probeIdent, status); - createLog(message, lastStatusChange, throwable); - } - private ProbeIdent getProbeIdent(String name) { - return statusProbeToStatus.keySet().stream().filter(key -> key.name.equals(name)).findFirst().orElse(null); - } - - private void createLog(String message, ZonedDateTime lastStatusChange, Throwable throwable) { - Status overallStatus = getOverallStatus(); - StatusProbeCriticality criticality = getOverallCriticality(); - if (message == null) { - message = ""; + fun logStatusChange( + name: String, message: String?, status: Status, lastStatusChange: ZonedDateTime?, + throwable: Throwable? + ) { + var probeIdent = getProbeIdent(name) + if (probeIdent == null) { + probeIdent = ProbeIdent(name, StatusProbeCriticality.K1) + } + statusProbeToStatus[probeIdent] = status + createLog(message, lastStatusChange, throwable) } - if (Status.UP.equals(overallStatus)) { - commLog.info(MARKER, new StringMapMessage() - .with(LABEL_CRITICALITY, criticality) - .with(LABEL_STATUS, overallStatus) - .with(LABEL_MESSAGE, message) - .with(LABEL_LAST_STATUS_CHANGE, lastStatusChange)); - } else { - commLog.error(MARKER, new StringMapMessage() - .with(LABEL_CRITICALITY, criticality) - .with(LABEL_STATUS, overallStatus) - .with(LABEL_MESSAGE, message) - .with(LABEL_REASON, getReason()) - .with(LABEL_LAST_STATUS_CHANGE, lastStatusChange), throwable); + + private fun getProbeIdent(name: String): ProbeIdent? { + return statusProbeToStatus.keys.asSequence() + .filter { it.name == name } + .firstOrNull() } - } - private StatusProbeCriticality getOverallCriticality() { - List crits = statusProbeToStatus.keySet().stream().map(key -> key.criticality).toList(); - return crits.contains(K1) ? K1 : crits.contains(K2) ? K2 : K3; - } - - private Status getOverallStatus() { - if (statusProbeToStatus.containsValue(Status.DOWN)) { - return Status.DOWN; + private fun createLog(message: String?, lastStatusChange: ZonedDateTime?, throwable: Throwable?) { + val cleanedMessage = message ?: "" + val overallStatus = overallStatus + val criticality = overallCriticality + if (Status.UP == overallStatus) { + commLog.info( + MARKER, StringMapMessage() + .with(LABEL_CRITICALITY, criticality) + .with(LABEL_STATUS, overallStatus) + .with(LABEL_MESSAGE, cleanedMessage) + .with(LABEL_LAST_STATUS_CHANGE, lastStatusChange) + ) + } else { + commLog.error( + MARKER, StringMapMessage() + .with(LABEL_CRITICALITY, criticality) + .with(LABEL_STATUS, overallStatus) + .with(LABEL_MESSAGE, cleanedMessage) + .with(LABEL_REASON, reason) + .with(LABEL_LAST_STATUS_CHANGE, lastStatusChange), throwable + ) + } } - return Status.UP; - } - private String getReason() { + private val overallCriticality: StatusProbeCriticality + get() { + 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 + } - List probesDown = statusProbeToStatus.entrySet().stream() - .filter(entry -> Status.DOWN.equals(entry.getValue())) - .map(Map.Entry::getKey) - .toList(); + private val overallStatus: Status + get() = if (statusProbeToStatus.containsValue(Status.DOWN)) Status.DOWN + else Status.UP - String reasonK1 = getDownStatusProbes(probesDown, K1); - String reasonK2 = getDownStatusProbes(probesDown, K2); - String reasonK3 = getDownStatusProbes(probesDown, K3); + 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 <= ' ' } + } - return "%s%s%s".formatted(reasonK1, reasonK2, reasonK3).trim(); - } - - private String getDownStatusProbes(List probesDown, StatusProbeCriticality criticality) { - List 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"; + private fun getDownStatusProbes(probesDown: List, criticality: StatusProbeCriticality): String { + val downProbeNames = probesDown.asSequence() + .filter { it.criticality == criticality } + .map { it.name } + .toList() + return if (downProbeNames.isNotEmpty()) { + "$criticality failed: ${downProbeNames.joinToString(separator = ",")}\n" + } else "" } - return ""; - } - public record ProbeIdent(String name, StatusProbeCriticality criticality) { + data class ProbeIdent(val name: String, val criticality: StatusProbeCriticality) - } + 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" + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/TimeBasedStatusProbe.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/TimeBasedStatusProbe.kt index 1fcb76f..4ec2052 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/TimeBasedStatusProbe.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/monitoring/statusprobe/TimeBasedStatusProbe.kt @@ -1,64 +1,48 @@ -package de.twomartens.wahlrecht.monitoring.statusprobe; +package de.twomartens.wahlrecht.monitoring.statusprobe -import java.time.Clock; -import java.time.Duration; -import java.time.ZonedDateTime; -import org.springframework.boot.actuate.health.Status; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.boot.actuate.health.Status +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler +import java.time.Clock +import java.time.Duration +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; - - 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, - ThreadPoolTaskScheduler threadPoolTaskScheduler, Duration schedulePeriod, StatusProbeCriticality criticality, - String name, StatusProbeLogger statusProbeLogger) { - super(clock, criticality, name, statusProbeLogger); - this.clock = clock; - this.maxFailureDuration = maxFailureDuration; - this.lastSuccess = null; - scheduleTask(threadPoolTaskScheduler, schedulePeriod); - } - - @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); + init { + scheduleTask(threadPoolTaskScheduler, schedulePeriod) } - } - private boolean isOverdue() { - if (lastSuccess == null) { - return false; + @Synchronized + override fun setStatus(status: Status, throwable: Throwable?, message: String?) { + if (status === Status.DOWN) { + this.temporaryThrowable = throwable + this.temporaryMessage = message + } else if (status === Status.UP) { + lastSuccess = ZonedDateTime.now(clock) + super.setStatus(status, throwable, message) + } } - Duration timeSinceLastSuccess = Duration.between(lastSuccess, ZonedDateTime.now(clock)); - return maxFailureDuration.minus(timeSinceLastSuccess).isNegative(); - } - public synchronized void runScheduledTask() { - if (isOverdue()) { - super.setStatus(Status.DOWN, throwable, message); + private val isOverdue: Boolean + get() { + if (lastSuccess == null) { + return false + } + val timeSinceLastSuccess = Duration.between(lastSuccess, ZonedDateTime.now(clock)) + return maxFailureDuration.minus(timeSinceLastSuccess).isNegative + } + + @Synchronized + override fun runScheduledTask() { + if (isOverdue) { + super.setStatus(Status.DOWN, temporaryThrowable, temporaryMessage) + } } - } - - } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt index 39f6b5e..f36e3c3 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/CandidateRepository.kt @@ -1,10 +1,6 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.Candidate; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.Candidate +import org.springframework.data.mongodb.repository.MongoRepository -public interface CandidateRepository extends MongoRepository { - - Optional findByName(String name); -} +interface CandidateRepository : MongoRepository \ No newline at end of file diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt index acb3635..3ee0dba 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ConstituencyRepository.kt @@ -1,13 +1,6 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.Constituency; -import java.util.Collection; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.Constituency +import org.springframework.data.mongodb.repository.MongoRepository -public interface ConstituencyRepository extends MongoRepository { - - Optional findByElectionNameAndNumber(String electionName, int number); - - Collection findByElectionName(String electionName); -} +interface ConstituencyRepository : MongoRepository diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt index 6da5c03..a4218ba 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionRepository.kt @@ -1,10 +1,6 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.Election; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.Election +import org.springframework.data.mongodb.repository.MongoRepository -public interface ElectionRepository extends MongoRepository { - - Optional findByName(String name); -} +interface ElectionRepository : MongoRepository diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt index bdbc237..8e95a33 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/ElectionResultRepository.kt @@ -1,7 +1,6 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.ElectionResult; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.ElectionResult +import org.springframework.data.mongodb.repository.MongoRepository -public interface ElectionResultRepository extends MongoRepository { -} +interface ElectionResultRepository : MongoRepository diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt index 20b2f48..b10d410 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/NominationRepository.kt @@ -1,12 +1,6 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.Nomination; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.Nomination +import org.springframework.data.mongodb.repository.MongoRepository -import java.util.Optional; - -public interface NominationRepository extends MongoRepository { - - Optional findByNameAndElectionNameAndPartyAbbreviation(String name, - String electionName, String partyAbbreviation); -} +interface NominationRepository : MongoRepository diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt index 2148721..eeb8007 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/repository/PartyRepository.kt @@ -1,14 +1,8 @@ -package de.twomartens.wahlrecht.repository; +package de.twomartens.wahlrecht.repository -import de.twomartens.wahlrecht.model.db.PartyInElection; -import java.util.Collection; -import java.util.Optional; -import org.springframework.data.mongodb.repository.MongoRepository; +import de.twomartens.wahlrecht.model.db.PartyInElection +import org.springframework.data.mongodb.repository.MongoRepository -public interface PartyRepository extends MongoRepository { - - Optional findByAbbreviationAndElectionName(String abbreviation, - String electionName); - - Collection findByElectionName(String name); +interface PartyRepository : MongoRepository { + fun findByElectionName(name: String): Collection } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/security/SpringPolicyEnforcerFilter.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/security/SpringPolicyEnforcerFilter.kt index a037412..a11b20f 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/security/SpringPolicyEnforcerFilter.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/security/SpringPolicyEnforcerFilter.kt @@ -12,18 +12,16 @@ import org.keycloak.adapters.authorization.spi.ConfigurationResolver import org.keycloak.adapters.authorization.spi.HttpRequest import org.keycloak.representations.adapters.config.PolicyEnforcerConfig import java.io.IOException -import java.util.* -class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolver?) : Filter { - private val policyEnforcer: MutableMap = Collections.synchronizedMap(HashMap()) +class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolver) : Filter { + private val policyEnforcer: MutableMap = mutableMapOf() @Throws(IOException::class, ServletException::class) override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse?, filterChain: FilterChain) { val request = servletRequest as HttpServletRequest val response = servletResponse as HttpServletResponse? - val httpRequest = ServletHttpRequest(request - ) { extractBearerToken(request) } + val httpRequest = ServletHttpRequest(request) { extractBearerToken(request) } val policyEnforcer = getOrCreatePolicyEnforcer(httpRequest) val authzContext = policyEnforcer.enforce(httpRequest, ServletHttpResponse(response)) request.setAttribute(AuthorizationContext::class.java.name, authzContext) @@ -55,9 +53,7 @@ class SpringPolicyEnforcerFilter(private val configResolver: ConfigurationResolv } private fun getOrCreatePolicyEnforcer(request: HttpRequest): SpringPolicyEnforcer { - return policyEnforcer.computeIfAbsent(configResolver!!.resolve(request)) { enforcerConfig: PolicyEnforcerConfig -> - createPolicyEnforcer(enforcerConfig) - } + return policyEnforcer.computeIfAbsent(configResolver.resolve(request)) { createPolicyEnforcer(it) } } private fun createPolicyEnforcer(enforcerConfig: PolicyEnforcerConfig): SpringPolicyEnforcer { diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt index b42212b..df3e9c5 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/CandidateService.kt @@ -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.repository.CandidateRepository; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; +import de.twomartens.wahlrecht.model.db.Candidate +import de.twomartens.wahlrecht.repository.CandidateRepository +import org.springframework.stereotype.Service -@RequiredArgsConstructor @Service -public class CandidateService { - - private final CandidateRepository repository; - private Map 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; +class CandidateService(private val repository: CandidateRepository) { + private val candidates: MutableMap by lazy { + fetchCandidates() } - if (existing != null) { - existing.setProfession(candidate.getProfession()); - candidate = 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) { + existing.profession = candidate.profession + result = existing + } + result = repository.save(result) + candidates[candidate.name] = candidate + return result } - candidate = repository.save(candidate); - candidates.put(candidate.getName(), candidate); - return candidate; - } - - private void fetchCandidates() { - candidates = repository.findAll().stream() - .collect(Collectors.toMap(Candidate::getName, Function.identity())); - } + private fun fetchCandidates(): MutableMap { + return repository.findAll().asSequence() + .map { Pair(it.name, it) } + .toMap() + .toMutableMap() + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt index 6a92d13..ced68e6 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ConstituencyService.kt @@ -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.internal.ConstituencyId; -import de.twomartens.wahlrecht.repository.ConstituencyRepository; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Service; +import de.twomartens.wahlrecht.model.db.Constituency +import de.twomartens.wahlrecht.model.internal.ConstituencyId +import de.twomartens.wahlrecht.repository.ConstituencyRepository +import org.springframework.stereotype.Service @Service -@RequiredArgsConstructor -public class ConstituencyService { - - private final ConstituencyRepository repository; - - private Map constituencies; - - public Constituency storeConstituency(@NonNull Constituency constituency) { - if (constituencies == null) { - fetchConstituencies(); +class ConstituencyService(private val repository: ConstituencyRepository) { + private val constituencies: MutableMap by lazy { + fetchConstituencies() } - ConstituencyId constituencyId = new ConstituencyId(constituency.getElectionName(), - constituency.getNumber()); - Constituency existing = constituencies.get(constituencyId); - - if (constituency.equals(existing)) { - return existing; + fun storeConstituency(constituency: Constituency): Constituency { + var result = constituency + val constituencyId = ConstituencyId( + constituency.electionName, + constituency.number + ) + val existing = constituencies[constituencyId] + if (constituency == existing) { + return existing + } + if (existing != null) { + existing.numberOfSeats = constituency.numberOfSeats + existing.name = constituency.name + result = existing + } + val stored = repository.save(result) + constituencies[constituencyId] = stored + return stored } - if (existing != null) { - existing.setNumberOfSeats(constituency.getNumberOfSeats()); - existing.setName(constituency.getName()); - constituency = existing; + private fun fetchConstituencies(): MutableMap { + return repository.findAll().asSequence() + .map { Pair(ConstituencyId(electionName = it.electionName, number = it.number), it) } + .toMap() + .toMutableMap() } - Constituency stored = repository.save(constituency); - constituencies.put(constituencyId, stored); - - return stored; - } - - private void fetchConstituencies() { - constituencies = repository.findAll().stream() - .collect(Collectors.toMap( - constituency -> new ConstituencyId(constituency.getElectionName(), - constituency.getNumber()), - Function.identity())); - } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt index 8162ce6..dc6a051 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionResultService.kt @@ -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.repository.ElectionResultRepository; -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; +import de.twomartens.wahlrecht.model.db.ElectionResult +import de.twomartens.wahlrecht.repository.ElectionResultRepository +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Service +import org.springframework.web.server.ResponseStatusException -@RequiredArgsConstructor @Service -public class ElectionResultService { - - private final ElectionResultRepository repository; - - private Map electionResults; - - public ElectionResult getElectionResult(String electionName) { - if (electionResults == null) { - fetchResults(); +class ElectionResultService(private val repository: ElectionResultRepository) { + private val electionResults: MutableMap by lazy { + fetchResults() } - ElectionResult electionResult = electionResults.get(electionName); - if (electionResult == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, - "no election result found for %s".formatted(electionName)); - } - return electionResult; - } - - public boolean storeResult(ElectionResult result) { - if (electionResults == null) { - fetchResults(); + fun getElectionResult(electionName: String): ElectionResult { + return electionResults[electionName] + ?: throw ResponseStatusException( + HttpStatus.NOT_FOUND, + "no election result found for $electionName" + ) } - String id = result.getElectionName(); - ElectionResult existing = electionResults.get(id); - boolean needsUpdate = !result.equals(existing); + fun storeResult(electionResult: ElectionResult): Boolean { + var result = electionResult - if (!needsUpdate) { - return false; + val id = result.electionName + val existing = electionResults[id] + val needsUpdate = result != existing + if (!needsUpdate) { + return false + } + var createdNew = true + if (existing != null) { + existing.overallResults = result.overallResults + existing.constituencyResults = result.constituencyResults + result = existing + createdNew = false + } + val stored = repository.save(result) + electionResults[id] = stored + return createdNew } - boolean createdNew = true; - if (existing != null) { - existing.setOverallResults(result.getOverallResults()); - existing.setConstituencyResults(result.getConstituencyResults()); - result = existing; - createdNew = false; + private fun fetchResults(): MutableMap { + return repository.findAll().asSequence() + .map { Pair(it.electionName, it) } + .toMap() + .toMutableMap() } - - ElectionResult stored = repository.save(result); - electionResults.put(id, stored); - - return createdNew; - } - - private void fetchResults() { - electionResults = repository.findAll().stream() - .collect(Collectors.toMap( - ElectionResult::getElectionName, - Function.identity())); - } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt index b60b45d..6e16e36 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/ElectionService.kt @@ -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.model.db.Constituency; -import de.twomartens.wahlrecht.model.db.Election; -import de.twomartens.wahlrecht.repository.ElectionRepository; -import java.util.ArrayList; -import java.util.Collection; -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; +import de.twomartens.wahlrecht.mapper.v1.ElectionMapper +import de.twomartens.wahlrecht.model.db.Constituency +import de.twomartens.wahlrecht.model.db.Election +import de.twomartens.wahlrecht.repository.ElectionRepository +import org.mapstruct.factory.Mappers +import org.springframework.stereotype.Service -@RequiredArgsConstructor @Service -public class ElectionService { - - private final ElectionRepository electionRepository; - private final ConstituencyService constituencyService; - private final ElectionMapper electionMapper = Mappers.getMapper(ElectionMapper.class); - - private Map elections; - - public Collection getElections() { - return electionRepository.findAll(); - } - - public Election getElection(String electionName) { - if (elections == null) { - fetchElections(); - } - return elections.get(electionName); - } - - public de.twomartens.wahlrecht.model.internal.Election getElectionInternal(String electionName) { - return electionMapper.mapToInternal(getElection(electionName)); - } - - public boolean storeElection(@NonNull Election election) { - if (elections == null) { - fetchElections(); - } - boolean createdNew = true; - String electionName = election.getName(); - Election existing = elections.get(electionName); - - if (election.equals(existing)) { - return false; +class ElectionService( + private val electionRepository: ElectionRepository, + private val constituencyService: ConstituencyService +) { + private val electionMapper = Mappers.getMapper(ElectionMapper::class.java) + private val _elections: MutableMap by lazy { + fetchElections() } - Collection constituencies = new ArrayList<>(); - election.getConstituencies() - .forEach(constituency -> constituencies.add( - constituencyService.storeConstituency(constituency))); + val elections: Map = _elections - if (existing != null) { - existing.setDay(election.getDay()); - existing.setTotalNumberOfSeats(election.getTotalNumberOfSeats()); - existing.setVotingThreshold(election.getVotingThreshold()); - election = existing; - createdNew = false; + private fun getElection(electionName: String): Election? { + return elections[electionName] } - election.setConstituencies(constituencies); - Election stored = electionRepository.save(election); - elections.put(electionName, stored); + fun getElectionInternal(electionName: String): de.twomartens.wahlrecht.model.internal.Election { + return electionMapper.mapToInternal(getElection(electionName)!!) + } - return createdNew; - } + fun storeElection(election: Election): Boolean { + var result = election + var createdNew = true + val electionName = election.name + val existing = elections[electionName] + if (election == existing) { + return false + } + val constituencies: MutableCollection = mutableListOf() + result.constituencies + .forEach { constituencies.add(constituencyService.storeConstituency(it)) } + if (existing != null) { + existing.day = election.day + existing.totalNumberOfSeats = election.totalNumberOfSeats + existing.votingThreshold = election.votingThreshold + result = existing + createdNew = false + } + result.constituencies = constituencies + val stored = electionRepository.save(result) + _elections[electionName] = stored + return createdNew + } - private void fetchElections() { - elections = electionRepository.findAll().stream() - .collect(Collectors.toMap(Election::getName, Function.identity())); - } + private fun fetchElections(): MutableMap { + return electionRepository.findAll().asSequence() + .map { Pair(it.name, it) } + .toMap() + .toMutableMap() + } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt index 3035cd8..d646149 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PartyService.kt @@ -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.PartyInElection; -import de.twomartens.wahlrecht.model.internal.PartyId; -import de.twomartens.wahlrecht.repository.PartyRepository; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -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; +import de.twomartens.wahlrecht.model.db.Nomination +import de.twomartens.wahlrecht.model.db.PartyInElection +import de.twomartens.wahlrecht.model.internal.PartyId +import de.twomartens.wahlrecht.repository.PartyRepository +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Service +import org.springframework.web.server.ResponseStatusException -@RequiredArgsConstructor @Service -public class PartyService { +class PartyService( + private val partyRepository: PartyRepository, + private val nominationService: NominationService +) { - private final PartyRepository partyRepository; - private final NominationService nominationService; - private Map parties = null; - - public Collection getPartiesByElectionName(String electionName) { - Collection parties = partyRepository.findByElectionName(electionName); - if (parties.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for " + electionName); + private val parties: MutableMap by lazy { + fetchParties() } - return parties; - } - - public boolean storeParty(PartyInElection partyInElection) { - if (parties == null) { - fetchParties(); + fun getPartiesByElectionName(electionName: String): Collection { + val parties = partyRepository.findByElectionName(electionName) + if (parties.isEmpty()) { + throw ResponseStatusException(HttpStatus.NOT_FOUND, "no party found for $electionName") + } + return parties } - boolean createdNew = true; - PartyId partyId = new PartyId(partyInElection.getElectionName(), - partyInElection.getAbbreviation()); - PartyInElection existing = parties.get(partyId); - boolean needsUpdate = !partyInElection.equals(existing); - if (!needsUpdate) { - return false; + fun storeParty(partyInElection: PartyInElection): Boolean { + var result = partyInElection + var createdNew = true + val partyId = PartyId( + partyInElection.electionName, + partyInElection.abbreviation + ) + val existing = parties[partyId] + val needsUpdate = partyInElection != existing + if (!needsUpdate) { + return false + } + val constituencyNominations: MutableMap = mutableMapOf() + result.constituencyNominations + .forEach { constituencyNominations[it.key] = nominationService.storeNomination(it.value) } + if (existing != null) { + existing.name = partyInElection.name + if (constituencyNominations.isEmpty()) { + constituencyNominations.putAll(existing.constituencyNominations) + } + result = existing + createdNew = false + } + partyInElection.constituencyNominations = constituencyNominations + partyInElection.overallNomination = nominationService.storeNomination(partyInElection.overallNomination)!! + result = partyRepository.save(result) + parties[partyId] = result + return createdNew } - Map constituencyNominations = new HashMap<>(); - partyInElection.getConstituencyNominations() - .forEach((key, value) -> constituencyNominations.put(key, - nominationService.storeNomination(value))); - if (existing != null) { - existing.setName(partyInElection.getName()); - if (constituencyNominations.isEmpty()) { - constituencyNominations.putAll(existing.getConstituencyNominations()); - } - partyInElection = existing; - createdNew = false; + private fun fetchParties(): MutableMap { + val allParties = partyRepository.findAll() + return allParties.asSequence() + .map { Pair(PartyId(electionName = it.electionName, abbreviation = it.abbreviation), it) } + .toMap() + .toMutableMap() } - partyInElection.setConstituencyNominations(constituencyNominations); - partyInElection.setOverallNomination( - nominationService.storeNomination(partyInElection.getOverallNomination())); - partyInElection = partyRepository.save(partyInElection); - parties.put(partyId, partyInElection); - - return createdNew; - } - - private void fetchParties() { - List allParties = partyRepository.findAll(); - parties = allParties.stream() - .collect(Collectors.toMap( - p -> new PartyId(p.getElectionName(), p.getAbbreviation()), - Function.identity())); - } } diff --git a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PropertyReloadService.kt b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PropertyReloadService.kt index 94fcd1b..54adffa 100644 --- a/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PropertyReloadService.kt +++ b/module-server/src/main/kotlin/de/twomartens/wahlrecht/service/PropertyReloadService.kt @@ -1,37 +1,41 @@ -package de.twomartens.wahlrecht.service; +package de.twomartens.wahlrecht.service -import java.util.Set; -import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.apache.logging.log4j.message.StringMapMessage; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Service; +import mu.KotlinLogging +import org.apache.logging.log4j.message.StringMapMessage +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.cloud.context.refresh.ContextRefresher +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.util.concurrent.TimeUnit /** * automatically reloads application*.yaml. reload can also be triggered manually by doing a post * request on http://localhost:12001/actuator/refresh */ -@Log4j2 @Service -@RequiredArgsConstructor -public class PropertyReloadService { - - public static final int REFRESH_SECONDS = 60; - public static final String PARAM_MESSAGE = "message"; - public static final String PARAM_PROPERTIES = "labels.properties"; - - private final ContextRefresher contextRefresher; - - @Scheduled(fixedDelay = REFRESH_SECONDS, initialDelay = REFRESH_SECONDS, timeUnit = TimeUnit.SECONDS) - public void refresh() { - Set properties = contextRefresher.refresh(); - if (!properties.isEmpty()) { - log.info(new StringMapMessage() - .with(PARAM_MESSAGE, "properties changed") - .with(PARAM_PROPERTIES, String.join("\n", properties))); +class PropertyReloadService(@Qualifier("configDataContextRefresher") private val contextRefresher: ContextRefresher) { + @Scheduled( + fixedDelay = REFRESH_SECONDS.toLong(), + initialDelay = REFRESH_SECONDS.toLong(), + timeUnit = TimeUnit.SECONDS + ) + fun refresh() { + val properties = contextRefresher.refresh() + if (properties.isNotEmpty()) { + log.info( + StringMapMessage() + .with(PARAM_MESSAGE, "properties changed") + .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 {} + } } diff --git a/module-server/src/main/resources/application.yaml b/module-server/src/main/resources/application.yaml index 663eb6f..3066fae 100644 --- a/module-server/src/main/resources/application.yaml +++ b/module-server/src/main/resources/application.yaml @@ -41,6 +41,11 @@ spring: de.twomartens.wahlrecht: defaultTimeZone: Europe/Berlin greeting: "Hello %s" + statusprobe: + scheduleDuration: 60 + maxKafkaFailureDuration: 5 + maxBlobFailureCount: 5 + maxFailurePercent: 50 springdoc: swagger-ui: diff --git a/module-server/src/test/kotlin/de/twomartens/wahlrecht/service/CalculationServiceTest.kt b/module-server/src/test/kotlin/de/twomartens/wahlrecht/service/CalculationServiceTest.kt index fe1609f..e9d7d36 100644 --- a/module-server/src/test/kotlin/de/twomartens/wahlrecht/service/CalculationServiceTest.kt +++ b/module-server/src/test/kotlin/de/twomartens/wahlrecht/service/CalculationServiceTest.kt @@ -11,7 +11,6 @@ import org.mockito.Mockito import org.mockito.junit.jupiter.MockitoExtension import java.time.LocalDate import java.time.Month -import java.util.* import kotlin.collections.set @ExtendWith(MockitoExtension::class) @@ -261,12 +260,9 @@ class CalculationServiceTest { } private fun buildCandidates(vararg names: String): Collection { - return Arrays.stream(names).map { name: String? -> - Candidate( - name, - "" - ) - }.toList() + return names + .map { Candidate(it, "") } + .toList() } private fun buildVotesPerPosition(vararg votes: Int): Map { @@ -339,13 +335,13 @@ class CalculationServiceTest { @Test fun shouldCalculateOverallCandidatesCorrectly() { - val seatsPerVotingResult = java.util.Map.of( - SPD_BEZIRK_RESULT, 2, - CDU_BEZIRK_RESULT, 2, - LINKE_BEZIRK_RESULT, 3, - GRUENE_BEZIRK_RESULT, 8, - FDP_BEZIRK_RESULT, 3, - AFD_BEZIRK_RESULT, 3 + val seatsPerVotingResult = mapOf( + Pair(SPD_BEZIRK_RESULT, 2), + Pair(CDU_BEZIRK_RESULT, 2), + Pair(LINKE_BEZIRK_RESULT, 3), + Pair(GRUENE_BEZIRK_RESULT, 8), + Pair(FDP_BEZIRK_RESULT, 3), + Pair(AFD_BEZIRK_RESULT, 3) ) val electedCandidates = setUpConstituencyResults() 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(candidates) .anyMatch { candidate -> - (candidate.name().equals(GOTTLIEB_GABOR) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == GOTTLIEB_GABOR + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(RIEGEL_ANN_KATHRIN) - && candidate.elected === Elected.OVERALL_VOTE_ORDER) + (candidate.name == RIEGEL_ANN_KATHRIN + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } .anySatisfy { id, candidates -> @@ -377,14 +373,13 @@ class CalculationServiceTest { .isEqualTo(CDU_BEZIRK.id) Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(HOEFLICH_JUTTA) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == HOEFLICH_JUTTA + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name() - .equals(DR_LANGHEIN_A_W_HEINRICH) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == DR_LANGHEIN_A_W_HEINRICH + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } .anySatisfy { id, candidates -> @@ -392,18 +387,18 @@ class CalculationServiceTest { .isEqualTo(LINKE_BEZIRK.id) Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(PAGELS_MANUELA) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == PAGELS_MANUELA + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(WIEGMANN_ROLAND) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == WIEGMANN_ROLAND + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(KLEINERT_MIKEY) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == KLEINERT_MIKEY + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } .anySatisfy { id, candidates -> @@ -411,19 +406,18 @@ class CalculationServiceTest { .isEqualTo(FDP_BEZIRK.id) Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(KRUEGER_KLAUS) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == KRUEGER_KLAUS + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(SCHWANKE_BENJAMIN) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == SCHWANKE_BENJAMIN + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name() - .equals(MUELLER_SOENKSEN_BURKHARDT) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == MUELLER_SOENKSEN_BURKHARDT + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } .anySatisfy { id, candidates -> @@ -431,18 +425,18 @@ class CalculationServiceTest { .isEqualTo(AFD_BEZIRK.id) Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(SCHOEMER_DIRK) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == SCHOEMER_DIRK + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(ZIMMERMANN_ELKE) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == ZIMMERMANN_ELKE + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(PILLATZKE_JOERG) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == PILLATZKE_JOERG + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } .anySatisfy { id, candidates -> @@ -450,62 +444,63 @@ class CalculationServiceTest { .isEqualTo(GRUENE_BEZIRK.id) Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(KUHLMANN_DIETMAR) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == KUHLMANN_DIETMAR + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(KLEIN_ROBERT) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == KLEIN_ROBERT + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(ERK_ARAMAK) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == ERK_ARAMAK + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(MARTENS_JIM) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == MARTENS_JIM + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name() - .equals(DR_FISCHER_JOST_LEONHARDT) - && candidate.elected() === Elected.OVERALL_NOMINATION_ORDER) + (candidate.name == DR_FISCHER_JOST_LEONHARDT + && candidate.elected == Elected.OVERALL_NOMINATION_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(KUELL_GABRIELA) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == KUELL_GABRIELA + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(BOHNY_CARL_MARIA) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == BOHNY_CARL_MARIA + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } Assertions.assertThat(candidates) .anyMatch { candidate -> - (candidate.name().equals(DORSCH_SEBASTIAN) - && candidate.elected() === Elected.OVERALL_VOTE_ORDER) + (candidate.name == DORSCH_SEBASTIAN + && candidate.elected == Elected.OVERALL_VOTE_ORDER) } } } private fun setUpConstituencyResults(): Map> { - return java.util.Map.of( - GRUENE_BEZIRK_RESULT.nominationId.partyAbbreviation, listOf( - ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(3), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(5), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(6), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(7), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(11), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(15), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(16), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(20), Elected.CONSTITUENCY), - ElectedCandidate(GRUENE_BEZIRK.getCandidate(22), Elected.CONSTITUENCY) + return mapOf( + Pair( + GRUENE_BEZIRK_RESULT.nominationId.partyAbbreviation, listOf( + ElectedCandidate(GRUENE_BEZIRK.getCandidate(1), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(2), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(3), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(5), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(6), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(7), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(11), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(15), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(16), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(20), Elected.CONSTITUENCY), + ElectedCandidate(GRUENE_BEZIRK.getCandidate(22), Elected.CONSTITUENCY) + ) ) ) }