From bc9ace3ed6d276f3637dde0a0276b82420c4cc5f Mon Sep 17 00:00:00 2001 From: FyloZ Date: Wed, 22 Dec 2021 15:42:48 -0500 Subject: [PATCH] Add a mix initializer to find invalid mix materials positions and fix them --- build.gradle.kts | 24 +++--- docker-compose.yml | 8 +- .../config/ApplicationListeners.kt | 20 ++--- .../config/annotations/RequireDatabase.kt | 10 +++ .../initializers/AbstractInitializer.kt | 12 +++ .../initializers/MaterialTypeInitializer.kt | 57 ++++++++++++++ .../config/initializers/MixInitializer.kt | 78 +++++++++++++++++++ .../service/MaterialTypeService.kt | 30 +------ src/main/resources/application.properties | 2 +- src/main/resources/logback.xml | 9 ++- 10 files changed, 186 insertions(+), 64 deletions(-) create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt create mode 100644 src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt diff --git a/build.gradle.kts b/build.gradle.kts index 6fe585e..22e7087 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,12 +3,12 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile group = "dev.fyloz.colorrecipesexplorer" val kotlinVersion = "1.6.0" -val springBootVersion = "2.5.6" +val springBootVersion = "2.6.1" plugins { // Outer scope variables can't be accessed in the plugins section, so we have to redefine them here val kotlinVersion = "1.6.0" - val springBootVersion = "2.5.6" + val springBootVersion = "2.6.1" id("java") id("org.jetbrains.kotlin.jvm") version kotlinVersion @@ -28,18 +28,18 @@ repositories { dependencies { implementation(platform("org.jetbrains.kotlin:kotlin-bom:${kotlinVersion}")) - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") - implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0") - implementation("javax.xml.bind:jaxb-api:2.3.0") + + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") + implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1") + implementation("io.github.microutils:kotlin-logging-jvm:2.1.21") implementation("io.jsonwebtoken:jjwt-api:0.11.2") implementation("io.jsonwebtoken:jjwt-impl:0.11.2") implementation("io.jsonwebtoken:jjwt-jackson:0.11.2") + implementation("javax.xml.bind:jaxb-api:2.3.0") implementation("org.apache.poi:poi-ooxml:4.1.0") implementation("org.apache.pdfbox:pdfbox:2.0.4") - implementation("org.apache.logging.log4j:log4j-api:2.15.0") - implementation("org.apache.logging.log4j:log4j-to-slf4j:2.15.0") - implementation("dev.fyloz.colorrecipesexplorer:database-manager:5.2.1") + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}") + implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") implementation("org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}") implementation("org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}") @@ -49,13 +49,13 @@ dependencies { implementation("org.springframework.boot:spring-boot-configuration-processor:${springBootVersion}") implementation("org.springframework.boot:spring-boot-devtools:${springBootVersion}") - testImplementation("org.springframework:spring-test:5.3.13") - testImplementation("org.mockito:mockito-inline:3.11.2") testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0") testImplementation("io.mockk:mockk:1.12.0") + testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") + testImplementation("org.mockito:mockito-inline:3.11.2") + testImplementation("org.springframework:spring-test:5.3.13") testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") testImplementation("org.springframework.boot:spring-boot-test-autoconfigure:${springBootVersion}") - testImplementation("org.jetbrains.kotlin:kotlin-test:${kotlinVersion}") runtimeOnly("com.h2database:h2:1.4.199") runtimeOnly("mysql:mysql-connector-java:8.0.22") diff --git a/docker-compose.yml b/docker-compose.yml index 9c03148..9de1c6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,16 @@ version: "3.1" services: - frontend: + cre.frontend: image: fyloz.dev:5443/color-recipes-explorer/frontend:latest ports: - - 4200:80 - database: + - "4200:80" + cre.database: image: mysql command: --default-authentication-plugin=mysql_native_password environment: MYSQL_ROOT_PASSWORD: "pass" MYSQL_DATABASE: "cre" ports: - - 3306:3306 + - "3306:3306" diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt index 353ee71..2f92bf5 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/ApplicationListeners.kt @@ -1,18 +1,16 @@ package dev.fyloz.colorrecipesexplorer.config +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.config.initializers.AbstractInitializer import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties -import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties import dev.fyloz.colorrecipesexplorer.emergencyMode import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.restartApplication -import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService import dev.fyloz.colorrecipesexplorer.service.config.ConfigurationService import org.slf4j.Logger import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent -import org.springframework.boot.context.event.ApplicationReadyEvent import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Profile import org.springframework.core.Ordered import org.springframework.core.annotation.Order import javax.annotation.PostConstruct @@ -20,15 +18,13 @@ import kotlin.concurrent.thread @Configuration @Order(Ordered.HIGHEST_PRECEDENCE) -@Profile("!emergency") +@RequireDatabase class ApplicationReadyListener( - private val materialTypeService: MaterialTypeService, private val configurationService: ConfigurationService, - private val materialTypeProperties: MaterialTypeProperties, private val creProperties: CreProperties, private val logger: Logger -) : ApplicationListener { - override fun onApplicationEvent(event: ApplicationReadyEvent) { +) : AbstractInitializer() { + override fun initialize() { if (emergencyMode) { logger.error("Emergency mode is enabled, default material types will not be created") thread { @@ -40,15 +36,9 @@ class ApplicationReadyListener( } initDatabaseConfigurations() - initMaterialTypes() CRE_PROPERTIES = creProperties } - private fun initMaterialTypes() { - logger.info("Initializing system material types") - materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes) - } - private fun initDatabaseConfigurations() { configurationService.initializeProperties { !it.file } } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt new file mode 100644 index 0000000..019d055 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/annotations/RequireDatabase.kt @@ -0,0 +1,10 @@ +package dev.fyloz.colorrecipesexplorer.config.annotations + +import org.springframework.context.annotation.Profile +import java.lang.annotation.Inherited + +@Profile("!emergency") +@Inherited +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class RequireDatabase \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt new file mode 100644 index 0000000..fbeaf56 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt @@ -0,0 +1,12 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import org.springframework.boot.context.event.ApplicationReadyEvent +import org.springframework.context.ApplicationListener + +abstract class AbstractInitializer : ApplicationListener { + override fun onApplicationEvent(event: ApplicationReadyEvent) { + initialize() + } + + abstract fun initialize() +} diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt new file mode 100644 index 0000000..1bb99d2 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MaterialTypeInitializer.kt @@ -0,0 +1,57 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties +import dev.fyloz.colorrecipesexplorer.model.MaterialType +import dev.fyloz.colorrecipesexplorer.model.materialType +import dev.fyloz.colorrecipesexplorer.service.MaterialTypeService +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration + +@Configuration +@RequireDatabase +class MaterialTypeInitializer( + private val materialTypeService: MaterialTypeService, + private val materialTypeProperties: MaterialTypeProperties +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing material type initializer...") + ensureSystemMaterialTypesExists() + logger.debug("System material types are up to date!") + } + + private fun ensureSystemMaterialTypesExists() { + val systemTypes = materialTypeProperties.systemTypes.map { it.toMaterialType() } + val oldSystemTypes = materialTypeService.getAllSystemTypes().toMutableSet() + + fun saveOrUpdateSystemType(type: MaterialType) { + if (materialTypeService.existsByName(type.name)) { + with(materialTypeService.getByName(type.name)) { + if (!this.systemType) { + logger.info("Material type '${type.name}' already exists and will be flagged as a system type") + materialTypeService.update(this.copy(systemType = true)) + } else { + logger.debug("System material type '${type.name}' already exists") + } + } + } else { + logger.info("System material type '${type.name}' will be created") + materialTypeService.save(type) + } + } + + // Save new system types + systemTypes.forEach { + saveOrUpdateSystemType(it) + oldSystemTypes.removeIf { c -> c.name == it.name } + } + + // Remove old system types + oldSystemTypes.forEach { + logger.info("Material type '${it.name}' is not a system type anymore") + materialTypeService.update(materialType(it, newSystemType = false)) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt new file mode 100644 index 0000000..12908cb --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt @@ -0,0 +1,78 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.model.Mix +import dev.fyloz.colorrecipesexplorer.model.MixMaterial +import dev.fyloz.colorrecipesexplorer.service.MixService +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration +import java.util.* + +@Configuration +@RequireDatabase +class MixInitializer( + private val mixService: MixService +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing mix initializer...") + fixAllPositions() + } + + private fun fixAllPositions() { + logger.debug("Validating mix materials positions...") + + mixService.getAll() + .filter { mix -> + mix.mixMaterials.any { it.position == 0 } + } + .forEach(this::fixMixPositions) + + logger.debug("Mix materials positions are valid!") + } + + private fun fixMixPositions(mix: Mix) { + val maxPosition = mix.mixMaterials.maxOf { it.position } + val nextPosition = maxPosition + 1 + + logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:") + + val invalidMixMaterials: Collection = with(mix.mixMaterials.filter { it.position == 0 }) { + if (nextPosition == 1 && this.size > 1) { + orderMixMaterials(this) + } else { + this + } + } + + val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, nextPosition) + val updatedMixMaterials = mergeMixMaterials(mix.mixMaterials, fixedMixMaterials) + + with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) { + mixService.update(this) + } + } + + private fun increaseMixMaterialsPosition(mixMaterials: Iterable, firstPosition: Int) = + mixMaterials + .mapIndexed { index, mixMaterial -> mixMaterial.copy(position = firstPosition + index) } + .onEach { + logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}") + } + + private fun mergeMixMaterials(mixMaterials: Iterable, updatedMixMaterials: Iterable) = + mixMaterials + .filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } } + .plus(updatedMixMaterials) + + private fun orderMixMaterials(mixMaterials: Collection) = + LinkedList(mixMaterials).apply { + while (this.peek().material.materialType?.usePercentages == true) { + // The first mix material can't use percents, so move it to the end of the queue + val pop = this.pop() + this.add(pop) + logger.debug("\tMaterial ${pop.material.id} (${pop.material.name}) uses percents, moving to the end of the queue") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt index 8d0ce96..2b7eec1 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/service/MaterialTypeService.kt @@ -1,7 +1,5 @@ package dev.fyloz.colorrecipesexplorer.service -import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties -import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.model.* import dev.fyloz.colorrecipesexplorer.model.validation.isNotNullAndNotBlank import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository @@ -21,9 +19,6 @@ interface MaterialTypeService : /** Gets all material types who are not a system type. */ fun getAllNonSystemType(): Collection - - /** Saves and update the system material types. */ - fun saveSystemTypes(systemTypeProperties: Collection) } @Service @@ -71,34 +66,11 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma throw materialTypePrefixAlreadyExistsException(entity.prefix) } - return super.update(entity) + return super.update(entity) } override fun delete(entity: MaterialType) { if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialTypeException(entity) super.delete(entity) } - - override fun saveSystemTypes(systemTypeProperties: Collection) { - val systemTypes = systemTypeProperties.map { it.toMaterialType() } - val oldSystemTypes = getAllSystemTypes().toMutableSet() - - fun saveOrUpdateSystemType(type: MaterialType) { - if (existsByName(type.name)) { - val persistedMaterialType = getByName(type.name) - update(materialType(type, newId = persistedMaterialType.id, newSystemType = true)) - } else { - save(type) - } - } - - // Save new system types - systemTypes.forEach { - saveOrUpdateSystemType(it) - oldSystemTypes.removeIf { c -> c.name == it.name } - } - - // Remove old system types - oldSystemTypes.forEach { update(materialType(it, newSystemType = false)) } - } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 18852ab..1aa848b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -31,4 +31,4 @@ spring.jackson.deserialization.fail-on-null-for-primitives=true spring.jackson.default-property-inclusion=non_null spring.profiles.active=@spring.profiles.active@ -spring.datasource.continue-on-error=true +spring.sql.init.continue-on-error=true diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index b5ccfc6..1ecd253 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -5,9 +5,12 @@ - %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable + %green(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{36}): %msg%n%throwable + + INFO + @@ -21,12 +24,12 @@ - %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger.%M - %msg%n + %d{dd-MM-yyyy HH:mm:ss.SSS} [%thread] %-5level %logger{36}.%M - %msg%n - +