diff --git a/README.md b/README.md index a9c624a..48dff55 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,27 @@ ## About This software is part of the Event Metadata System (EMS), providing REST API and Web User Interface (UI) for the -Event Catalogue of an experiment on particle collisions. The PostgreSQL database is currently used as the event metadata storage. EMS supports integration with the [Unified Condition Database (UniConDa)](https://git.jinr.ru/nica_db/unidb_platform) containing run metadata of an experiment for fast pre-selection and KeyCloak identity provider ensuring cental authentification and authorization for users. +Event Catalogue of an experiment on particle collisions. The PostgreSQL database is currently used as the event metadata +storage. EMS supports integration with the [Unified Condition Database (UniConDa)](https://git.jinr.ru/nica_db/unidb_platform) +containing run metadata of an experiment for fast pre-selection and KeyCloak identity provider ensuring central +authentication and authorization for users. ## Deployment ### Setting up the configuration file -The system is configurable via YAML file for a particular experiment on particle collisions, including a set of specific event metadata. The configuration should be prepared in a file named `ems.config.yaml`. An example of the EMS configuration for the BM@N experiment can be seen in the `ems.bmn-config.yaml` file. +The system is configurable via YAML file for a particular experiment on particle collisions, including a set of specific +event metadata. The configuration should be prepared in a file named `ems.config.yaml`. An example of the EMS configuration +for the BM@N experiment can be seen in the `ems.bmn-config.yaml` file. -In the configuration file, you must provide credentials for the event database (Event Catalogue) and optionally for the Unified Condition database (if it is employed), optionally set KeyCloak server parameters, and specify URLs and event parameters (metadata) stored in the Event Catalogue. +In the configuration file, you must provide credentials for the event database (Event Catalogue) and optionally for the +Unified Condition database (if it is employed), optionally set KeyCloak server parameters, and specify URLs and event +parameters (metadata) stored in the Event Catalogue. -Supported event parameter types are currently: `int`, `float`, `string`, `bool`. Acceptable ranges for `int` and `float` values can be defined using `|` separator for both Web interface and API (for instance, `track-number=10|15`). The ranges are inclusive, that is start and end of the intervals are included. The intervals unbound from one side are also supported, for example, `track-number=10|` or `track-number=|15`. +Supported event parameter types are currently: `int`, `float`, `string`, `bool`. Acceptable ranges for `int` and `float` +values can be defined using `|` separator for both Web interface and API (for instance, `track-number=10|15`). The ranges +are inclusive, that is start and end of the intervals are included. The intervals unbound from one side are also +supported, for example, `track-number=10|` or `track-number=|15`. ### Run installation of the EMS interfaces on a RedHat-based Operating System (AlmaLinux, CentOS, RedHat) @@ -107,6 +117,12 @@ Message body must contain the JSON list of events (only `reference:` part is req optional and ignored, if present). +#### Count number of entries in EMS and return just this value +`GET /event_api/v1/count[?parameter1=value1[¶meter2=value2[...]]]` + +Returns number of records matching the request, in a form `{"count": number}`. + + #### Read software records from dictionary `GET /event_api/v1/software` @@ -127,10 +143,6 @@ Message body example `{"software_id": 100, "software_version": "22.11"}` Message body example `{"storage_id": 100, "storage_name": "data1"}` -#### TODO: Count number of entries in EMS and return just this value -`GET /count[?parameter1=value1[¶meter2=value2[...]]]` - - #### TODO: Get event records as a ROOT file (synchronous) `GET /event_api/v1/eventFile[?parameter1=value1[¶meter2=value2[...]]]` @@ -187,4 +199,4 @@ curl -X POST -u USER:PASS -H "Content-Type: application/json" http://127.0.0.1/e ``` Note: `software_version` and `storage_name` must exist in the corresponding EMS database tables. -The `file_path` will be created in the `file_` table, if not there yet. +The `file_path` will be automatically created in the `file_` table, if not there yet. diff --git a/src/commonMain/kotlin/URLs.kt b/src/commonMain/kotlin/URLs.kt index 76c25e5..e1e2e97 100644 --- a/src/commonMain/kotlin/URLs.kt +++ b/src/commonMain/kotlin/URLs.kt @@ -5,3 +5,4 @@ const val SOFTWARE_URL = "/event_api/v1/software" const val STORAGE_URL = "/event_api/v1/storage" const val STATISTICS_URL = "/event_api/v1/statistics" const val EVENT_ENTITY_API_NAME = "event" +const val EVENT_COUNT_API_NAME = "count" diff --git a/src/jvmMain/kotlin/Application.kt b/src/jvmMain/kotlin/Application.kt index f676b30..052727b 100644 --- a/src/jvmMain/kotlin/Application.kt +++ b/src/jvmMain/kotlin/Application.kt @@ -278,13 +278,12 @@ fun Application.main() { // no role checking here - any user allowed val parameterBundle = ParameterBundle.buildFromCall(call, page) if (parameterBundle.hasInvalidParameters()) { - call.respond(HttpStatusCode.BadRequest) + call.respond(HttpStatusCode.BadRequest, "Invalid parameters detected in request") return@get } var connEMD: Connection? = null try { connEMD = newEMDConnection(config, this.context)!! - // val softwareMap = getSoftwareMap(connEMD) // not used for now val res = queryEMD(parameterBundle, page, connCondition, connEMD!!, null) val lstEvents = ArrayList() while (res?.next() == true) { @@ -328,6 +327,36 @@ fun Application.main() { } } + get("/${EVENT_COUNT_API_NAME}") { + val parameterBundle = ParameterBundle.buildFromCall(call, page) + if (parameterBundle.hasInvalidParameters()) { + call.respond(HttpStatusCode.BadRequest, "Invalid parameters detected in request") + return@get + } + var connEMD: Connection? = null + try { + connEMD = newEMDConnection(config, this.context)!! + val res = queryEMD(parameterBundle, page, connCondition, connEMD!!, null, countOnly = true) + if (res?.next() == true) { + call.respond(HttpStatusCode.OK, mapOf("count" to res.getInt(1))) + } else { + call.respond(HttpStatusCode.ServiceUnavailable, "Could not obtain event count from database") + } + } catch (err: PSQLException) { + if (err.toString().contains("The connection attempt failed.")) { + call.respond(HttpStatusCode.ServiceUnavailable, "Database connection failed: $err") + } else { + call.respond(HttpStatusCode.Conflict, "Database error: $err") + } + } catch (err: BadRequestException) { + call.respond(HttpStatusCode.UnprocessableEntity, "Error processing content: $err") + } catch (err: Exception) { + call.respond(HttpStatusCode.InternalServerError, "Error obtaining event data: $err") + } finally { + connEMD?.close() + } + } + post("/${EVENT_ENTITY_API_NAME}") { val roles = call.principal()?.roles!! // println("Roles in EVENT_ENTITY_API_NAME: $roles") diff --git a/src/jvmMain/kotlin/EventDBUtils.kt b/src/jvmMain/kotlin/EventDBUtils.kt index ba1d93a..ffef079 100644 --- a/src/jvmMain/kotlin/EventDBUtils.kt +++ b/src/jvmMain/kotlin/EventDBUtils.kt @@ -64,13 +64,14 @@ fun queryEMD( connCondition: Connection?, connEMD: Connection, body: BODY?, - defaultLimit: Int? = null + defaultLimit: Int? = null, + countOnly: Boolean = false ): ResultSet? { with(parameterBundle) { val et = page.db_table_name // TODO: Check how joins affect the performance. Consider doing DIY joins? var query = - """SELECT * FROM $et + """SELECT ${if (countOnly) "count(1)" else "*"} FROM $et INNER JOIN software_ ON $et.software_id = software_.software_id INNER JOIN file_ ON $et.file_guid = file_.file_guid INNER JOIN storage_ ON file_.storage_id = storage_.storage_id diff --git a/src/jvmMain/kotlin/models.kt b/src/jvmMain/kotlin/models.kt index bd0d45d..91b3861 100644 --- a/src/jvmMain/kotlin/models.kt +++ b/src/jvmMain/kotlin/models.kt @@ -28,3 +28,7 @@ fun EventReprForDelete.str(): String = data class EventListRepr( val events: Array ) + +data class EventCountRepr( + val count: Long +) diff --git a/src/jvmTest/kotlin/TestREST.kt b/src/jvmTest/kotlin/TestREST.kt index 736c2e1..55b2ec4 100644 --- a/src/jvmTest/kotlin/TestREST.kt +++ b/src/jvmTest/kotlin/TestREST.kt @@ -187,6 +187,14 @@ class RestApiTest { println(response.bodyAsText()) } + println("************************************************************") + println("Counting events") + response = authenticatedClient().get("$BASE_URL/count?period_number=$PERIOD&run_number=$RUN") { + contentType(ContentType.Application.Json) + } + val eventsCount1 = gson.fromJson(response.bodyAsText(), EventCountRepr::class.java) + println("Initially we had ${eventsCount1.count} events") + println("************************************************************") println("Creating events 1, 2") response = authenticatedClient().post("$BASE_URL/event") { @@ -196,6 +204,15 @@ class RestApiTest { println(response.status) println(response.bodyAsText()) + println("************************************************************") + println("Counting events") + response = authenticatedClient().get("$BASE_URL/count?period_number=$PERIOD&run_number=$RUN") { + contentType(ContentType.Application.Json) + } + val eventsCount2 = gson.fromJson(response.bodyAsText(), EventCountRepr::class.java) + println("After creation we have ${eventsCount2.count} events") + assertEquals(eventsCount1.count + 2, eventsCount2.count) + println("************************************************************") println("Checking that events 1,2 are in catalogue and event 3 is not") response = authenticatedClient().get("$BASE_URL/event?period_number=$PERIOD&run_number=$RUN") {