package golf.handicap.hibernate.db import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import dmo.fs.entities.PersistedCourse import dmo.fs.entities.PersistedRatings import dmo.fs.utils.Err import handicap.grpc.* import handicap.grpc.Course.Builder import handicap.grpc.Course.newBuilder import io.smallrye.mutiny.Uni import io.vertx.core.json.JsonArray import io.vertx.core.json.JsonObject import io.vertx.mutiny.core.Promise import jakarta.persistence.criteria.CriteriaQuery import jakarta.persistence.criteria.Predicate import jakarta.persistence.criteria.Root import org.hibernate.reactive.mutiny.Mutiny import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.CompletableFuture class PopulateCourse : DuplicateVertxContext(), IPopulateCourse { companion object { private val LOGGER = LoggerFactory.getLogger(PopulateCourse::class.java.name) enum class CourseRating(val entry: String) { COLOR("color"), PAR("par"), SLOPE("slope"), RATING("rating"), TEE("tee"), STATUS("status"), SEQ("seq"), ADJUSTED("adjusted"), HANDICAP("handicap"), } } override fun getCourseWithTee( courseMap: HashMap, sessionFactory: Mutiny.SessionFactory ): Uni { var message = "Success" val builder = sessionFactory.criteriaBuilder val query: CriteriaQuery = builder.createQuery(PersistedCourse::class.java) val root: Root = query.from(PersistedCourse::class.java) val predicate: Predicate? = builder.equal(root.get("courseSeq"), courseMap[CourseRating.SEQ.entry]) query.where(predicate) var persistedCourse: Uni val handicapDataPromise: Promise = Promise.promise() getVertxContext().runOnContext { _ -> sessionFactory.openSession().onItemOrFailure().invoke { session, err -> persistedCourse = session.createQuery(query).singleResult.onItemOrFailure().call { course, throwable -> if (throwable != null && !throwable.message!!.startsWith(Err.displayErr(66))) { LOGGER.error(throwable.message, throwable) } Uni.createFrom().item(course) } var cf: CompletableFuture = CompletableFuture.completedFuture(null) persistedCourse.onItemOrFailure().invoke { courseWithRatings, err -> if (err != null && !err.message!!.startsWith(Err.displayErr(66))) { LOGGER.error(err.message, err) throw RuntimeException(err.message) } if (err != null) { LOGGER.error("Error Message: ${err.message} -- ${Err.displayErr(66)}") } val persistedCourse = buildCourse(courseMap) // check for ratings in courseRatings var whichRating = "" if (courseWithRatings != null) { courseWithRatings.ratings.forEach { rating -> whichRating = if ( ((courseMap[CourseRating.RATING.entry] as String).toBigDecimal() != rating.teeRating || courseMap[CourseRating.SLOPE.entry] as Int != rating.teeSlope || courseMap[CourseRating.PAR.entry] as Int != rating.teePar || courseMap[CourseRating.COLOR.entry] as String != rating.teeColor) && rating.tee == courseMap[CourseRating.TEE.entry] as Int ) { "update" // change existing rating } else if (err != null && err.message!!.startsWith(Err.displayErr(66))) { "course" // new course with first rating } else if (rating.tee == courseMap[CourseRating.TEE.entry] as Int) { "hit" } else { if (whichRating.isEmpty()) { whichRating = "new" // new rating } whichRating } } } when (whichRating) { "new" -> { val ratingsSet = newRating(courseMap, persistedCourse) persistedCourse.ratings = ratingsSet cf = session.merge(persistedCourse).onItemOrFailure().invoke { _, err -> if (err != null) { cf.completeExceptionally(err) throw RuntimeException(err.message) } cf.complete(null) }.subscribeAsCompletionStage().newIncompleteFuture() } "update" -> { persistedCourse.ratings = updateRating(courseMap, courseWithRatings) session.merge(persistedCourse).onItemOrFailure().invoke { pc, err -> if (err != null) { LOGGER.error(err.message, err) } cf.complete(null) }.subscribeAsCompletionStage() } "hit" -> { cf.complete(null) } else -> { cf = session.persist(persistedCourse).onItemOrFailure().invoke { _, err -> if (err != null) { LOGGER.error(err.message) err.printStackTrace() cf.complete(null) } persistedCourse.ratings = buildRating(courseMap, persistedCourse) cf = session.persist(persistedCourse).onItemOrFailure().invoke { _, err2 -> if (err2 != null) { LOGGER.error(err2.message) err2.printStackTrace() } cf.complete(null) }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() } } cf.whenComplete { _, err -> session.flush().onItemOrFailure().invoke { _, err -> if (err != null) { LOGGER.error(Err.displayErr(67), err.message) courseMap[CourseRating.STATUS.entry] = -1 message = err.message as String } session.close().onItemOrFailure().invoke { if (err != null) { LOGGER.error(Err.displayErr(67), err.message) courseMap[CourseRating.STATUS.entry] = -1 message = err.message as String } val jsonString = JsonObject(courseMap).toString() courseMap[CourseRating.STATUS.entry] = -1 val handicapData = HandicapData.newBuilder() .setMessage(message) .setCmd(2) .setJson(jsonString) .build() handicapDataPromise.complete(handicapData) }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() } }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() } return handicapDataPromise.future() } override fun listCourses(state: String, sessionFactory: Mutiny.SessionFactory): Uni { val builderPromise: Promise = Promise.promise() val coursesBuilder = ListCoursesResponse.newBuilder() val builder = sessionFactory.criteriaBuilder val query: CriteriaQuery = builder.createQuery(PersistedCourse::class.java) val root: Root = query.from(PersistedCourse::class.java) query.where(builder.equal(root.get("courseState"), state)) val ratingTees: Array = arrayOf(-1, -1, -1, -1, -1) var courseBuilder: Builder? = null var cf: CompletableFuture?> = CompletableFuture.completedFuture>(null) getVertxContext().runOnContext { _ -> sessionFactory.openSession().onItemOrFailure().invoke { session, err -> cf = session.createQuery(query).resultList.onItemOrFailure().invoke { courses, err -> if (err != null && !err.message!!.startsWith(Err.displayErr(66))) { LOGGER.error(Err.displayErr(68), err.message) cf.completeExceptionally(err) } session.close().onItemOrFailure().invoke { _, err -> if (err != null) { LOGGER.error(Err.displayErr(68), err.message) } }.subscribeAsCompletionStage() cf.complete(courses) }.subscribeAsCompletionStage() cf.whenComplete { courses, throwable -> courses?.forEach { course -> courseBuilder = newBuilder() courseBuilder.id = course.courseSeq courseBuilder.name = course.courseName course.ratings.forEach { rating -> val ratingBuilder = Rating.newBuilder() .setRating(rating.teeRating.toString()) .setSlope(rating.teeSlope!!) .setTee(rating.tee!!) .setColor(rating.teeColor) .setPar(rating.teePar!!) courseBuilder.addRatings(ratingBuilder) ratingTees[rating.tee!!] = rating.tee!! // which tees have been added } if (coursesBuilder.isInitialized) { // Generating placeholders for tees not defined setUndefinedTees(ratingTees, courseBuilder) coursesBuilder.addCourses(courseBuilder) } } if (courseBuilder == null) { coursesBuilder.addCourses(newBuilder()) } builderPromise.complete(coursesBuilder) } }.subscribeAsCompletionStage() } return builderPromise.future() } override fun getCourses(state: String, sessionFactory: Mutiny.SessionFactory): Uni { val coursesPromise: Promise = Promise.promise() val coursesBuilder = ListCoursesResponse.newBuilder() val builder = sessionFactory.criteriaBuilder val query: CriteriaQuery = builder.createQuery(PersistedCourse::class.java) val root: Root = query.from(PersistedCourse::class.java) query.where(builder.equal(root.get("courseState"), state)) val ratingTees: Array = arrayOf(-1, -1, -1, -1, -1) val courses: List? var courseBuilder: Builder? = null getVertxContext().runOnContext { _ -> sessionFactory.openSession().onItemOrFailure().invoke { session, err -> val uniCourse: Uni> = session.createQuery(query).resultList uniCourse.onItemOrFailure().invoke { courses, err -> if (err != null) { LOGGER.error("Query course error: {}", err.message) } courses.forEach { course -> courseBuilder = newBuilder() courseBuilder.id = course.courseSeq courseBuilder.name = course.courseName course.ratings.forEach { rating -> val ratingBuilder = Rating.newBuilder() .setRating(rating.teeRating.toString()) .setSlope(rating.teeSlope!!) .setTee(rating.tee!!) .setColor(rating.teeColor) .setPar(rating.teePar!!) courseBuilder.addRatings(ratingBuilder) ratingTees[rating.tee!!] = rating.tee!! // which tees have been added } if (coursesBuilder.isInitialized) { // Generating placeholders for tees not defined setUndefinedTees(ratingTees, courseBuilder) coursesBuilder.addCourses(courseBuilder) } } if (courseBuilder == null) { coursesBuilder.addCourses(newBuilder()) } coursesPromise.complete(convertBuilderToJson(coursesBuilder)) }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() } return coursesPromise.future() } private fun convertBuilderToJson(coursesBuilder: ListCoursesResponse.Builder): String { val coursesObject = JsonObject() val allCoursesObject = JsonObject() val allCoursesArray = JsonArray() coursesBuilder.coursesList.forEach { course -> val coursesArray = JsonArray() val ratingsArray = JsonArray() val courseObject = JsonObject() val ratingsObject = JsonObject() coursesObject.put("courses", coursesArray) coursesArray.add(courseObject) courseObject.put("id", course.id).put("name", course.name) course.ratingsList.forEach { rating -> if (rating.color == null || rating.color.isEmpty()) { ratingsArray.add(ratingsObject.put("tee", rating.tee)) } else { ratingsArray.add( ratingsObject.put(CourseRating.TEE.entry, rating.tee) .put(CourseRating.RATING.entry, rating.rating) .put(CourseRating.SLOPE.entry, rating.slope) .put(CourseRating.PAR.entry, rating.par) .put(CourseRating.COLOR.entry, rating.color) ) } } courseObject.put("ratings", ratingsArray) allCoursesArray.add(courseObject) } val courseJsonObject = allCoursesObject.put("courses", allCoursesArray) return courseJsonObject.toString() } private fun setUndefinedTees( ratingTees: Array, courseBuilder: Builder ) { for (int in ratingTees.indices) { if (ratingTees[int] == -1) { val ratingBuilder = Rating.newBuilder().setTee(int) courseBuilder.addRatings(ratingBuilder) } ratingTees[int] = -1 } } fun getRatingMap(json: String): HashMap { val mapper = ObjectMapper() val ratingMap = mapper.readValue(json, object : TypeReference>() {}) val color: String = ratingMap[CourseRating.COLOR.entry] as String if (!color.startsWith("#")) { val rgb: List = color.split("(")[1].split(")")[0].split(",") val hex = "%02x" ratingMap[CourseRating.COLOR.entry] = String.format( "#%s%s%s", hex.format(rgb[0].trim().toInt()), hex.format(rgb[1].trim().toInt()), hex.format(rgb[2].trim().toInt()) ) .uppercase() } return ratingMap } private fun buildCourse(courseMap: HashMap): PersistedCourse { val persistedCourse = PersistedCourse() persistedCourse.courseName = courseMap["courseName"].toString() persistedCourse.courseState = courseMap["state"].toString() persistedCourse.courseCountry = courseMap["country"].toString() if (courseMap[CourseRating.SEQ.entry] != null) { persistedCourse.courseSeq = courseMap[CourseRating.SEQ.entry] as Int } return persistedCourse } private fun updateRating(courseMap: HashMap, courseRatings: PersistedCourse): Set { courseRatings.ratings.iterator().forEach { if (it.tee == courseMap[CourseRating.TEE.entry]) { setRating(courseMap, it) } } return courseRatings.ratings } private fun newRating(courseMap: HashMap, courseRatings: PersistedCourse): Set { val persistedRating = PersistedRatings() setRating(courseMap, persistedRating) persistedRating.course_seq = courseMap[CourseRating.SEQ.entry] as Int courseRatings.courseSeq = courseMap[CourseRating.SEQ.entry] as Int persistedRating.ratingsId = PersistedRatings.RatingsId(courseRatings.courseSeq, persistedRating.tee) val ratings: Set = courseRatings.ratings.plus(persistedRating) return ratings } private fun buildRating(courseMap: HashMap, persistedCourse: PersistedCourse): Set { val persistedRating = PersistedRatings() val ratingsSet = HashSet() setRating(courseMap, persistedRating) persistedRating.course_seq = persistedCourse.courseSeq persistedRating.ratingsId = PersistedRatings.RatingsId(persistedCourse.courseSeq, persistedRating.tee) ratingsSet.add(persistedRating) return ratingsSet } private fun setRating(courseMap: HashMap, persistedRating: PersistedRatings) { persistedRating.teeRating = (courseMap[CourseRating.RATING.entry] as String).toBigDecimal() persistedRating.teePar = courseMap[CourseRating.PAR.entry] as Int persistedRating.tee = courseMap[CourseRating.TEE.entry] as Int persistedRating.teeColor = courseMap[CourseRating.COLOR.entry] as String persistedRating.teeSlope = courseMap[CourseRating.SLOPE.entry] as Int } }