Skip to content

Commit f1c4088

Browse files
authored
Merge pull request #16 from nenadjakic/feature/basic-data-type-support
Added validation for attribute-value entity. Suport basic data type l…
2 parents eea4af5 + e631ede commit f1c4088

20 files changed

+233
-15
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.github.nenadjakic.eav.configuration
2+
3+
class EavPlatformProperties {
4+
}

src/main/kotlin/com/github/nenadjakic/eav/controller/AttributeValueController.kt

+11-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.github.nenadjakic.eav.dto.AttributeValueAddRequest
44
import com.github.nenadjakic.eav.dto.AttributeValueResponse
55
import com.github.nenadjakic.eav.entity.AttributeValue
66
import com.github.nenadjakic.eav.extension.collectionMap
7+
import com.github.nenadjakic.eav.service.AttributeService
78
import com.github.nenadjakic.eav.service.AttributeValueService
89
import io.swagger.v3.oas.annotations.Operation
910
import io.swagger.v3.oas.annotations.responses.ApiResponse
@@ -14,25 +15,27 @@ import org.modelmapper.ModelMapper
1415
import org.springframework.http.ResponseEntity
1516
import org.springframework.security.access.prepost.PreAuthorize
1617
import org.springframework.validation.annotation.Validated
17-
import org.springframework.web.bind.annotation.DeleteMapping
18-
import org.springframework.web.bind.annotation.GetMapping
19-
import org.springframework.web.bind.annotation.PathVariable
20-
import org.springframework.web.bind.annotation.PostMapping
21-
import org.springframework.web.bind.annotation.PutMapping
22-
import org.springframework.web.bind.annotation.RequestBody
23-
import org.springframework.web.bind.annotation.RequestMapping
24-
import org.springframework.web.bind.annotation.RestController
18+
import org.springframework.web.bind.WebDataBinder
19+
import org.springframework.web.bind.annotation.*
2520
import org.springframework.web.servlet.support.ServletUriComponentsBuilder
2621

22+
2723
@Tag(name = "Attribute-value controller", description = "API endpoints for managing values of attributes.")
2824
@RestController
2925
@RequestMapping("/attribute-value")
3026
@Validated
3127
open class AttributeValueController(
3228
val modelMapper: ModelMapper,
29+
val attributeService: AttributeService,
3330
val attributeValueService: AttributeValueService
3431
) {
3532

33+
//@InitBinder(value = ["attributeValueAddRequest"])
34+
protected fun initBinder(binder: WebDataBinder) {
35+
// može i kao bean
36+
//binder.addValidators(AttributeValueValidator(attributeService))
37+
}
38+
3639
@Operation(
3740
operationId = "findAttributeValueById",
3841
summary = "Get attribute-value by id.",

src/main/kotlin/com/github/nenadjakic/eav/controller/EavReadController.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ import org.springframework.web.bind.annotation.RequestParam
1616
*/
1717
interface EavReadController<RE> {
1818

19-
@PreAuthorize("hasRole('READER')")
19+
//@PreAuthorize("hasRole('READER')")
2020
//@PostAuthorize("@attributeSecurityService.canRead(1L)")
2121
@GetMapping(produces = [MediaType.APPLICATION_JSON_VALUE])
2222
fun findAll(): ResponseEntity<List<RE>>
2323

24-
@PreAuthorize("hasRole('READER')")
24+
//@PreAuthorize("hasRole('READER')")
2525
@GetMapping(value = ["/page"], produces = [MediaType.APPLICATION_JSON_VALUE])
2626
fun findPage(@RequestParam pageNumber: Int, @RequestParam(required = false) pageSize: Int?): ResponseEntity<Page<RE>>
2727

28-
@PreAuthorize("hasRole('READER')")
28+
//@PreAuthorize("hasRole('READER')")
2929
@GetMapping(value = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
3030
fun findById(@PathVariable id: Long): ResponseEntity<RE>
3131
}

src/main/kotlin/com/github/nenadjakic/eav/controller/EntityTypeController.kt

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ open class EntityTypeController(
7171
open override fun findById(id: Long): ResponseEntity<EntityTypeResponse> {
7272
val entityType = entityTypeService.findById(id)
7373
val response = if (entityType != null) modelMapper.map(entityType, EntityTypeResponse::class.java) else null
74+
7475
return ResponseEntity.ofNullable(response)
7576
}
7677

src/main/kotlin/com/github/nenadjakic/eav/dto/AttributeValueAddRequest.kt

+6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
package com.github.nenadjakic.eav.dto
22

3+
import com.github.nenadjakic.eav.validator.ExistsAttributeId
4+
import com.github.nenadjakic.eav.validator.ValidAttributeForEntity
5+
import com.github.nenadjakic.eav.validator.ValidAttributeValue
36
import jakarta.validation.constraints.NotNull
47

8+
@ValidAttributeValue
9+
@ValidAttributeForEntity
510
class AttributeValueAddRequest {
611
@NotNull
712
var entityId: Long? = null
813

914
@NotNull
15+
@ExistsAttributeId
1016
var attributeId: Long? = null
1117

1218
var value: String? = null

src/main/kotlin/com/github/nenadjakic/eav/entity/EntityType.kt

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class EntityType : AbstractEntityId<Long>() {
2828
@Column(name = "description", length = 1000)
2929
var description: String? = null
3030

31+
@OneToMany(mappedBy = "entityType")
32+
lateinit var entities: MutableList<com.github.nenadjakic.eav.entity.Entity>
33+
3134
@OneToMany(mappedBy = "entityType")
3235
private val _attributes: MutableSet<Attribute> = mutableSetOf()
3336

src/main/kotlin/com/github/nenadjakic/eav/handler/EavExceptionHandler.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class EavExceptionHandler : ResponseEntityExceptionHandler() {
3939
val errors = ex.bindingResult
4040
.fieldErrors
4141
.stream()
42-
.map { obj: FieldError -> obj.defaultMessage }
42+
.map { obj: FieldError -> obj.field + ":" + obj.defaultMessage }
4343
.collect(Collectors.toList())
4444

4545
val path = (request as ServletWebRequest).request.requestURI
@@ -56,7 +56,7 @@ class EavExceptionHandler : ResponseEntityExceptionHandler() {
5656
@ExceptionHandler(ConstraintViolationException::class)
5757
open fun handleConstraintViolationException(ex: ConstraintViolationException, request: WebRequest?): ResponseEntity<ErrorInfo> {
5858
logger.error("Error occurred.", ex)
59-
return getErrorInfoResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, ex, request as ServletWebRequest)
59+
return getErrorInfoResponseEntity(HttpStatus.BAD_REQUEST, ex, request as ServletWebRequest)
6060
}
6161

6262
private fun getErrorInfoResponseEntity(
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
package com.github.nenadjakic.eav.repository
22

33
import com.github.nenadjakic.eav.entity.Attribute
4+
import org.springframework.data.domain.Page
5+
import org.springframework.data.domain.Pageable
6+
import org.springframework.data.jpa.repository.EntityGraph
47
import org.springframework.data.jpa.repository.JpaRepository
58

6-
interface AttributeRepository : JpaRepository<Attribute, Long>
9+
interface AttributeRepository : JpaRepository<Attribute, Long> {
10+
11+
@EntityGraph(attributePaths = [ "entityType", "metadata" ])
12+
override fun findAll(): List<Attribute>
13+
14+
@EntityGraph(attributePaths = [ "entityType", "metadata" ])
15+
override fun findAll(pageable: Pageable): Page<Attribute>
16+
}

src/main/kotlin/com/github/nenadjakic/eav/repository/AttributeValueRepository.kt

+4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ package com.github.nenadjakic.eav.repository
22

33
import com.github.nenadjakic.eav.entity.AttributeValue
44
import com.github.nenadjakic.eav.entity.EntityAttributeId
5+
import org.springframework.data.jpa.repository.EntityGraph
56
import org.springframework.data.jpa.repository.JpaRepository
67

78
interface AttributeValueRepository : JpaRepository<AttributeValue, EntityAttributeId> {
89

10+
@EntityGraph(
11+
attributePaths = [ "entity", "attribute", "entity.entityType", "attribute.metadata" ],
12+
)
913
fun findByEntityId(entityId: Long): List<AttributeValue>
1014
}

src/main/kotlin/com/github/nenadjakic/eav/repository/EntityTypeRepository.kt

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,14 @@ package com.github.nenadjakic.eav.repository
22

33
import com.github.nenadjakic.eav.entity.EntityType
44
import org.springframework.data.jpa.repository.JpaRepository
5+
import org.springframework.data.jpa.repository.Query
6+
import org.springframework.data.repository.query.Param
57

6-
interface EntityTypeRepository : JpaRepository<EntityType, Long>
8+
interface EntityTypeRepository : JpaRepository<EntityType, Long> {
9+
10+
@Query(value = "select case when count(et) > 0 then true else false end from EntityType et " +
11+
"join Entity e on et.id = e.entityType.id " +
12+
"join Attribute a on et.id = a.entityType.id " +
13+
"where e.id = :entityId and a.id = :attributeId")
14+
fun existsByEntityIdAndAttributeId(@Param("entityId") entityId: Long, @Param("attributeId") attributeId: Long): Boolean
15+
}

src/main/kotlin/com/github/nenadjakic/eav/service/AttributeService.kt

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import org.springframework.stereotype.Service
88

99
@Service
1010
open class AttributeService(private val attributeRepository: AttributeRepository): EavService<Attribute> {
11+
12+
open fun existsById(id: Long): Boolean = attributeRepository.existsById(id)
13+
1114
open override fun findById(id: Long): Attribute? = attributeRepository.findById(id).orElse(null)
1215

1316
open override fun findAll(): List<Attribute> = attributeRepository.findAll()

src/main/kotlin/com/github/nenadjakic/eav/service/EntityTypeService.kt

+2
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ open class EntityTypeService(
2323
open override fun update(entity: EntityType): EntityType = entityTypeRepository.save(entity)
2424

2525
open override fun create(entity: EntityType): EntityType = entityTypeRepository.save(entity)
26+
27+
fun existsByEntityIdAndAttributeId(entityId: Long, attributeId: Long): Boolean = entityTypeRepository.existsByEntityIdAndAttributeId(entityId, attributeId)
2628
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import jakarta.validation.Constraint
4+
import kotlin.annotation.AnnotationRetention.RUNTIME
5+
import kotlin.annotation.AnnotationTarget.*
6+
import kotlin.reflect.KClass
7+
8+
@Target(
9+
FUNCTION,
10+
FIELD,
11+
ANNOTATION_CLASS,
12+
CONSTRUCTOR,
13+
VALUE_PARAMETER,
14+
TYPE_PARAMETER,
15+
PROPERTY_GETTER,
16+
PROPERTY_SETTER
17+
)
18+
@Retention(RUNTIME)
19+
@MustBeDocumented
20+
@Constraint(validatedBy = [ ExistsAttributeIdValidator::class ])
21+
annotation class ExistsAttributeId(
22+
val message: String = "{existsAttributeId}",
23+
val groups: Array<KClass<out Any>> = [],
24+
val payload: Array<KClass<out Any>> = []
25+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import com.github.nenadjakic.eav.service.AttributeService
4+
import jakarta.validation.ConstraintValidator
5+
import jakarta.validation.ConstraintValidatorContext
6+
7+
class ExistsAttributeIdValidator(
8+
private val attributeService: AttributeService
9+
) : ConstraintValidator<ExistsAttributeId, Long> {
10+
override fun isValid(value: Long, context: ConstraintValidatorContext?): Boolean = attributeService.existsById(value)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import jakarta.validation.Constraint
4+
import kotlin.annotation.AnnotationRetention.RUNTIME
5+
import kotlin.annotation.AnnotationTarget.*
6+
import kotlin.reflect.KClass
7+
8+
@Target(
9+
CLASS
10+
)
11+
@Retention(RUNTIME)
12+
@MustBeDocumented
13+
@Constraint(validatedBy = [ ValidAttributeForEntityValidator::class ])
14+
annotation class ValidAttributeForEntity(
15+
val message: String = "{validAttributeForEntity}",
16+
val groups: Array<KClass<out Any>> = [],
17+
val payload: Array<KClass<out Any>> = []
18+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import com.github.nenadjakic.eav.dto.AttributeValueAddRequest
4+
import com.github.nenadjakic.eav.service.EntityTypeService
5+
import jakarta.validation.ConstraintValidator
6+
import jakarta.validation.ConstraintValidatorContext
7+
8+
class ValidAttributeForEntityValidator(
9+
private val entityTypeService: EntityTypeService
10+
) : ConstraintValidator<ValidAttributeForEntity, AttributeValueAddRequest> {
11+
override fun isValid(value: AttributeValueAddRequest, context: ConstraintValidatorContext?): Boolean {
12+
13+
if (value.entityId != null && value.attributeId != null) {
14+
if (!entityTypeService.existsByEntityIdAndAttributeId(value.entityId!!, value.attributeId!!)) {
15+
context!!.disableDefaultConstraintViolation()
16+
context.buildConstraintViolationWithTemplate("{validAttributeForEntity}")
17+
.addPropertyNode("enitityId")
18+
.addPropertyNode("attributeId")
19+
.addConstraintViolation()
20+
return false
21+
}
22+
}
23+
return true
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import jakarta.validation.Constraint
4+
import kotlin.annotation.AnnotationRetention.RUNTIME
5+
import kotlin.annotation.AnnotationTarget.*
6+
import kotlin.reflect.KClass
7+
8+
@Target(TYPE, CLASS)
9+
@Retention(RUNTIME)
10+
@MustBeDocumented
11+
@Constraint(validatedBy = [ ValidAttributeValueValidator::class ])
12+
annotation class ValidAttributeValue(
13+
val message: String = "{validAttributeValue}",
14+
val groups: Array<KClass<out Any>> = [],
15+
val payload: Array<KClass<out Any>> = []
16+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.github.nenadjakic.eav.validator
2+
3+
import com.github.nenadjakic.eav.dto.AttributeValueAddRequest
4+
import com.github.nenadjakic.eav.entity.DataType
5+
import com.github.nenadjakic.eav.service.AttributeService
6+
import com.github.nenadjakic.eav.service.EntityTypeService
7+
import jakarta.validation.ConstraintValidator
8+
import jakarta.validation.ConstraintValidatorContext
9+
import java.time.LocalDate
10+
import java.time.LocalDateTime
11+
import java.time.LocalTime
12+
import java.time.format.DateTimeFormatter
13+
14+
class ValidAttributeValueValidator(
15+
private val attributeService: AttributeService,
16+
private val entityTypeService: EntityTypeService
17+
) : ConstraintValidator<ValidAttributeValue, AttributeValueAddRequest> {
18+
19+
override fun isValid(value: AttributeValueAddRequest, context: ConstraintValidatorContext?): Boolean {
20+
if (value.value == null && value.attributeId == null) {
21+
return true
22+
}
23+
24+
val attribute = attributeService.findById(value.attributeId!!);
25+
if (attribute != null) {
26+
value.value?.let {
27+
if (null == when (attribute.metadata.dataType) {
28+
DataType.INTEGER -> it.toIntOrNull()
29+
DataType.BOOLEAN -> it.toBooleanStrictOrNull()
30+
DataType.DECIMAL -> it.toFloatOrNull()
31+
DataType.DATE -> {
32+
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
33+
try {
34+
LocalDate.parse(it, formatter)
35+
} catch (e: Exception) {
36+
null
37+
}
38+
}
39+
40+
DataType.TIME -> {
41+
val formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
42+
try {
43+
LocalTime.parse(it, formatter)
44+
} catch (e: Exception) {
45+
null
46+
}
47+
}
48+
49+
DataType.DATETIME -> {
50+
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-ddTHH:mm:ss.SSS")
51+
try {
52+
LocalDateTime.parse(it, formatter)
53+
} catch (e: Exception) {
54+
null
55+
}
56+
}
57+
58+
else -> 100
59+
}) {
60+
context!!.disableDefaultConstraintViolation()
61+
context.buildConstraintViolationWithTemplate("{validAttributeValue}")
62+
.addPropertyNode("value")
63+
.addConstraintViolation()
64+
65+
return false
66+
}
67+
}
68+
}
69+
return true
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
existsAttributeId=must exists
2+
validAttributeForEntity=attribute and entity are not suitable
3+
validAttributeValue=attribute value must be valid

src/main/resources/application.properties

+4
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ eav-platform.security.jwt.secret-key=changemeandexternalisemechangemeandexternal
2222
eav-platform.security.jwt.access-token.valid-minutes=60
2323
eav-platform.security.jwt.refresh-token.valid-minutes=10080
2424

25+
eav-platform.data-type.time-pattern=HH:mm:ss.SSS
26+
eav-platform.data-type.date-pattern=yyyy-MM-dd
27+
eav-platform.data-type.datetime-pattern=yyyy-MM-ddTHH:mm:ss.SSS
28+
2529
#logging.level.root=INFO

0 commit comments

Comments
 (0)