package golf.handicap.routes import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import dmo.fs.db.handicap.DbConfiguration import dmo.fs.db.wsnext.hib.DodexHibernateConfig import dmo.fs.quarkus.Server import dmo.fs.utils.ColorUtilConstants import dmo.fs.utils.Constants import dmo.fs.utils.Err import golf.handicap.Golfer import golf.handicap.Score import golf.handicap.hibernate.HandicapHibernate import golf.handicap.hibernate.db.* import golf.handicap.hibernate.db.DuplicateVertxContext.Companion.getVertxContext import handicap.grpc.* import io.grpc.Status import io.grpc.stub.StreamObserver import io.quarkus.arc.properties.IfBuildProperty import io.quarkus.arc.properties.UnlessBuildProperty import io.quarkus.grpc.GrpcService import io.vertx.core.Promise import io.vertx.core.http.HttpMethod import io.vertx.core.json.JsonObject import io.vertx.mutiny.core.Context import io.vertx.mutiny.core.Vertx import io.vertx.mutiny.ext.web.Route import io.vertx.mutiny.ext.web.Router import io.vertx.mutiny.ext.web.handler.CorsHandler import io.vertx.mutiny.ext.web.handler.FaviconHandler import io.vertx.mutiny.ext.web.handler.StaticHandler import io.vertx.mutiny.ext.web.handler.TimeoutHandler import org.eclipse.microprofile.config.ConfigProvider import org.hibernate.reactive.mutiny.Mutiny import org.slf4j.LoggerFactory import java.math.BigDecimal import java.util.* import golf.handicap.hibernate.db.PopulateCourse.Companion.CourseRating import golf.handicap.hibernate.db.PopulateGolferScores.Companion.Parms class GrpcRoutesHibernate(vertx: Vertx, router: Router) : HandicapRoutes { val router: Router = router private val faviconHandler: FaviconHandler = FaviconHandler.create(vertx) var promise: Promise = Promise.promise() companion object { private val LOGGER = LoggerFactory.getLogger(GrpcRoutesHibernate::class.java.name) private var handicapDatabase: DodexHibernateConfig? = null private var isUsingHandicap: Boolean? = false private var isUsingSqlite3: Boolean? = null private var isUsingH2: Boolean? = null private var isReactive: Boolean? = null private var usingHandicap: String? = null private var enableHandicapAdmin: Boolean? = null var handicapAdminPin: String? = null init { usingHandicap = System.getenv()[Constants.USE_HANDICAP] if (usingHandicap != null) { isUsingHandicap = "true" == usingHandicap } else { usingHandicap = System.getProperty(Constants.USE_HANDICAP) isUsingHandicap = "true" == usingHandicap } } } init { getConfig(vertx) isUsingSqlite3 = DbConfiguration.isUsingSqlite3() isReactive = isUsingSqlite3!! } private fun getConfig(vertx: Vertx) { val context = Optional.ofNullable(vertx.orCreateContext) if (context.isPresent) { val jsonObject = Optional.ofNullable(vertx.orCreateContext.config()) try { var appConfig = if (jsonObject.isPresent) jsonObject.get() else JsonObject() if (appConfig.isEmpty) { val jsonMapper = ObjectMapper() var node: JsonNode? javaClass.getResourceAsStream(Constants.APPLICATION_CONF_FILE) .use { `in` -> node = jsonMapper.readTree(`in`) } appConfig = JsonObject.mapFrom(node) } usingHandicap = true.toString() if (usingHandicap == null) { val config = ConfigProvider.getConfig() val enableHandicap = Optional.ofNullable(config.getValue(Constants.ENABLE_HANDICAP, Boolean::class.java)) if (enableHandicap.isPresent) isUsingHandicap = enableHandicap.get() } val enableAdmin = Optional.ofNullable(appConfig.getBoolean(Constants.ENABLE_HANDICAP)) if (enableAdmin.isPresent) enableHandicapAdmin = enableAdmin.get() val handicapPin = Optional.ofNullable(appConfig.getString(Constants.ADMIN_PIN)) if (handicapPin.isPresent) handicapAdminPin = handicapPin.get() if (isUsingHandicap!!) { Server.setIsUsingHandicap(isUsingHandicap) handicapDatabase = DbConfiguration.getDefaultDb() if (handicapDatabase == null) { val warning = String.format( """%s When using 'DEFAULT_DB=h2', no database setup is required for dev. However, it is best to use postgres or mariadb. %s""", ColorUtilConstants.GREEN, ColorUtilConstants.RESET, ) throw Exception( """ When using Handicap, DEFAULT_DB must be 'h2', 'mariadb', 'postgres', 'oracle' or 'mssql'. $warning """ ) } } } catch (exception: java.lang.Exception) { exception.printStackTrace() } } } override fun getVertxRouter(handicapPromise: Promise): Router { val staticHandler: StaticHandler = StaticHandler.create("") staticHandler.setCachingEnabled(false) staticHandler.setMaxAgeSeconds(0) if (handicapDatabase != null && isUsingHandicap!!) { val staticRoute: Route = router.route("/handicap/*").handler(TimeoutHandler.create(2000)) val corsHandler = CorsHandler.create() val methods = setOf(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.OPTIONS, HttpMethod.HEAD) handicapPromise.complete() } else { handicapPromise.complete() } return router } override fun setRoutePromise(promise: Promise) { this.promise = promise } override fun routes(router: Router): Router { router.get("/handicap/courses").produces("application/json").handler { it.response().send("{}") } return router } /* 1. To start up Handicap without environment variable, comment line below */ @IfBuildProperty(name = "USE_HANDICAP", stringValue = "true") /* 2. set handicap.enable.handicap=true -- in application.properties 3. uncomment line below */ // @IfBuildProperty(name = "handicap.enable.handicap", stringValue = "true") @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "sqlite3", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "ibmdb2", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "firebase", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "neo4j", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "cassandra", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "cubrid", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "h2", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "mariadb", enableIfMissing = true) @UnlessBuildProperty(name = "DEFAULT_DB", stringValue = "postgres", enableIfMissing = true) @GrpcService class HandicapIndexService : HandicapIndexGrpc.HandicapIndexImplBase() { var sessionFactory: Mutiny.SessionFactory; init { Server.setIsUsingHandicap(true) var dodexHibernateConfig: DodexHibernateConfig = DbConfiguration.getDefaultDb(); dodexHibernateConfig.configDatabase(); sessionFactory = dodexHibernateConfig.getSessionFactory() } override fun listCourses( request: Command, responseObserver: StreamObserver ) { val populateCourse: IPopulateCourse = PopulateCourse() populateCourse.listCourses(request.getKey(), sessionFactory) .onItemOrFailure().invoke { coursesBuilder, err -> if (err != null) { LOGGER.error(Err.displayErr(98), err.message) } responseObserver.onNext(coursesBuilder.build()) responseObserver.onCompleted() }.subscribeAsCompletionStage() } override fun addRating(request: Command, responseObserver: StreamObserver) { val populateCourse: IPopulateCourse = PopulateCourse() val mapper = ObjectMapper() val ratingMap = mapper.readValue(request.json, object : TypeReference>() {}) val color: String = ratingMap["color"] as String if (!color.startsWith("#")) { val rgb: List = color.split("(")[1].split(")")[0].split(",") val hex = "%02x" ratingMap["color"] = String.format( "#%s%s%s", hex.format(rgb[0].trim().toInt()), hex.format(rgb[1].trim().toInt()), hex.format(rgb[2].trim().toInt()) ) .uppercase() } populateCourse .getCourseWithTee(ratingMap, sessionFactory) .onItem().invoke { handicapData -> responseObserver.onNext(handicapData) responseObserver.onCompleted() } .onFailure().invoke { err -> LOGGER.error(Err.displayErr(93), err.message) responseObserver.onCompleted() }.subscribeAsCompletionStage() } override fun addScore(request: Command, responseObserver: StreamObserver) { val mapper = ObjectMapper() val score: Score = mapper.readValue(request.json, object : TypeReference() {}) val populateScore: IPopulateScore = PopulateScore() getVertxContext().runOnContext { _ -> sessionFactory.openSession().onItemOrFailure().invoke { session, err -> populateScore .setScore( score, session ) .onItemOrFailure().invoke { -> val handicap = HandicapHibernate() handicap .getHandicap(score.golfer!!, session) .onItemOrFailure().invoke { latestTee, err -> if (err != null) { LOGGER.error(Err.displayErr(94), err.message) } val newHandicap: Float = latestTee[CourseRating.HANDICAP.entry] as Float val slope: Float = latestTee[CourseRating.SLOPE.entry] as Float val rating: Float = latestTee[CourseRating.RATING.entry] as Float val par: Int = latestTee[CourseRating.PAR.entry] as Int score.handicap = newHandicap val courseHandicap: Float = newHandicap * slope / 113 + (rating - par) score.netScore = score.grossScore.toFloat() - courseHandicap score.golfer!!.handicap = newHandicap.toDouble() populateScore .setScore(score, session) .onItemOrFailure().invoke { data, err -> if (err != null) { LOGGER.error(Err.displayErr(94), err.message) } responseObserver.onNext( HandicapData.newBuilder() .setMessage("Success") .setCmd(request.cmd) .setJson(ObjectMapper().writeValueAsString(score)) .build() ) responseObserver.onCompleted() } .subscribeAsCompletionStage() }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() }.subscribeAsCompletionStage() } } override fun getGolfer( request: HandicapSetup, responseObserver: StreamObserver ) { if ("Test" == request.message) { LOGGER.warn(Err.displayErr(95), request.json) } var requestJson = JsonObject(request.json) val golfer = requestJson.mapTo(Golfer::class.java) val cmd = request.cmd if (cmd < 0 || cmd > 8) { val status: Status = Status.FAILED_PRECONDITION.withDescription(Err.displayErr(96)) responseObserver.onError(status.asRuntimeException()) } else { val populateGolfer: IPopulateGolfer = PopulateGolfer() populateGolfer.getGolfer(golfer, cmd, sessionFactory).onItem().invoke { resultGolfer -> requestJson = JsonObject.mapFrom(resultGolfer) requestJson.remove("status") requestJson.put("status", resultGolfer?.status) if (enableHandicapAdmin!!) { requestJson.put("adminstatus", 10) requestJson.put("admin", handicapAdminPin) } val responseData = HandicapData.newBuilder() .setMessage(resultGolfer?.message) .setCmd(request.cmd) .setJson(requestJson.toString()) responseObserver.onNext( HandicapData.newBuilder() .setMessage(resultGolfer?.message) .setCmd(request.cmd) .setJson(requestJson.toString()) .build() ) if ("Test" == request.message) { LOGGER.warn(Err.displayErr(97), request.json) } responseObserver.onCompleted() }.onFailure().invoke { err -> err.stackTrace responseObserver.onCompleted() }.subscribeAsCompletionStage() } } override fun golferScores(request: Command, responseObserver: StreamObserver) { val populateScores: IPopulateGolferScores = PopulateGolferScores() val requestJson = JsonObject(request.json) val golfer = requestJson.mapTo(Golfer::class.java) if (request.cmd == 10) { val names = request.key.split(",") golfer.lastName = names[0] golfer.firstName = if (names.size > 1) names[1].trim() else "" golfer.pin = "" } populateScores.getGolferScores(golfer, 365, sessionFactory).onItemOrFailure().invoke { scoresMap, err -> if (err != null) { err.printStackTrace() } responseObserver.onNext( HandicapData.newBuilder() .setMessage("Success") .setCmd(request.cmd) .setJson(scoresMap["array"].toString()) .build() ) responseObserver.onCompleted() }.subscribeAsCompletionStage() } override fun listGolfers( request: Command, responseObserver: StreamObserver ) { val populateGolfer: IPopulateGolfer = PopulateGolfer() populateGolfer.getGolfers(sessionFactory).onItem().invoke { listGolfersBuilder -> responseObserver.onNext(listGolfersBuilder.build()) responseObserver.onCompleted() }.subscribeAsCompletionStage() } override fun removeScore(request: Command, responseObserver: StreamObserver) { val populateScores: IPopulateGolferScores = PopulateGolferScores() val requestJson = JsonObject(request.json) val golfer = requestJson.mapTo(Golfer::class.java) getVertxContext().runOnContext { _ -> sessionFactory.openSession().onItemOrFailure().invoke { session, err -> if (golfer.pin != null) { populateScores.removeLastScore(request.key, session).onItem().invoke { used -> if (used == null) { golfer!!.handicap = 0.0 val jsonObject = JsonObject.mapFrom(golfer) jsonObject.put(Parms.USED.entry, "N") doResponse(request, responseObserver, jsonObject, session) } else { val handicap = HandicapHibernate() handicap.getHandicap(golfer, session).onItemOrFailure().invoke { latestTee, err -> if (err != null) { LOGGER.error(Err.displayErr(99), err.message) } golfer!!.handicap = BigDecimal(latestTee!!["handicap"].toString()).toDouble() val jsonObject = JsonObject.mapFrom(golfer) jsonObject.put(Parms.USED.entry, used) doResponse(request, responseObserver, jsonObject, session) }.subscribeAsCompletionStage() } }.subscribeAsCompletionStage() } else { golfer!!.handicap = 0.0 val jsonObject = JsonObject.mapFrom(golfer) jsonObject.put(Parms.USED.entry, "N") doResponse(request, responseObserver, jsonObject, session) } }.subscribeAsCompletionStage() } } fun doResponse( request: Command, responseObserver: StreamObserver, jsonObject: JsonObject, session: Mutiny.Session ) { responseObserver.onNext( HandicapData.newBuilder() .setMessage("Success") .setCmd(request.cmd) .setJson(jsonObject.toString()) .build() ) session.close().onItemOrFailure().invoke { responseObserver.onCompleted() }.subscribeAsCompletionStage() } } }