Add a mix initializer to find invalid mix materials positions and fix them
This commit is contained in:
parent
d0d35fa832
commit
bc9ace3ed6
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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<ApplicationReadyEvent> {
|
||||
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 }
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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<ApplicationReadyEvent> {
|
||||
override fun onApplicationEvent(event: ApplicationReadyEvent) {
|
||||
initialize()
|
||||
}
|
||||
|
||||
abstract fun initialize()
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MixMaterial> = 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<MixMaterial>, 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<MixMaterial>, updatedMixMaterials: Iterable<MixMaterial>) =
|
||||
mixMaterials
|
||||
.filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } }
|
||||
.plus(updatedMixMaterials)
|
||||
|
||||
private fun orderMixMaterials(mixMaterials: Collection<MixMaterial>) =
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<MaterialType>
|
||||
|
||||
/** Saves and update the system material types. */
|
||||
fun saveSystemTypes(systemTypeProperties: Collection<MaterialTypeProperties.MaterialTypeProperty>)
|
||||
}
|
||||
|
||||
@Service
|
||||
|
@ -71,34 +66,11 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
|||
throw materialTypePrefixAlreadyExistsException(entity.prefix)
|
||||
}
|
||||
|
||||
return super<AbstractExternalNamedModelService>.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<MaterialTypeProperties.MaterialTypeProperty>) {
|
||||
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)) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,9 +5,12 @@
|
|||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>
|
||||
%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
|
||||
</Pattern>
|
||||
</layout>
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||
<level>INFO</level>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
|
@ -21,12 +24,12 @@
|
|||
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<Pattern>
|
||||
%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
|
||||
</Pattern>
|
||||
</encoder>
|
||||
|
||||
</appender>
|
||||
|
||||
<logger name="dev.fyloz.colorrecipesexplorer" level="debug"/>
|
||||
<root level="info">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="LOG_FILE"/>
|
||||
|
|
Loading…
Reference in New Issue