diff --git a/.github/workflows/plugin-alertMessenger.yml b/.github/workflows/plugin-alertMessenger.yml new file mode 100644 index 000000000..036e7555c --- /dev/null +++ b/.github/workflows/plugin-alertMessenger.yml @@ -0,0 +1,40 @@ +name: CI - Plugin - Alert Messenger + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [ "**" ] + paths: + - "software/plugins/alert-messenger/**" + - ".github/workflows/plugin-alertMessenger.yml" + pull_request: + branches: [ "**" ] + paths: + - "software/plugins/alert-messenger/**" + - ".github/workflows/plugin-alertMessenger.yml" + workflow_call: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: +defaults: + run: + working-directory: "software/plugins/alert-messenger/" +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + build: + uses: ./.github/workflows/wf-gradleBuild.yaml + with: + path: "software/plugins/alert-messenger/" + unitTest: + uses: ./.github/workflows/wf-gradleUnitTest.yaml + with: + path: "software/plugins/alert-messenger/" + intTest: + uses: ./.github/workflows/wf-gradleQuarkusIntTest.yaml + strategy: + matrix: + containerBased: [ false ] # TODO:: enable true + with: + path: "software/plugins/alert-messenger/" + containerBased: ${{ matrix.containerBased }} + diff --git a/deployment/Single Host/Station-Captain/src/lib/LogUtils.py b/deployment/Single Host/Station-Captain/src/lib/LogUtils.py index 3c0b1ad7c..918b4fa65 100644 --- a/deployment/Single Host/Station-Captain/src/lib/LogUtils.py +++ b/deployment/Single Host/Station-Captain/src/lib/LogUtils.py @@ -16,25 +16,36 @@ class LogUtils: @staticmethod def setupLogging(logFile:str, console:bool=False): - Path(LogUtils.logDir).mkdir(parents=True, exist_ok=True) - LogUtils.logFile = logFile + filePresent:bool = False + if not os.path.exists(logFile): + try: + Path(LogUtils.logDir).mkdir(parents=True, exist_ok=True) + os.mknod(logFile) + LogUtils.logFile = logFile + filePresent = True + except Exception as e: + LogUtils.logFile = False + filePresent = False + else: + filePresent = True logging.basicConfig(level=logging.NOTSET) - if console: LogUtils.logLevel = logging.DEBUG @staticmethod def setupLogger(name: str) -> logging.Logger: - fh = logging.handlers.RotatingFileHandler(LogUtils.logDir+LogUtils.logFile, maxBytes=10*1024*1024, backupCount=5) - fh.setLevel(logging.DEBUG) - # sh = logging.StreamHandler() # sh.setLevel(LogUtils.logLevel) logOut = logging.getLogger(name) # print(LogUtils.logLevel == logging.DEBUG) logOut.setLevel(LogUtils.logLevel) - logOut.addHandler(fh) + + if not LogUtils.logFile: + fh = logging.handlers.RotatingFileHandler(LogUtils.logDir+LogUtils.logFile, maxBytes=10*1024*1024, backupCount=5) + fh.setLevel(logging.DEBUG) + logOut.addHandler(fh) + # logOut.addHandler(sh) return logOut diff --git a/software/libs/core-api-lib-quarkus/deployment/pom.xml b/software/libs/core-api-lib-quarkus/deployment/pom.xml index 7c0526de9..b4dc932a4 100644 --- a/software/libs/core-api-lib-quarkus/deployment/pom.xml +++ b/software/libs/core-api-lib-quarkus/deployment/pom.xml @@ -5,7 +5,7 @@ tech.ebp.oqm.lib core-api-lib-quarkus-parent - 2.1.0-SNAPSHOT + 2.2.0-SNAPSHOT core-api-lib-quarkus-deployment Core Api Lib Quarkus - Deployment @@ -74,6 +74,18 @@ + + org.testcontainers + kafka + 1.20.1 + + + + org.testcontainers + redpanda + 1.20.1 + + diff --git a/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibDevserviceConfig.java b/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibDevserviceConfig.java index f6f158a55..bb18b2844 100644 --- a/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibDevserviceConfig.java +++ b/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibDevserviceConfig.java @@ -17,6 +17,13 @@ public class CoreApiLibDevserviceConfig { */ @WithDefault("true") public boolean enable; + + /** + * Enables kafka. + */ + @ConfigItem(name="enableKafka") + @WithDefault("false") + public boolean enableKafka; /** * The path of the public key file diff --git a/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibQuarkusProcessor.java b/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibQuarkusProcessor.java index 297b274f1..338401577 100644 --- a/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibQuarkusProcessor.java +++ b/software/libs/core-api-lib-quarkus/deployment/src/main/java/tech/ebp/oqm/lib/core/api/quarkus/deployment/CoreApiLibQuarkusProcessor.java @@ -10,6 +10,7 @@ import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.containers.Network; +import org.testcontainers.redpanda.RedpandaContainer; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; import tech.ebp.oqm.lib.core.api.quarkus.runtime.Constants; @@ -21,23 +22,24 @@ import java.util.Map; class CoreApiLibQuarkusProcessor { - + private static final String FEATURE = "core-api-lib-quarkus"; - private static final String MONGODB_DEVSERVICE_HOSTNAME = "mongodbserver"; - + private static final String MONGODB_DEVSERVICE_HOSTNAME = "oqm-dev-mongodb-server"; + private static final String KAFKA_DEVSERVICE_HOSTNAME = "oqm-dev-kafka-server"; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FEATURE); } - + @BuildStep List addRestConfiguration() { return List.of( - new RunTimeConfigurationDefaultBuildItem("quarkus.rest-client."+Constants.CORE_API_CLIENT_NAME+".url", "${quarkus." + Constants.CONFIG_ROOT_NAME + ".coreApiBaseUri}"), - new RunTimeConfigurationDefaultBuildItem("quarkus.rest-client."+Constants.CORE_API_CLIENT_OIDC_NAME+".url", "${quarkus.oidc.auth-server-url:}") + new RunTimeConfigurationDefaultBuildItem("quarkus.rest-client." + Constants.CORE_API_CLIENT_NAME + ".url", "${quarkus." + Constants.CONFIG_ROOT_NAME + ".coreApiBaseUri}"), + new RunTimeConfigurationDefaultBuildItem("quarkus.rest-client." + Constants.CORE_API_CLIENT_OIDC_NAME + ".url", "${quarkus.oidc.auth-server-url:}") ); } - + @BuildStep HealthBuildItem addHealthCheck(CoreApiLibBuildTimeConfig buildTimeConfig) { return new HealthBuildItem( @@ -45,7 +47,7 @@ HealthBuildItem addHealthCheck(CoreApiLibBuildTimeConfig buildTimeConfig) { buildTimeConfig.healthEnabled ); } - + @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) public List createContainer( LaunchModeBuildItem launchMode, @@ -53,41 +55,69 @@ public List createContainer( ) { List output = new ArrayList<>(); Map mongoConnectionInfo = new HashMap<>(); + Map kafkaConnectionInfo = new HashMap<>(); {//mongodb DockerImageName mongoImageName = DockerImageName.parse("mongo:7"); - + MongoDBContainer mongoDBContainer = new MongoDBContainer(mongoImageName); mongoDBContainer.addExposedPorts(); mongoDBContainer.withNetwork(Network.SHARED); mongoDBContainer.withNetworkAliases(MONGODB_DEVSERVICE_HOSTNAME); mongoDBContainer.start(); - + mongoConnectionInfo.put("quarkus.mongodb.connection-string", "mongodb://" + MONGODB_DEVSERVICE_HOSTNAME + ":27017"); - + output.add(new DevServicesResultBuildItem.RunningDevService( FEATURE, mongoDBContainer.getContainerId(), mongoDBContainer::close, Map.of() ) - .toBuildItem() + .toBuildItem() + ); + } + if (config.devservice.enableKafka) { + RedpandaContainer kafka = new RedpandaContainer(DockerImageName.parse("docker.redpanda.com/redpandadata/redpanda:v23.1.2")) + .withNetwork(Network.SHARED) + .withAccessToHost(true) + .withNetworkAliases(KAFKA_DEVSERVICE_HOSTNAME) + .withListener(() -> KAFKA_DEVSERVICE_HOSTNAME + ":19092"); + kafka.start(); + + kafkaConnectionInfo.putAll(Map.of( + "quarkus.reactive-messaging.health.enabled", "true", + "mp.messaging.outgoing.events-outgoing.bootstrap.servers", String.format("PLAINTEXT://%s:%d", KAFKA_DEVSERVICE_HOSTNAME, 19092), + "devservice.kafka.bootstrapServers", kafka.getBootstrapServers(), + "mp.messaging.outgoing.events-outgoing.connector", "smallrye-kafka", + "mp.messaging.outgoing.events-outgoing.broadcast", "true", + "mp.messaging.outgoing.events-outgoing.value.serializer", "io.quarkus.kafka.client.serialization.ObjectMapperSerializer" + )); + + output.add( + new DevServicesResultBuildItem.RunningDevService( + FEATURE, + kafka.getContainerId(), + kafka::close, + Map.of() + ).toBuildItem() ); } {//Base Station DockerImageName dockerImageName = DockerImageName.parse("ebprod/oqm-core-api:" + config.devservice.coreApiVersion); // You might want to use Quarkus config here to customise the container OqmCoreApiWebServiceContainer container = new OqmCoreApiWebServiceContainer(dockerImageName) - .withAccessToHost(true) - .withEnv(mongoConnectionInfo) - .withNetwork(Network.SHARED); + .withAccessToHost(true) + .withEnv(mongoConnectionInfo) + .withEnv(kafkaConnectionInfo) + .withNetwork(Network.SHARED); ; - + if ( config.devservice.certKeyPath.isPresent() || - config.devservice.certPath.isPresent() + config.devservice.certPath.isPresent() ) { - if(!(config.devservice.certKeyPath.isPresent() && - config.devservice.certPath.isPresent())){ + if (!(config.devservice.certKeyPath.isPresent() && + config.devservice.certPath.isPresent())) { throw new RuntimeException("Must specify both cert and key for core api devservice."); } @@ -105,23 +135,27 @@ public List createContainer( container.withEnv("mp.jwt.verify.publickey.location", "/tmp/systemCert.pem"); } - + container.start(); - + Map props = new HashMap<>(); props.put("quarkus." + Constants.CONFIG_ROOT_NAME + ".coreApiBaseUri", "http://" + container.getHost() + ":" + container.getPort()); props.put("quarkus.rest-client.oqmCoreApi.url", "${quarkus." + Constants.CONFIG_ROOT_NAME + ".coreApiBaseUri}"); - + + if (!kafkaConnectionInfo.isEmpty()) { + props.put("devservice.kafka.bootstrapServers", kafkaConnectionInfo.get("devservice.kafka.bootstrapServers")); + } + output.add(new DevServicesResultBuildItem.RunningDevService( FEATURE, container.getContainerId(), container::close, props ) - .toBuildItem() + .toBuildItem() ); } - + return output; } } diff --git a/software/libs/core-api-lib-quarkus/pom.xml b/software/libs/core-api-lib-quarkus/pom.xml index f12f81291..b30207894 100644 --- a/software/libs/core-api-lib-quarkus/pom.xml +++ b/software/libs/core-api-lib-quarkus/pom.xml @@ -5,7 +5,7 @@ tech.ebp.oqm.lib core-api-lib-quarkus-parent - 2.1.0-SNAPSHOT + 2.2.0-SNAPSHOT pom Core Api Lib Quarkus - Parent diff --git a/software/libs/core-api-lib-quarkus/runtime/pom.xml b/software/libs/core-api-lib-quarkus/runtime/pom.xml index 4201fa12e..13a644670 100644 --- a/software/libs/core-api-lib-quarkus/runtime/pom.xml +++ b/software/libs/core-api-lib-quarkus/runtime/pom.xml @@ -5,7 +5,7 @@ tech.ebp.oqm.lib core-api-lib-quarkus-parent - 2.1.0-SNAPSHOT + 2.2.0-SNAPSHOT core-api-lib-quarkus Core Api Lib Quarkus - Runtime diff --git a/software/oqm-core-api/build.gradle b/software/oqm-core-api/build.gradle index d0a593e22..067b852fb 100644 --- a/software/oqm-core-api/build.gradle +++ b/software/oqm-core-api/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'io.quarkus' - id "io.freefair.lombok" version "8.6" + id "io.freefair.lombok" version "8.7.1" } group 'com.ebp.openQuarterMaster' @@ -43,8 +43,6 @@ dependencies { implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' implementation 'com.fasterxml.jackson.module:jackson-module-blackbird' - implementation 'com.itextpdf:html2pdf:5.0.4' - // https://mvnrepository.com/artifact/tech.units/indriya // https://unitsofmeasurement.gitbook.io/uom-guide/getting-started/getting-started-with-indriya implementation 'tech.units:indriya:2.2' @@ -52,8 +50,8 @@ dependencies { // https://mvnrepository.com/artifact/tech.uom.lib/uom-lib-jackson implementation 'tech.uom.lib:uom-lib-jackson:2.1' - implementation 'org.apache.commons:commons-lang3:3.15.0' - implementation 'org.apache.commons:commons-compress:1.26.2' + implementation 'org.apache.commons:commons-lang3:3.16.0' + implementation 'org.apache.commons:commons-compress:1.27.0' implementation 'org.apache.commons:commons-csv:1.11.0' implementation 'org.apache.commons:commons-io:1.3.2' implementation 'commons-codec:commons-codec:1.17.1' diff --git a/software/oqm-core-api/oqm-core-api-chart/.helmignore b/software/oqm-core-api/oqm-core-api-chart/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/software/oqm-core-api/oqm-core-api-chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/software/oqm-core-api/oqm-core-api-chart/Chart.yaml b/software/oqm-core-api/oqm-core-api-chart/Chart.yaml new file mode 100644 index 000000000..cb71e8f44 --- /dev/null +++ b/software/oqm-core-api/oqm-core-api-chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: oqm-core-api-chart +description: A chart to stand up the core API component for Open QuarterMaster. + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/software/oqm-core-api/oqm-core-api-chart/README.md b/software/oqm-core-api/oqm-core-api-chart/README.md new file mode 100644 index 000000000..9d0155df5 --- /dev/null +++ b/software/oqm-core-api/oqm-core-api-chart/README.md @@ -0,0 +1,19 @@ +# OQM Core API Helm Charts + +## Requirements: + +### MongoDB Community Operator + +`helm install mongo-operator community-operator --repo https://mongodb.github.io/helm-charts -n ` + +- This operator is scoped to a namespace, meaning that that manifest needs to go in the same namespace as the operator. + +#### Resources and further reading: + +- TODO + +## TODOS: + +- Determine if best to have charts at app level, then this would gather them up as dependencies +- Add infra as dependencies, properly configure +- Figure out how to add entries for depot's service files, or otherwise possibly do that functionality diff --git a/software/oqm-core-api/oqm-core-api-chart/templates/infra-mongodb-crd.yaml b/software/oqm-core-api/oqm-core-api-chart/templates/infra-mongodb-crd.yaml new file mode 100644 index 000000000..cfce36929 --- /dev/null +++ b/software/oqm-core-api/oqm-core-api-chart/templates/infra-mongodb-crd.yaml @@ -0,0 +1,31 @@ +# https://github.com/mongodb/mongodb-kubernetes-operator/blob/master/config/samples/mongodb.com_v1_mongodbcommunity_cr.yaml +apiVersion: mongodbcommunity.mongodb.com/v1 +kind: MongoDBCommunity +metadata: + name: oqm-mongo +spec: + members: 3 + type: ReplicaSet + version: "6.0.5" + security: + authentication: + modes: ["SCRAM"] + users: + - name: my-user + db: admin + passwordSecretRef: # a reference to the secret that will be used to generate the user's password + name: my-user-password + roles: + - name: clusterAdmin + db: admin + - name: userAdminAnyDatabase + db: admin + scramCredentialsSecretName: my-scram +--- +apiVersion: v1 +kind: Secret +metadata: + name: my-user-password +type: Opaque +stringData: + password: pass \ No newline at end of file diff --git a/software/oqm-core-api/oqm-core-api-chart/values.yaml b/software/oqm-core-api/oqm-core-api-chart/values.yaml new file mode 100644 index 000000000..cd5b7a41a --- /dev/null +++ b/software/oqm-core-api/oqm-core-api-chart/values.yaml @@ -0,0 +1,107 @@ +# Default values for oqm-core-api-chart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/CoreApiInteractingEntity.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/CoreApiInteractingEntity.java index 1578527c0..d33b4c2f4 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/CoreApiInteractingEntity.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/CoreApiInteractingEntity.java @@ -19,7 +19,8 @@ @NoArgsConstructor @BsonDiscriminator public class CoreApiInteractingEntity extends InteractingEntity { - + + public static final int CUR_SCHEMA_VERSION = 1; /** * Don't change this. We ue this very specific ObjectId to identify the Base Station's specific entry in the db. */ @@ -64,4 +65,9 @@ public Set getRoles() { public boolean updateFrom(JsonWebToken jwt) { return false; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/ClassUpgraderNotFoundException.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/ClassUpgraderNotFoundException.java new file mode 100644 index 000000000..054b6a299 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/ClassUpgraderNotFoundException.java @@ -0,0 +1,7 @@ +package tech.ebp.oqm.core.api.exception; + +public class ClassUpgraderNotFoundException extends RuntimeException { + public ClassUpgraderNotFoundException(Class classNotFoundFor){ + super("Could not find upgrader for class " + classNotFoundFor.getCanonicalName()); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/UpgradeFailedException.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/UpgradeFailedException.java new file mode 100644 index 000000000..6ad82aafb --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/UpgradeFailedException.java @@ -0,0 +1,30 @@ +package tech.ebp.oqm.core.api.exception; + +import com.fasterxml.jackson.core.JsonProcessingException; + +public class UpgradeFailedException extends RuntimeException { + + + public UpgradeFailedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + + public UpgradeFailedException(Throwable cause) { + super(cause); + } + + public UpgradeFailedException(String message, Throwable cause) { + super(message, cause); + } + + public UpgradeFailedException(String message) { + super(message); + } + + public UpgradeFailedException(JsonProcessingException e, Class clazz){ + super( + "Failed to parse resulting json to " + clazz.getCanonicalName() + ". Error: " + e.getMessage(), + e + ); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/VersionBumperListIncontiguousException.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/VersionBumperListIncontiguousException.java new file mode 100644 index 000000000..0de092571 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/VersionBumperListIncontiguousException.java @@ -0,0 +1,8 @@ +package tech.ebp.oqm.core.api.exception; + +public class VersionBumperListIncontiguousException extends RuntimeException { + + public VersionBumperListIncontiguousException(int versionFrom, Class clazz){ + super("A version bumper could not be found to go from version " + versionFrom + " to " + (versionFrom + 1) + " for class " + clazz.getCanonicalName()); + } +} \ No newline at end of file diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java new file mode 100644 index 000000000..c83545a74 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java @@ -0,0 +1,28 @@ +package tech.ebp.oqm.core.api.health; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.health.*; +import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectSchemaUpgradeService; + +@Startup +@ApplicationScoped +public class SchemaUpgradeHealthCheck implements HealthCheck { + + @Inject + ObjectSchemaUpgradeService objectSchemaUpgradeService; + + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.builder() + .name("Database Schema Upgrade"); + + if(objectSchemaUpgradeService.upgradeRan()){ + responseBuilder = responseBuilder.up(); + } else { + responseBuilder = responseBuilder.down(); + } + + return responseBuilder.build(); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/InstanceMutex.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/InstanceMutex.java new file mode 100644 index 000000000..de7d4f2e3 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/InstanceMutex.java @@ -0,0 +1,30 @@ +package tech.ebp.oqm.core.api.model; + +import lombok.*; +import tech.ebp.oqm.core.api.model.object.MainObject; + +import java.time.ZonedDateTime; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class InstanceMutex extends MainObject { + @Override + public int getSchemaVersion() { + return 0; + } + + public InstanceMutex(String mutexId){ + this.mutexId = mutexId; + } + + private String mutexId; + + private boolean taken = false; + + private ZonedDateTime takenAt = null; + + protected String takenBy = null; +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/FileMainObject.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/FileMainObject.java index dcc2e2340..3c2d1acb9 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/FileMainObject.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/FileMainObject.java @@ -26,8 +26,8 @@ @ToString(callSuper = true) public class FileMainObject extends AttKeywordMainObject - // implements AttKeywordContaining { + public static final int CUR_SCHEMA_VERSION = 1; public FileMainObject(ObjectId id, Map<@NotBlank @NotNull String, String> attributes, List<@NotBlank String> keywords) { super(id, attributes, keywords); @@ -64,4 +64,9 @@ public boolean updateFrom(FileUploadBody newUpload){ } return true; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/MainObject.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/MainObject.java index 2b9047a85..50b092c64 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/MainObject.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/MainObject.java @@ -11,7 +11,7 @@ @AllArgsConstructor @NoArgsConstructor @JsonInclude(JsonInclude.Include.ALWAYS) -public abstract class MainObject { +public abstract class MainObject implements Versionable { /** * The id of this object in the Mongodb. diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/Versionable.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/Versionable.java index f1c36077d..3ebf34bb0 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/Versionable.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/Versionable.java @@ -5,5 +5,5 @@ public interface Versionable { @JsonProperty(access = JsonProperty.Access.READ_ONLY) - public int getObjectVersion(); + public int getSchemaVersion(); } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/CreateEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/CreateEvent.java index 159bf1097..44a3a4f8b 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/CreateEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/CreateEvent.java @@ -20,6 +20,7 @@ @ToString(callSuper = true) @BsonDiscriminator(value="CreateEvent") public class CreateEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public CreateEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -33,4 +34,9 @@ public CreateEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.CREATE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/DeleteEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/DeleteEvent.java index d19e39dd6..0cea4a000 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/DeleteEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/DeleteEvent.java @@ -21,6 +21,7 @@ //@SuperBuilder @BsonDiscriminator public class DeleteEvent extends DescriptiveEvent { + public static final int CUR_SCHEMA_VERSION = 1; public DeleteEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -34,4 +35,9 @@ public DeleteEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.DELETE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/UpdateEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/UpdateEvent.java index da59f6da7..a39f2a932 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/UpdateEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/UpdateEvent.java @@ -26,6 +26,7 @@ //@SuperBuilder @BsonDiscriminator public class UpdateEvent extends DescriptiveEvent { + public static final int CUR_SCHEMA_VERSION = 1; public UpdateEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -45,4 +46,9 @@ public UpdateEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.UPDATE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceAuthEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceAuthEvent.java index 0819f7ed6..45ad5e983 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceAuthEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceAuthEvent.java @@ -18,6 +18,7 @@ //@SuperBuilder @BsonDiscriminator public class ExtServiceAuthEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ExtServiceAuthEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -31,4 +32,9 @@ public ExtServiceAuthEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.EXT_SERVICE_AUTH; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceSetupEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceSetupEvent.java index 4ef4c73ac..dc5baf81f 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceSetupEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/externalService/ExtServiceSetupEvent.java @@ -18,6 +18,7 @@ //@SuperBuilder @BsonDiscriminator public class ExtServiceSetupEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ExtServiceSetupEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -31,4 +32,9 @@ public ExtServiceSetupEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.EXT_SERVICE_SETUP; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/file/NewFileVersionEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/file/NewFileVersionEvent.java index 06bcba2df..4bf5c8be3 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/file/NewFileVersionEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/file/NewFileVersionEvent.java @@ -14,9 +14,15 @@ @ToString(callSuper = true) @BsonDiscriminator public class NewFileVersionEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; @Override public EventType getType() { return EventType.FILE_NEW_VERSION; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemAddEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemAddEvent.java index 12991b724..6a015240f 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemAddEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemAddEvent.java @@ -22,6 +22,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemAddEvent extends ItemAddSubEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemAddEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -39,4 +40,9 @@ public ItemAddEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_ADD; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckinEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckinEvent.java index 71bbb7aa6..97310fee4 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckinEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckinEvent.java @@ -23,6 +23,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemCheckinEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemCheckinEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -40,4 +41,9 @@ public ItemCheckinEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_CHECKIN; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckoutEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckoutEvent.java index 677c22d57..c5bfc10e3 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckoutEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemCheckoutEvent.java @@ -23,6 +23,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemCheckoutEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemCheckoutEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -40,4 +41,9 @@ public ItemCheckoutEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_CHECKOUT; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemLowStockEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemLowStockEvent.java index b1df5885c..f526559c0 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemLowStockEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemLowStockEvent.java @@ -18,6 +18,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemLowStockEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemLowStockEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -37,4 +38,9 @@ public ItemLowStockEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_LOW_STOCK; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemSubEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemSubEvent.java index 1ecef2e2d..ee893ac15 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemSubEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemSubEvent.java @@ -22,6 +22,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemSubEvent extends ItemAddSubEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemSubEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -39,4 +40,9 @@ public ItemSubEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_SUBTRACT; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemTransferEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemTransferEvent.java index c16266578..d07b07e9b 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemTransferEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/ItemTransferEvent.java @@ -22,6 +22,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemTransferEvent extends ItemAddSubEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemTransferEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -43,4 +44,9 @@ public ItemTransferEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_TRANSFER; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiredEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiredEvent.java index 6b76008fb..eb0aa29f3 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiredEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiredEvent.java @@ -20,6 +20,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemExpiredEvent extends ItemExpiryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemExpiredEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -33,4 +34,9 @@ public ItemExpiredEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_EXPIRED; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiryWarningEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiryWarningEvent.java index 446a306a2..f983cd9c4 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiryWarningEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/item/expiry/ItemExpiryWarningEvent.java @@ -20,6 +20,7 @@ //@SuperBuilder @BsonDiscriminator public class ItemExpiryWarningEvent extends ItemExpiryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public ItemExpiryWarningEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -33,4 +34,9 @@ public ItemExpiryWarningEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_EXPIRY_WARNING; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionAddEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionAddEvent.java index 5cde73f8e..c4602955a 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionAddEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionAddEvent.java @@ -17,6 +17,8 @@ @ToString(callSuper = true) @BsonDiscriminator public class ItemListActionAddEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; + public ItemListActionAddEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); } @@ -31,4 +33,9 @@ public ItemListActionAddEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_LIST_ACTION_ADD; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionDeleteEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionDeleteEvent.java index 5f4876cec..422fedcdd 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionDeleteEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionDeleteEvent.java @@ -17,6 +17,8 @@ @ToString(callSuper = true) @BsonDiscriminator public class ItemListActionDeleteEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; + public ItemListActionDeleteEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); } @@ -32,4 +34,9 @@ public ItemListActionDeleteEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_LIST_ACTION_DELETE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionUpdateEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionUpdateEvent.java index ff447a7d7..955f5ca1e 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionUpdateEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListActionUpdateEvent.java @@ -17,6 +17,8 @@ @ToString(callSuper = true) @BsonDiscriminator public class ItemListActionUpdateEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; + public ItemListActionUpdateEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); } @@ -31,4 +33,9 @@ public ItemListActionUpdateEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_LIST_ACTION_UPDATE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListApplyEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListApplyEvent.java index 35976f0f7..63fd87ad9 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListApplyEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/itemList/ItemListApplyEvent.java @@ -17,6 +17,8 @@ @ToString(callSuper = true) @BsonDiscriminator public class ItemListApplyEvent extends DescriptiveEvent { + public static final int CUR_SCHEMA_VERSION = 1; + public ItemListApplyEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); } @@ -29,4 +31,9 @@ public ItemListApplyEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.ITEM_LIST_APPLY; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserDisabledEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserDisabledEvent.java index 8809ff358..7ef3c9232 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserDisabledEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserDisabledEvent.java @@ -21,6 +21,7 @@ //@SuperBuilder @BsonDiscriminator public class UserDisabledEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public UserDisabledEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -34,4 +35,9 @@ public UserDisabledEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.USER_DISABLED; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserEnabledEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserEnabledEvent.java index e1f132fdf..4f2813cb0 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserEnabledEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserEnabledEvent.java @@ -21,6 +21,7 @@ //@SuperBuilder @BsonDiscriminator public class UserEnabledEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public UserEnabledEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -34,4 +35,9 @@ public UserEnabledEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.USER_ENABLED; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserLoginEvent.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserLoginEvent.java index fc9d9f188..6ae173c10 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserLoginEvent.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/history/events/user/UserLoginEvent.java @@ -21,6 +21,7 @@ //@SuperBuilder @BsonDiscriminator public class UserLoginEvent extends ObjectHistoryEvent { + public static final int CUR_SCHEMA_VERSION = 1; public UserLoginEvent(ObjectId objectId, InteractingEntity entity) { super(objectId, entity); @@ -34,4 +35,9 @@ public UserLoginEvent(MainObject object, InteractingEntity entity) { public EventType getType() { return EventType.USER_LOGIN; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/GeneralService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/GeneralService.java index 3e5cb69df..bbf0315be 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/GeneralService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/GeneralService.java @@ -14,6 +14,7 @@ @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class GeneralService extends ExternalService { + public static final int CUR_SCHEMA_VERSION = 1; @JsonProperty(access = JsonProperty.Access.READ_ONLY) @Override @@ -26,4 +27,9 @@ public boolean updateFrom(JsonWebToken jwt) { //TODO return false; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/plugin/PluginService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/plugin/PluginService.java index 09ebb2724..c2783d626 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/plugin/PluginService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/externalService/plugin/PluginService.java @@ -23,6 +23,7 @@ @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class PluginService extends ExternalService { + public static final int CUR_SCHEMA_VERSION = 1; @NonNull @NotNull @@ -74,4 +75,9 @@ public boolean changedGiven(ExternalServiceSetupRequest newServiceIn) { return false; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/user/User.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/user/User.java index d04fe9261..3efcd6ba2 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/user/User.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/interactingEntity/user/User.java @@ -32,6 +32,7 @@ @Builder(builderClassName = "Builder") @BsonDiscriminator public class User extends InteractingEntity { + public static final int CUR_SCHEMA_VERSION = 1; @NonNull @NotNull @@ -88,4 +89,9 @@ public boolean updateFrom(JsonWebToken jwt) { return updated; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/itemList/ItemList.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/itemList/ItemList.java index 49ff95412..4f8d86360 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/itemList/ItemList.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/itemList/ItemList.java @@ -25,6 +25,7 @@ @ToString(callSuper = true) @AllArgsConstructor public class ItemList extends AttKeywordMainObject { + public static final int CUR_SCHEMA_VERSION = 1; /** * The name of this list @@ -66,4 +67,9 @@ public List getItemActions(ObjectId itemId){ } return this.getItemActions().get(itemId); } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/ItemCategory.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/ItemCategory.java index 61f57b441..fd6a074bc 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/ItemCategory.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/ItemCategory.java @@ -25,6 +25,7 @@ @ToString(callSuper = true) @AllArgsConstructor public class ItemCategory extends ImagedMainObject implements HasParent { + public static final int CUR_SCHEMA_VERSION = 1; @NonNull @NotNull @@ -54,4 +55,9 @@ public Color getTextColor(){ ? Color.BLACK : Color.WHITE; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/checkout/ItemCheckout.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/checkout/ItemCheckout.java index 3b7facc73..901a42d42 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/checkout/ItemCheckout.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/checkout/ItemCheckout.java @@ -25,6 +25,7 @@ @AllArgsConstructor @NoArgsConstructor public class ItemCheckout extends AttKeywordMainObject { + public static final int CUR_SCHEMA_VERSION = 1; /** * When these item(s) were checked out @@ -81,4 +82,9 @@ public class ItemCheckout extends AttKeywordMainObject { public boolean isStillCheckedOut(){ return this.checkInDetails == null; }; + + @Override + public int getSchemaVersion() { + return 1; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/ListAmountItem.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/ListAmountItem.java index c282be1ba..536ec879b 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/ListAmountItem.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/ListAmountItem.java @@ -24,6 +24,7 @@ @ToString(callSuper = true) @ValidHeldStoredUnits public class ListAmountItem extends InventoryItem, ListAmountStoredWrapper> { + public static final int CUR_SCHEMA_VERSION = 1; @Override public StorageType getStorageType() { @@ -71,4 +72,9 @@ public BigDecimal recalcValueOfStored() { return this.getValueOfStored(); } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/SimpleAmountItem.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/SimpleAmountItem.java index c9825f409..b61d445fa 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/SimpleAmountItem.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/SimpleAmountItem.java @@ -25,6 +25,7 @@ @ToString(callSuper = true) @ValidHeldStoredUnits public class SimpleAmountItem extends InventoryItem { + public static final int CUR_SCHEMA_VERSION = 1; @Override public StorageType getStorageType() { @@ -64,5 +65,9 @@ public BigDecimal recalcValueOfStored() { return this.getValueOfStored(); } - + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/TrackedItem.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/TrackedItem.java index 6a3e5d1a2..3e8ba12d0 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/TrackedItem.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/items/TrackedItem.java @@ -29,6 +29,7 @@ @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class TrackedItem extends InventoryItem, TrackedMapStoredWrapper> { + public static final int CUR_SCHEMA_VERSION = 1; @Override public StorageType getStorageType() { @@ -85,4 +86,9 @@ public BigDecimal recalcValueOfStored() { protected TrackedMapStoredWrapper newWrapperInstance() { return new TrackedMapStoredWrapper(); } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/storageBlock/StorageBlock.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/storageBlock/StorageBlock.java index 7700102a0..f259aee04 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/storageBlock/StorageBlock.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/storage/storageBlock/StorageBlock.java @@ -29,7 +29,8 @@ @ToString(callSuper = true) @AllArgsConstructor public class StorageBlock extends ImagedMainObject implements HasParent, FileAttachmentContaining { - + public static final int CUR_SCHEMA_VERSION = 1; + /** * The label for this storage block */ @@ -92,4 +93,10 @@ public String getLabelText() { } return this.getLabel() + " / " + this.getNickname(); } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } + } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/CollectionUpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/CollectionUpgradeResult.java new file mode 100644 index 000000000..d8ad5a1d9 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/CollectionUpgradeResult.java @@ -0,0 +1,18 @@ +package tech.ebp.oqm.core.api.model.object.upgrade; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.Setter; + +import java.time.Duration; + +@Data +@Setter(AccessLevel.PRIVATE) +@Builder +public class CollectionUpgradeResult { + + private String collectionName; + private long numObjectsUpgraded; + private Duration timeTaken; +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgradeResult.java new file mode 100644 index 000000000..947e363a0 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgradeResult.java @@ -0,0 +1,27 @@ +package tech.ebp.oqm.core.api.model.object.upgrade; + +import jakarta.validation.constraints.NotNull; +import lombok.*; +import tech.ebp.oqm.core.api.model.object.Versionable; + +import java.time.Duration; + +@Data +@Setter(AccessLevel.PRIVATE) +@Builder +public class ObjectUpgradeResult { + + @NotNull + @NonNull + private T upgradedObject; + + @NotNull + @NonNull + private Duration timeTaken; + + private int oldVersion; + + public boolean wasUpgraded(){ + return this.getOldVersion() < upgradedObject.getSchemaVersion(); + } +} \ No newline at end of file diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgrader.java deleted file mode 100644 index 5647d1e6c..000000000 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgrader.java +++ /dev/null @@ -1,35 +0,0 @@ -package tech.ebp.oqm.core.api.model.object.upgrade; - - -import com.fasterxml.jackson.databind.JsonNode; -import lombok.Getter; -import tech.ebp.oqm.core.api.model.object.Versionable; - -import java.util.Arrays; -import java.util.SortedSet; -import java.util.TreeSet; - -public abstract class ObjectUpgrader { - @Getter - private final SortedSet> versionBumpers; - - private final Class objClass; - - public ObjectUpgrader(Class objClass, SortedSet> versionBumpers) { - this.versionBumpers = versionBumpers; - this.objClass = objClass; - } - - public ObjectUpgrader(Class objClass, ObjectVersionBumper ... versionBumpers){ - this( - objClass, - new TreeSet<>(Arrays.stream(versionBumpers).toList()) - ); - } - - - public UpgradeResult upgrade(JsonNode oldObj){ - //TODO - return null; - } -} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectVersionBumper.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectVersionBumper.java deleted file mode 100644 index ad86c53c8..000000000 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectVersionBumper.java +++ /dev/null @@ -1,25 +0,0 @@ -package tech.ebp.oqm.core.api.model.object.upgrade; - -import com.fasterxml.jackson.databind.JsonNode; -import lombok.Getter; -import tech.ebp.oqm.core.api.model.object.Versionable; - -public abstract class ObjectVersionBumper implements Comparable> { - - protected ObjectVersionBumper(int bumperTo) { - this.bumperTo = bumperTo; - } - - @Getter - public final int bumperTo; - - public abstract JsonNode bumpObject(JsonNode node); - - @Override - public int compareTo(ObjectVersionBumper tObjectVersionBumper) { - return Integer.compare( - this.getBumperTo(), - tObjectVersionBumper.getBumperTo() - ); - } -} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/OqmDbUpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/OqmDbUpgradeResult.java new file mode 100644 index 000000000..b5062b499 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/OqmDbUpgradeResult.java @@ -0,0 +1,19 @@ +package tech.ebp.oqm.core.api.model.object.upgrade; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.Setter; + +import java.time.Duration; +import java.util.List; + +@Data +@Setter(AccessLevel.PRIVATE) +@Builder +public class OqmDbUpgradeResult { + + private String dbName; + private Duration timeTaken; + private List collectionUpgradeResults; +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/TotalUpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/TotalUpgradeResult.java new file mode 100644 index 000000000..17b226db4 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/TotalUpgradeResult.java @@ -0,0 +1,19 @@ +package tech.ebp.oqm.core.api.model.object.upgrade; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Data; +import lombok.Setter; + +import java.time.Duration; +import java.util.List; + +@Data +@Setter(AccessLevel.PRIVATE) +@Builder +public class TotalUpgradeResult { + + private Duration timeTaken; + private List topLevelUpgradeResults; + private List dbUpgradeResults; +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/UpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/UpgradeResult.java deleted file mode 100644 index 6c78a9a7f..000000000 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/UpgradeResult.java +++ /dev/null @@ -1,24 +0,0 @@ -package tech.ebp.oqm.core.api.model.object.upgrade; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NonNull; -import tech.ebp.oqm.core.api.model.object.Versionable; - -@Data -@AllArgsConstructor -@Builder -public class UpgradeResult { - - @NotNull - @NonNull - private T upgradedObject; - - private int oldVersion; - - private boolean wasUpgraded(){ - return this.getOldVersion() < upgradedObject.getObjectVersion(); - } -} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/rest/search/InstanceMutexSearch.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/rest/search/InstanceMutexSearch.java new file mode 100644 index 000000000..8d72955e1 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/rest/search/InstanceMutexSearch.java @@ -0,0 +1,26 @@ +package tech.ebp.oqm.core.api.model.rest.search; + +import com.mongodb.client.model.Filters; +import jakarta.ws.rs.QueryParam; +import lombok.Getter; +import lombok.ToString; +import org.bson.conversions.Bson; +import org.bson.types.ObjectId; +import tech.ebp.oqm.core.api.model.InstanceMutex; +import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; +import tech.ebp.oqm.core.api.service.mongo.search.SearchUtils; + +import javax.measure.Quantity; +import java.util.ArrayList; +import java.util.List; + +import static com.mongodb.client.model.Filters.*; + +@ToString(callSuper = true) +@Getter +public class InstanceMutexSearch extends SearchObject { + public static InstanceMutexSearch newInstance(){ + return new InstanceMutexSearch(); + } + +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/units/CustomUnitEntry.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/units/CustomUnitEntry.java index b877bf7da..76c0b9ce4 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/units/CustomUnitEntry.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/units/CustomUnitEntry.java @@ -17,6 +17,7 @@ @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class CustomUnitEntry extends MainObject { + public static final int CUR_SCHEMA_VERSION = 1; @NotNull @NonNull @@ -28,4 +29,9 @@ public class CustomUnitEntry extends MainObject { @NotNull @NonNull private NewCustomUnitRequest unitCreator; + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/scheduled/LifecycleBean.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/scheduled/LifecycleBean.java index 052b69d4a..91fa92a18 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/scheduled/LifecycleBean.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/scheduled/LifecycleBean.java @@ -9,14 +9,17 @@ import jakarta.inject.Singleton; import lombok.extern.slf4j.Slf4j; import org.eclipse.microprofile.config.ConfigProvider; +import tech.ebp.oqm.core.api.model.object.upgrade.TotalUpgradeResult; import tech.ebp.oqm.core.api.service.TempFileService; import tech.ebp.oqm.core.api.service.mongo.CustomUnitService; +import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectSchemaUpgradeService; import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; import java.nio.file.Paths; import java.time.Duration; import java.time.ZonedDateTime; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.TreeMap; @Singleton @@ -35,6 +38,9 @@ public class LifecycleBean { @Inject OqmDatabaseService dbService; + + @Inject + ObjectSchemaUpgradeService objectSchemaUpgradeService; private ZonedDateTime startDateTime; @@ -94,7 +100,14 @@ void onStart( this.customUnitService.collectionStats(); //ensures we can write to temp dir this.tempFileService.getTempDir("test", "dir"); - + // Upgrade the db schema + Optional schemaUpgradeResult = this.objectSchemaUpgradeService.updateSchema(); + if(schemaUpgradeResult.isEmpty()){ + log.warn("Did not upgrade schema at start."); + } else { + log.info("Schema upgrade result: {}", schemaUpgradeResult.get()); + } + } void onStop( diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/HasParentObjService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/HasParentObjService.java index 574ac0c84..3c94dd59e 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/HasParentObjService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/HasParentObjService.java @@ -46,7 +46,7 @@ public List getChildrenIn(String oqmDbIdOrName, String parentId) { public ParentedMainObjectTree getTree(String oqmDbIdOrName, Collection onlyInclude) { ParentedMainObjectTree output = this.getNewTree(); - FindIterable results = getCollection(oqmDbIdOrName).find(); + FindIterable results = getTypedCollection(oqmDbIdOrName).find(); output.add(results.iterator()); if (!onlyInclude.isEmpty()) { diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexService.java new file mode 100644 index 000000000..79b1a20d7 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexService.java @@ -0,0 +1,188 @@ +package tech.ebp.oqm.core.api.service.mongo; + +import com.mongodb.client.model.*; +import com.mongodb.client.result.UpdateResult; +import jakarta.enterprise.context.ApplicationScoped; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.bson.BsonDocument; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import tech.ebp.oqm.core.api.model.InstanceMutex; +import tech.ebp.oqm.core.api.model.collectionStats.CollectionStats; +import tech.ebp.oqm.core.api.model.rest.search.InstanceMutexSearch; + +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAmount; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static com.mongodb.client.model.Filters.*; + +/** + * Original inspiration: https://www.disk91.com/2018/technology/programming/springboot-mongo-create-a-mutex-to-synchronize-multiple-frontends/ + */ +@ApplicationScoped +@Slf4j +public class InstanceMutexService extends TopLevelMongoService { + + private TemporalAmount lockExpire = Duration.of(10, ChronoUnit.MINUTES); + + @ConfigProperty(name = "quarkus.uuid") + protected String instanceUuid; + + protected InstanceMutexService() { + super(InstanceMutex.class); + } + + protected String getIdentity(Optional additionalIdentity) { + return this.instanceUuid + additionalIdentity.map(s -> "-" + s).orElse(""); + } + + private void clearDuplicateMutexes(String mutexId) { + List mutexes = this.getCollection().find(eq("mutexId", mutexId)).into(new ArrayList<>()); + if (mutexes.size() > 1) { + log.info("Multiple mutex objects for {} detected ({} total mutexes). Deleting extra.", mutexId, mutexes.size()); + mutexes.removeFirst(); + + for (InstanceMutex mutex : mutexes) { + this.getCollection().deleteOne(eq("_id", mutex.getId())); + } + } + } + + /** + * Registers the mutex with the given id. Always call this before starting to use the mutex. + * + * @param mutexId The id of the mutex to register. + */ + public void register(String mutexId) { + // Verify if exists + List existingMutexes = this.getCollection().find(eq("mutexId", mutexId)).into(new ArrayList<>()); + + log.info("Existent mutexes: {}", existingMutexes); + + if (existingMutexes.isEmpty()) { + // create it assuming no collision at this point + InstanceMutex newMutex = new InstanceMutex(mutexId); + log.info("Creating new mutex {}", newMutex); + + UpdateResult result = this.getCollection().updateOne( + eq("mutexId", mutexId), + Updates.set("mutexId", mutexId), + new UpdateOptions().upsert(true) + ); + + if (result.getUpsertedId() != null) { + log.info("Mutex {} created.", mutexId); + } else { + log.info("Mutex {} was added in another thread before we could get to it.", mutexId); + } + } else if (existingMutexes.size() > 1) { + // problem + log.error("multiple mutex for id {}", mutexId); + this.clearDuplicateMutexes(mutexId); + } + } + + /** + * Locks the mutex with the given id. + *

+ * Non blocking. + *

+ * Call {@link #register(String)} before using this method. + * + * @param mutexId The is of the mutex to get the lock for + * @param additionalIdentity If an additional identity is required. Only use if need the mutex to offer concurrency within the same instance of the app. + * @return true when reserved, false when not. + */ + public boolean lock(@NonNull String mutexId, Optional additionalIdentity) { + String identity = this.getIdentity(additionalIdentity); + Bson mutexIdEquals = eq("mutexId", mutexId); + + //ensure only one mutex object + this.clearDuplicateMutexes(mutexId); + + // try an update + InstanceMutex old = this.getCollection().findOneAndUpdate( + and( + mutexIdEquals, + or( + not(exists("taken")), + eq("taken", false) + ) + + ), + Updates.combine( + Updates.set("taken", true), + Updates.set("takenAt", ZonedDateTime.now()), + Updates.set("takenBy", identity) + ) + ); + + if (old != null) { + // success ... + // Update the information + log.debug("Got lock for {} on mutex (showing old data) {}", identity, old); + log.info("Acquired lock for {} on mutex {}", identity, mutexId); + return true; + } else { + log.info("Failed to reserve Mutex {} for {}", mutexId, identity); + + InstanceMutex lockedMutex = this.getCollection().find(mutexIdEquals).first(); + log.debug("Locked mutex: {}", lockedMutex); + + if (lockedMutex == null) { + log.warn("No mutex found. It needs to be registered first."); + } else if (lockedMutex.getTakenAt() != null && ZonedDateTime.now().isAfter(lockedMutex.getTakenAt().plus(this.lockExpire))) { + this.getCollection().findOneAndUpdate(mutexIdEquals, Updates.set("taken", false)); + log.warn("Unlocked mutex that appeared deadlocked: {}", mutexId); + } else { + log.debug("Was locked. returning."); + } + + return false; + } + } + + public boolean lock(@NonNull String mutexId) { + return this.lock(mutexId, Optional.empty()); + } + + /** + * Free a mutex previously reserved. + * + * @param mutexId The id of the mutex to free + */ + public void free(@NonNull String mutexId, Optional additionalIdentity) { + String identity = this.getIdentity(additionalIdentity); + + InstanceMutex mutex = this.getCollection().findOneAndUpdate( + and( + eq("mutexId", mutexId), + eq("taken", true), + eq("takenBy", identity) + ), + Updates.combine( + Updates.set("taken", false), + Updates.set("takenAt", null), + Updates.set("takenBy", null) + ) + ); + + if (mutex == null) { + log.info("Mutex NOT freed. Either not taken or not taken by this identity. Mutex: {}", mutex); + } else { + log.info("Mutex FREED: {}", mutexId); + } + } + + public void free(@NonNull String mutexId) { + this.free(mutexId, Optional.empty()); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java index 34c51038e..ec6c7f4a2 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java @@ -18,6 +18,7 @@ import lombok.Getter; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; import org.bson.types.ObjectId; import org.eclipse.microprofile.config.inject.ConfigProperty; import tech.ebp.oqm.core.api.model.collectionStats.CollectionStats; @@ -127,7 +128,7 @@ protected MongoDbAwareService( // return this.collection; // } - protected MongoCollection getCollection(DbCacheEntry db) { + protected MongoCollection getTypedCollection(DbCacheEntry db) { log.debug("Getting collection for cache entry {}", db); if(!this.collections.containsKey(db.getDbId())){ log.info("Collection for db cache entry not present. Creating. Cache entry: {}", db); @@ -138,11 +139,18 @@ protected MongoCollection getCollection(DbCacheEntry db) { } return this.collections.get(db.getDbId()); } - - protected MongoCollection getCollection(String oqmDbIdOrName) { - return this.getCollection(this.getOqmDatabaseService().getOqmDatabase(oqmDbIdOrName)); + + public MongoCollection getTypedCollection(String oqmDbIdOrName) { + DbCacheEntry dbCacheEntry = this.getOqmDatabaseService().getOqmDatabase(oqmDbIdOrName); + + return this.getTypedCollection(dbCacheEntry); } - + + public MongoCollection getDocumentCollection(String oqmDbIdOrName) { + DbCacheEntry dbCacheEntry = this.getOqmDatabaseService().getOqmDatabase(oqmDbIdOrName); + return dbCacheEntry.getMongoDatabase().getCollection(this.collectionName); + } + public static TransactionOptions getDefaultTransactionOptions() { return TransactionOptions.builder() .readPreference(ReadPreference.primary()) @@ -177,7 +185,7 @@ public void ensureObjectValid(String oqmDbIdOrName, boolean newObject, @Valid T } protected > X addBaseStats(String oqmDbIdOrName, X builder){ - return (X) builder.size(this.getCollection(oqmDbIdOrName).countDocuments()); + return (X) builder.size(this.getTypedCollection(oqmDbIdOrName).countDocuments()); } /** diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectService.java index 05f81f5e0..bb8f6e9e4 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectService.java @@ -274,7 +274,7 @@ public T remove(String oqmDbIdOrName, ObjectId objectId) { @WithSpan public long removeAll(String oqmDbIdOrName, ClientSession session, InteractingEntity entity) { //TODO:: add history event to each - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (session == null) { return collection.deleteMany(new BsonDocument()).getDeletedCount(); } else { diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoryService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoryService.java index 9060acd8b..37a842a84 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoryService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoryService.java @@ -69,7 +69,7 @@ public MongoHistoryService( @WithSpan public DeleteEvent isDeleted(String oqmDbIdOrName, ClientSession clientSession, ObjectId id) { - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); DeleteEvent found; Bson search = and( @@ -103,7 +103,7 @@ public DeleteEvent isDeleted(String oqmDbIdOrName, ObjectId id) { @WithSpan public ObjectHistoryEvent getLatestHistoryEventFor(String oqmDbIdOrName, ClientSession clientSession, ObjectId id) { ObjectHistoryEvent found; - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (clientSession != null) { found = collection .find(clientSession, eq("objectId", id)) @@ -130,7 +130,7 @@ public ObjectHistoryEvent getLatestHistoryEventFor(String oqmDbIdOrName, ObjectI @WithSpan public boolean hasHistoryFor(String oqmDbIdOrName, ClientSession clientSession, ObjectId id) { ObjectHistoryEvent found; - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (clientSession != null) { found = collection .find(clientSession, eq("objectId", id)) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoObjectService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoObjectService.java index a391c2b57..55f1177d8 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoObjectService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoObjectService.java @@ -69,7 +69,7 @@ private FindIterable find(String oqmDbIdOrName, ClientSession session, Bson f log.debug("Filter for find: {}", filter); FindIterable output; - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (filter != null) { if (session == null) { output = collection.find(filter); @@ -179,7 +179,7 @@ public List list(String oqmDbIdOrName) { } public Iterator iterator(String oqmDbIdOrName) { - return getCollection(oqmDbIdOrName).find().iterator(); + return getTypedCollection(oqmDbIdOrName).find().iterator(); } /** @@ -224,7 +224,7 @@ public SearchResult search(String oqmDbIdOrName, @NonNull S searchObject, boo * @return If the collection is empty or not. */ public boolean collectionEmpty(String oqmDbIdOrName) { - return this.getCollection(oqmDbIdOrName).countDocuments() == 0; + return this.getTypedCollection(oqmDbIdOrName).countDocuments() == 0; } /** @@ -235,7 +235,7 @@ public boolean collectionEmpty(String oqmDbIdOrName) { * @return the count of records in the collection */ public long count(String oqmDbIdOrName, ClientSession clientSession, Bson filter) { - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (filter == null) { if (clientSession == null) { return collection.countDocuments(); @@ -280,7 +280,7 @@ public long count(String oqmDbIdOrName) { * @return The object found. Null if not found. */ public T get(String oqmDbIdOrName, ObjectId objectId) throws DbNotFoundException, DbDeletedException { - T found = getCollection(oqmDbIdOrName) + T found = getTypedCollection(oqmDbIdOrName) .find(eq("_id", objectId)) .limit(1) .first(); @@ -293,7 +293,7 @@ public T get(String oqmDbIdOrName, ObjectId objectId) throws DbNotFoundException } public T get(String oqmDbIdOrName, ClientSession clientSession, ObjectId objectId) throws DbNotFoundException, DbDeletedException { - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); T found; if (clientSession == null) { @@ -362,7 +362,7 @@ public T update(String oqmDbIdOrName, ObjectId id, ObjectNode updateJson) throws } this.ensureObjectValid(oqmDbIdOrName, false, object, null);//TODO:: add client session - this.getCollection(oqmDbIdOrName).findOneAndReplace(eq("_id", id), object); + this.getTypedCollection(oqmDbIdOrName).findOneAndReplace(eq("_id", id), object); return object; } @@ -381,7 +381,7 @@ public T update(String oqmDbIdOrName, String id, ObjectNode updateJson) { public T update(String oqmDbIdOrName, ClientSession clientSession, @Valid T object) throws DbNotFoundException { //TODO:: review this this.get(oqmDbIdOrName, clientSession, object.getId()); - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (clientSession != null) { return collection.findOneAndReplace(clientSession, eq("_id", object.getId()), object); } else { @@ -407,7 +407,7 @@ public ObjectId add(String oqmDbIdOrName, ClientSession session, @NonNull @Valid this.ensureObjectValid(oqmDbIdOrName, true, object, session); InsertOneResult result; - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (session == null) { result = collection.insertOne(object); } else { @@ -480,7 +480,7 @@ public T remove(String oqmDbIdOrName, ClientSession clientSession, ObjectId obje this.assertNotReferenced(oqmDbIdOrName, clientSession, toRemove); DeleteResult result; - MongoCollection collection = this.getCollection(oqmDbIdOrName); + MongoCollection collection = this.getTypedCollection(oqmDbIdOrName); if (clientSession == null) { result = collection.deleteOne(eq("_id", objectId)); } else { @@ -523,12 +523,12 @@ public T remove(String oqmDbIdOrName, String objectId) { */ public long removeAll(String oqmDbIdOrName) { //TODO:: client session - return this.getCollection(oqmDbIdOrName).deleteMany(new BsonDocument()).getDeletedCount(); + return this.getTypedCollection(oqmDbIdOrName).deleteMany(new BsonDocument()).getDeletedCount(); } @Override public long clear(String oqmDbIdOrName, @NonNull ClientSession session) { - return this.getCollection(oqmDbIdOrName).deleteMany(new BsonDocument()).getDeletedCount(); + return this.getTypedCollection(oqmDbIdOrName).deleteMany(new BsonDocument()).getDeletedCount(); } /** @@ -539,7 +539,7 @@ public long clear(String oqmDbIdOrName, @NonNull ClientSession session) { * @return The sum of all values at the field */ protected long getSumOfIntField(String oqmDbIdOrName, String field) { - Document returned = this.getCollection(oqmDbIdOrName).aggregate( + Document returned = this.getTypedCollection(oqmDbIdOrName).aggregate( List.of( new Document( "$group", @@ -557,7 +557,7 @@ protected long getSumOfIntField(String oqmDbIdOrName, String field) { } protected double getSumOfFloatField(String oqmDbIdOrName, String field) { - Document returned = this.getCollection(oqmDbIdOrName).aggregate( + Document returned = this.getTypedCollection(oqmDbIdOrName).aggregate( List.of( new Document( "$group", @@ -579,7 +579,7 @@ public boolean fieldValueExists( String field, String value ) { - return this.getCollection(oqmDbIdOrName) + return this.getTypedCollection(oqmDbIdOrName) .find( eq(field, value) ).limit(1) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/image/ImageService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/image/ImageService.java index fdd3cfe52..363e71694 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/image/ImageService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/image/ImageService.java @@ -64,6 +64,7 @@ public ImageService() { * @return The resized image */ public BufferedImage resizeImage(BufferedImage inputImage) { + log.debug("Resizing image {}", inputImage); // creates output image BufferedImage outputImage = new BufferedImage( this.imageResizeConfig.width(), @@ -95,8 +96,14 @@ public ObjectId add(String oqmDbIdOrName, ClientSession clientSession, Image fil FilenameUtils.getExtension(fileName), "imageUploads" ); + log.info("Image needs resized: {}", origImage); { BufferedImage bufferedImage = ImageIO.read(origImage); + + if(bufferedImage == null){ + throw new IllegalArgumentException("Image data given was invalid or unsupported."); + } + bufferedImage = resizeImage(bufferedImage); ImageIO.write(bufferedImage, this.imageResizeConfig.savedType(), usingImage); } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/EventNotificationWrapper.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/EventNotificationWrapper.java index 55b22231e..4f3249942 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/EventNotificationWrapper.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/EventNotificationWrapper.java @@ -1,14 +1,12 @@ package tech.ebp.oqm.core.api.service.notification; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Setter; +import lombok.*; import org.bson.types.ObjectId; import tech.ebp.oqm.core.api.model.object.history.ObjectHistoryEvent; @Data @AllArgsConstructor +@NoArgsConstructor @Setter(AccessLevel.PRIVATE) public class EventNotificationWrapper { private ObjectId database; diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/HistoryEventNotificationService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/HistoryEventNotificationService.java index 7f392e74f..cf40dbdaf 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/HistoryEventNotificationService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/notification/HistoryEventNotificationService.java @@ -6,6 +6,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeaders; import org.bson.types.ObjectId; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.reactive.messaging.Channel; @@ -22,78 +24,98 @@ @Slf4j @ApplicationScoped public class HistoryEventNotificationService { - + public static final String INTERNAL_EVENT_CHANNEL = "events-internal"; public static final String OUTGOING_EVENT_CHANNEL = "events-outgoing"; - public static final String ALL_EVENT_TOPIC = "all-events"; - + public static final String TOPIC_PREPEND = "oqm-core-"; + public static final String ALL_EVENT_TOPIC_LABEL = "all-events"; + public static final String ALL_EVENT_TOPIC = TOPIC_PREPEND + ALL_EVENT_TOPIC_LABEL; + @ConfigProperty(name = "mp.messaging.outgoing.events-outgoing.bootstrap.servers") Optional outgoingServers; @ConfigProperty(name = "kafka.bootstrap.servers") Optional kafkaServers; - + @Inject @Broadcast @Channel(INTERNAL_EVENT_CHANNEL) - @OnOverflow(value = OnOverflow.Strategy.DROP)//TODO:: this better https://quarkus.io/version/3.2/guides/kafka#sending-messages-with-emitter + @OnOverflow(value = OnOverflow.Strategy.DROP) Emitter internalEventEmitter; - + + @Inject @Broadcast @Channel(OUTGOING_EVENT_CHANNEL) @OnOverflow(value = OnOverflow.Strategy.DROP) - Emitter outgoingEventEmitter; - - private boolean haveOutgoingServers(){ + Emitter outgoingEventEmitter; + + private boolean haveOutgoingServers() { return outgoingServers.isPresent() || kafkaServers.isPresent(); } - + /** * Don't call this directly, use the other one */ @WithSpan @Incoming(INTERNAL_EVENT_CHANNEL) void sendEventOutgoing(EventNotificationWrapper notificationWrapper) { - if(!this.haveOutgoingServers()){ + if (!this.haveOutgoingServers()) { log.info("NOT Sending event to external channels (no outgoing servers configured): {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); return; } log.info("Sending event to external channels: {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); try { + Headers headers = new RecordHeaders() + .add("database", notificationWrapper.getDatabase().toHexString().getBytes()) + .add("object", notificationWrapper.getObjectName().getBytes()); this.outgoingEventEmitter.send( - Message.of( - notificationWrapper.getEvent() - ).addMetadata( - OutgoingKafkaRecordMetadata.builder() - .withTopic( - (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + notificationWrapper.getObjectName() + "-" + notificationWrapper.getEvent().getType() - ) - .build() - )); - this.outgoingEventEmitter.send( - Message.of( - notificationWrapper.getEvent() - ).addMetadata( - OutgoingKafkaRecordMetadata.builder() - .withTopic((notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC) - .build() - )); + Message.of(notificationWrapper) + .addMetadata( + OutgoingKafkaRecordMetadata.builder() + .withTopic(ALL_EVENT_TOPIC) + .withHeaders(headers) + .build() + )); + //TODO:: maybe support in future +// this.outgoingEventEmitter.send( +// Message.of( +// notificationWrapper +// ).addMetadata( +// OutgoingKafkaRecordMetadata.builder() +// .withTopic( +// TOPIC_PREPEND + (notificationWrapper.getDatabase() == null ? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC_LABEL +// ) +// .withHeaders(headers) +// .build() +// )); + + + //TODO:: maybe support this in future +// this.outgoingEventEmitter.send( +// Message.of(notificationWrapper.getEvent()).addMetadata( +// OutgoingKafkaRecordMetadata.builder() +// .withTopic( +// TOPIC_PREPEND + (notificationWrapper.getDatabase() == null ? "" : notificationWrapper.getDatabase().toHexString() + "-") + notificationWrapper.getObjectName() + "-" + notificationWrapper.getEvent().getType() +// ) +// .withHeaders(headers) +// .build() +// )); log.debug("Sent event to external channels: {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); - } catch(Throwable e) { + } catch (Throwable e) { log.error("FAILED to send event to external channels: {}/{}:", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId(), e); throw e; } } - + public void sendEvent(ObjectId oqmDatabase, Class objectClass, ObjectHistoryEvent event) { this.sendEvents(oqmDatabase, objectClass, event); } - + public void sendEvents(ObjectId oqmDatabase, Class objectClass, ObjectHistoryEvent... events) { this.sendEvents(oqmDatabase, objectClass, Arrays.asList(events)); } - + public void sendEvents(ObjectId oqmDatabase, Class objectClass, Collection events) { for (ObjectHistoryEvent event : events) { log.info("Sending event to internal channel: {}/{}", objectClass.getSimpleName(), event.getId()); @@ -103,5 +125,5 @@ public void sendEvents(ObjectId oqmDatabase, Class objectClass, Collection, ObjectSchemaUpgrader> upgraderMap; + private OqmDatabaseService oqmDatabaseService; + private List> oqmDbServices; + private TotalUpgradeResult startupUpgradeResult = null; + + public ObjectSchemaUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { + if (!this.upgraderMap.containsKey(clazz)) { + throw new ClassUpgraderNotFoundException(clazz); + } + return (ObjectSchemaUpgrader) this.upgraderMap.get(clazz); + } + + private void clearUpgraderMap() { + this.upgraderMap = null; + } + + @Inject + public ObjectSchemaUpgradeService( + OqmDatabaseService oqmDatabaseService, + StorageBlockService storageBlockService, + InventoryItemService inventoryItemService + ) { + this.oqmDatabaseService = oqmDatabaseService; + //TODO:: populate rest of oqmDbServices + this.oqmDbServices = List.of( + storageBlockService, + inventoryItemService + ); + + this.upgraderMap = Map.of( + StorageBlock.class, new StorageBlockSchemaUpgrader(), + InventoryItem.class, new InventoryItemSchemaUpgrader() + ); + } + + public Optional getStartupUpgradeResult() { + return Optional.ofNullable(this.startupUpgradeResult); + } + + public boolean upgradeRan() { + return this.startupUpgradeResult == null; + } + + + private CollectionUpgradeResult upgradeOqmCollection(ClientSession cs, MongoCollection documentCollection, MongoCollection typedCollection, Class objectClass) throws ClassUpgraderNotFoundException { + ObjectSchemaUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); + CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder() + .collectionName(documentCollection.getNamespace().getCollectionName()); + + StopWatch sw = StopWatch.createStarted(); + long numUpdated = 0; + + if(objectVersionBumper.upgradesAvailable()) { + //TODO:: add search for any objects with versions less than current. + try (MongoCursor it = documentCollection.find().cursor()) { + while (it.hasNext()) { + Document doc = it.next(); + ObjectUpgradeResult result = objectVersionBumper.upgrade(doc); + + if (result.wasUpgraded()) { + numUpdated++; + typedCollection.findOneAndReplace( + cs, + eq("id", result.getUpgradedObject().getId()), + result.getUpgradedObject() + ); + } + } + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + sw.stop(); + outputBuilder.timeTaken(Duration.of(sw.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)) + .numObjectsUpgraded(numUpdated); + + return outputBuilder.build(); + } + + private CollectionUpgradeResult upgradeOqmCollection(ClientSession dbCs, OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { + log.info("Updating schema of oqm database service {} in ", service.getClass()); + String oqmDbId = oqmDb.getId().toHexString(); + //TODO:: hande upgrading history + CollectionUpgradeResult result = this.upgradeOqmCollection( + dbCs, + service.getDocumentCollection(oqmDbId), + service.getTypedCollection(oqmDbId), + service.getClazz() + ); + + log.info("DONE Updating schema of oqm database service {} in ", service.getClass()); + return result; + } + + + private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { + log.info("Updating schema of oqm database: {}", oqmDb); + OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder() + .dbName(oqmDb.getName()); + StopWatch dbUpgradeTime = StopWatch.createStarted(); + + List> futures = new ArrayList<>(); + ClientSession cs = null; + + try { + for (MongoDbAwareService curService : this.oqmDbServices) { + if (cs == null) { + cs = curService.getNewClientSession(true); + } + ClientSession finalCs = cs; + futures.add( + CompletableFuture.supplyAsync(() -> { + return upgradeOqmCollection(finalCs, oqmDb, curService); + }) + ); + } + if(cs != null) { + cs.commitTransaction(); + } + } finally { + if(cs != null){ + cs.close(); + } + } + + outputBuilder.collectionUpgradeResults( + futures.stream().map(CompletableFuture::join).toList() + ); + dbUpgradeTime.stop(); + outputBuilder.timeTaken(Duration.of(dbUpgradeTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + + log.info("Done updating oqm database: {}", oqmDb); + + return outputBuilder.build(); + } + + public Optional updateSchema() { + if (this.upgradeRan()) { + return Optional.empty(); + } + log.info("Upgrading the schema held in the Database."); + + TotalUpgradeResult.Builder totalResultBuilder = TotalUpgradeResult.builder(); + StopWatch totalTime = StopWatch.createStarted(); + + //TODO:: migrate top levels + + + List> resultMap = new ArrayList<>(); + for (OqmMongoDatabase curDb : this.oqmDatabaseService.listIterator()) { + resultMap.add(CompletableFuture.supplyAsync(() -> { + return upgradeOqmDb(curDb); + }) + ); + } + totalResultBuilder.dbUpgradeResults(resultMap.stream().map((CompletableFuture future) -> { + try { + return future.get(); + } catch (Throwable e) { + throw new UpgradeFailedException("Failed to upgrade data in database.", e); + } + }) + .toList()); + totalTime.stop(); + totalResultBuilder.timeTaken(Duration.of(totalTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + + log.info("DONE upgrading the schema held in the Database."); + this.startupUpgradeResult = totalResultBuilder.build(); + + return this.getStartupUpgradeResult(); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java new file mode 100644 index 000000000..1a4bace5a --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java @@ -0,0 +1,127 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import org.bson.Document; +import org.bson.json.JsonWriterSettings; +import tech.ebp.oqm.core.api.exception.UpgradeFailedException; +import tech.ebp.oqm.core.api.exception.VersionBumperListIncontiguousException; +import tech.ebp.oqm.core.api.model.object.ObjectUtils; +import tech.ebp.oqm.core.api.model.object.Versionable; +import tech.ebp.oqm.core.api.model.object.upgrade.ObjectUpgradeResult; + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * Handles object upgrading for a particular object. + * @param + */ +@Slf4j +public abstract class ObjectSchemaUpgrader { + + @Getter + private final SortedSet> versionBumpers; + @Getter + private final Class objClass; + private final Map>> bumperListCacheMap = new ConcurrentHashMap<>(); + + protected ObjectSchemaUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { + this.versionBumpers = versionBumpers; + this.objClass = objClass; + + //check that the set is contiguous + int lastVersion = 1; + for(ObjectSchemaVersionBumper cur : this.versionBumpers){ + if((lastVersion + 1) != cur.getBumperTo()){ + throw new VersionBumperListIncontiguousException(lastVersion, this.objClass); + } + lastVersion = cur.getBumperTo(); + } + } + + protected ObjectSchemaUpgrader(Class objClass, ObjectSchemaVersionBumper... versionBumpers) throws VersionBumperListIncontiguousException { + this( + objClass, + new TreeSet<>(Arrays.stream(versionBumpers).toList()) + ); + } + + protected LinkedList> getBumperFromCache(int versionTo){ + if(!this.bumperListCacheMap.containsKey(versionTo)){ + return null; + } + return new LinkedList<>(this.bumperListCacheMap.get(versionTo)); + } + + protected Iterator> getBumperIteratorAtVersion(int curObjVersion){ + int curVersionTo = curObjVersion + 1; + + LinkedList> bumpers = this.getBumperFromCache(curVersionTo); + + if(bumpers != null){ + return bumpers.iterator(); + } + + bumpers = new LinkedList<>(this.versionBumpers); + + while(!bumpers.isEmpty() && bumpers.getFirst().getBumperTo() < curVersionTo){ + bumpers.removeFirst(); + } + + this.bumperListCacheMap.put(curObjVersion, bumpers); + bumpers = this.getBumperFromCache(curObjVersion); + + return bumpers.iterator(); + } + + + public ObjectUpgradeResult upgrade(JsonNode oldObj){ + int curVersion = oldObj.get("schemaVersion").asInt(1); + ObjectUpgradeResult.Builder resultBuilder = ObjectUpgradeResult.builder(); + resultBuilder.oldVersion(curVersion); + + JsonNode upgradedJson = oldObj.deepCopy(); + + StopWatch sw = StopWatch.createStarted(); + Iterator> it = getBumperIteratorAtVersion(curVersion); + while (it.hasNext()){ + ObjectSchemaVersionBumper curBumper = it.next(); + + upgradedJson = curBumper.bumpObject(upgradedJson); + } + T upgradedObj = null; + try { + upgradedObj = ObjectUtils.OBJECT_MAPPER.treeToValue(upgradedJson, this.objClass); + } catch(JsonProcessingException e) { + throw new UpgradeFailedException(e, this.getObjClass()); + } + sw.stop(); + + resultBuilder.upgradedObject(upgradedObj); + resultBuilder.timeTaken(Duration.ofMillis(sw.getTime())); + + return resultBuilder.build(); + } + + public ObjectUpgradeResult upgrade(Document oldObj) throws JsonProcessingException { + return this.upgrade( + ObjectUtils.OBJECT_MAPPER.readTree( + oldObj.toJson( + JsonWriterSettings.builder() + .build() + ) + ) + ); + } + + public boolean upgradesAvailable(){ + return !this.versionBumpers.isEmpty(); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java new file mode 100644 index 000000000..bafd957dd --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java @@ -0,0 +1,42 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import tech.ebp.oqm.core.api.model.object.Versionable; + +/** + * Abstract class to take an object from the next lower version to the version noted by {@link #bumperTo} + * + * @param + */ +public abstract class ObjectSchemaVersionBumper implements Comparable> { + + protected ObjectSchemaVersionBumper(int bumperTo) { + this.bumperTo = bumperTo; + } + + /** + * Notes what version this bumper bumps the object to. + */ + @Getter + public final int bumperTo; + + /** + * Method to mutate the given object and return the resulting upgraded object. + *

+ * The updates can happen either in-place, or using a copy of the object. It is recommended users of this method use the returned value rather than rely on pass-by-reference. + * + * @param oldObj The object to update + * + * @return The updated object + */ + public abstract JsonNode bumpObject(JsonNode oldObj); + + @Override + public int compareTo(ObjectSchemaVersionBumper tObjectSchemaVersionBumper) { + return Integer.compare( + this.getBumperTo(), + tObjectSchemaVersionBumper.getBumperTo() + ); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/inventoryItem/InventoryItemSchemaUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/inventoryItem/InventoryItemSchemaUpgrader.java new file mode 100644 index 000000000..061bbe96f --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/inventoryItem/InventoryItemSchemaUpgrader.java @@ -0,0 +1,19 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.inventoryItem; + +import tech.ebp.oqm.core.api.model.object.storage.items.InventoryItem; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectSchemaUpgrader; + +import java.util.TreeSet; + +/** + * TODO:: figure out how to handle the subtypes + */ +public class InventoryItemSchemaUpgrader extends ObjectSchemaUpgrader { + + public InventoryItemSchemaUpgrader() { + super( + InventoryItem.class, + new TreeSet<>() + ); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java new file mode 100644 index 000000000..d6810e26c --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java @@ -0,0 +1,18 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.storageBlock; + +import lombok.extern.slf4j.Slf4j; +import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectSchemaUpgrader; + +import java.util.TreeSet; + +@Slf4j +public class StorageBlockSchemaUpgrader extends ObjectSchemaUpgrader { + + public StorageBlockSchemaUpgrader() { + super( + StorageBlock.class, + new TreeSet<>() + ); + } +} diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/serviceState/db/OqmMongoDatabase.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/serviceState/db/OqmMongoDatabase.java index 61054fe24..92cf4f72b 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/serviceState/db/OqmMongoDatabase.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/serviceState/db/OqmMongoDatabase.java @@ -21,6 +21,7 @@ @AllArgsConstructor @Builder public class OqmMongoDatabase extends AttKeywordMainObject { + public static final int CUR_SCHEMA_VERSION = 1; @NotNull @Length(min = 1, max = 15) @@ -42,4 +43,9 @@ public String getDisplayName(){ } return this.displayName; } + + @Override + public int getSchemaVersion() { + return CUR_SCHEMA_VERSION; + } } diff --git a/software/oqm-core-api/src/main/resources/application.yaml b/software/oqm-core-api/src/main/resources/application.yaml index d5230be0b..7b0c3f32d 100644 --- a/software/oqm-core-api/src/main/resources/application.yaml +++ b/software/oqm-core-api/src/main/resources/application.yaml @@ -102,7 +102,8 @@ quarkus: body: preallocate-body-buffer: true limits: - max-body-size: 500M + max-body-size: 750M + max-form-attribute-size: 750M auth: proactive: false access-log: diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/TestMainObject.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/TestMainObject.java index 6075c5e4e..8053d8762 100644 --- a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/TestMainObject.java +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/TestMainObject.java @@ -13,4 +13,9 @@ public class TestMainObject extends AttKeywordMainObject { public TestMainObject(ObjectId objectId, Map atts, List keywords) { super(objectId, atts, keywords); } + + @Override + public int getSchemaVersion() { + return 1; + } } diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/object/history/ObjectHistoryEventTest.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/object/history/ObjectHistoryEventTest.java index de8563402..bd1a592a7 100644 --- a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/object/history/ObjectHistoryEventTest.java +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/model/object/history/ObjectHistoryEventTest.java @@ -18,6 +18,11 @@ public class TestObjectHistoryEvent extends DescriptiveEvent { public EventType getType() { return EventType.CREATE; } + + @Override + public int getSchemaVersion() { + return 1; + } } @Test diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexServiceTest.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexServiceTest.java new file mode 100644 index 000000000..83511be7c --- /dev/null +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/InstanceMutexServiceTest.java @@ -0,0 +1,170 @@ +package tech.ebp.oqm.core.api.service.mongo; + + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import tech.ebp.oqm.core.api.testResources.lifecycleManagers.TestResourceLifecycleManager; +import tech.ebp.oqm.core.api.testResources.testClasses.RunningServerTest; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +@Slf4j +@QuarkusTest +@QuarkusTestResource(TestResourceLifecycleManager.class) +public class InstanceMutexServiceTest extends RunningServerTest { + + public static Stream getParams() { + return Stream.of( + Arguments.of(2, 10, Duration.of(250, ChronoUnit.MILLIS)), + Arguments.of(3, 10, Duration.of(250, ChronoUnit.MILLIS)), + Arguments.of(5, 10, Duration.of(250, ChronoUnit.MILLIS)), + Arguments.of(10, 10, Duration.of(250, ChronoUnit.MILLIS)), + Arguments.of(20, 20, Duration.of(150, ChronoUnit.MILLIS)) + ); + } + + @Inject + InstanceMutexService instanceMutexService; + + @Test + public void basicTest() { + String mutexId = "testMutex"; + this.instanceMutexService.register(mutexId); + + assertTrue(this.instanceMutexService.lock(mutexId)); + + assertFalse(this.instanceMutexService.lock(mutexId)); + + this.instanceMutexService.free(mutexId); + + assertTrue(this.instanceMutexService.lock(mutexId)); + this.instanceMutexService.free(mutexId); + } + + + @ParameterizedTest + @MethodSource("getParams") + public void threadTest(int numThreads, int numIterations, Duration workDuration) throws InterruptedException, ExecutionException { + String mutexId = "testMutex2"; + List>> futures = new ArrayList<>(numThreads); + SortedSet results = new TreeSet<>(); + ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); + + TestThread.Builder threadBuilder = TestThread.builder() + .mutexId(mutexId) + .numIterations(numIterations) + .durationOfWork(workDuration) + .instanceMutexService(instanceMutexService); + + for (int i = 1; i <= numThreads; i++) { + threadBuilder.threadId("testThread-" + i); + + futures.add(executor.submit(threadBuilder.build())); + } + executor.shutdown(); + while (!executor.awaitTermination(1, TimeUnit.MINUTES)) { + log.info("Still waiting on threads..."); + } + + for (Future> future : futures) { + results.addAll(future.get()); + } + + assertEquals(numIterations * numThreads, results.size()); + + //TODO:: check results + log.info("Results: {}", results); + + Iterator iterator = results.iterator(); + ThreadResult cur = iterator.next(); + while (iterator.hasNext()) { + ThreadResult next = iterator.next(); + + assertTrue( + next.getStart().isAfter(cur.getStart()), + "result " + cur + " start overlaps with the next result " + next + " (next start is before cur start)" + ); + assertTrue( + (next.getStart().isAfter(cur.getEnd()) || next.getStart().equals(cur.getEnd())), + "result " + cur + " overlaps with the next result " + next + " (next start is before cur end)" + ); + + cur = next; + } + + } + + @Builder + @Data + @AllArgsConstructor + static + class ThreadResult implements Comparable { + private String threadId; + private LocalDateTime start; + private LocalDateTime end; + + @Override + public int compareTo(@NonNull InstanceMutexServiceTest.ThreadResult threadResult) { + return this.getStart().compareTo(threadResult.getStart()); + } + } + + @Builder + @Slf4j + @AllArgsConstructor + static class TestThread implements Callable> { + + private String mutexId; + private String threadId; + private InstanceMutexService instanceMutexService; + private int numIterations; + private Duration durationOfWork; + + @SneakyThrows + @Override + public List call() { + log.info("Running test thread {}", this.threadId); + + this.instanceMutexService.register(this.mutexId); + +// Thread.sleep(500); + + List results = new ArrayList<>(this.numIterations); + for (int i = 1; i <= this.numIterations; i++) { + log.info("Thread {} waiting for lock on iteration {}", this.threadId, i); + while (!instanceMutexService.lock(this.mutexId, Optional.of(this.threadId))) { + Thread.sleep(50); + } + log.info("Thread {} got lock on iteration {}/{}", this.threadId, i, this.numIterations); + ThreadResult.Builder resultBuilder = ThreadResult.builder() + .threadId(this.threadId) + .start(LocalDateTime.now()); + + Thread.sleep(this.durationOfWork); + + resultBuilder.end(LocalDateTime.now()); + + this.instanceMutexService.free(this.mutexId, Optional.of(this.threadId)); + log.info("Thread {} done doing work & released lock on iteration {}", this.threadId, i); + results.add(resultBuilder.build()); + } + log.info("DONE running test thread {}", this.threadId); + return results; + } + } + +} diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectServiceTest.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectServiceTest.java index 3d6c83d69..60f4ce8d3 100644 --- a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectServiceTest.java +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/MongoHistoriedObjectServiceTest.java @@ -11,12 +11,11 @@ import org.bson.types.ObjectId; import org.junit.jupiter.api.Test; import tech.ebp.oqm.core.api.model.object.ObjectUtils; -import tech.ebp.oqm.core.api.service.mongo.InteractingEntityService; +import tech.ebp.oqm.core.api.service.notification.EventNotificationWrapper; import tech.ebp.oqm.core.api.service.notification.HistoryEventNotificationService; import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; import tech.ebp.oqm.core.api.testResources.data.TestMainObject; import tech.ebp.oqm.core.api.testResources.data.TestMongoHistoriedService; -import tech.ebp.oqm.core.api.testResources.data.TestUserService; import tech.ebp.oqm.core.api.testResources.lifecycleManagers.TestResourceLifecycleManager; import tech.ebp.oqm.core.api.testResources.testClasses.RunningServerTest; import tech.ebp.oqm.core.api.model.object.history.ObjectHistoryEvent; @@ -73,24 +72,28 @@ public void testAdd() throws JsonProcessingException { assertEquals(objectId, createEvent.getObjectId()); assertNotNull(createEvent.getEntity()); assertEquals(testUser.getId(), createEvent.getEntity()); - + + ConsumerTask createFromAll = this.kafkaCompanion.consumeStrings().fromTopics( - this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, + HistoryEventNotificationService.ALL_EVENT_TOPIC, 1 ); createFromAll.awaitCompletion(); assertEquals(1, createFromAll.count()); - CreateEvent createEventFromMessage = ObjectUtils.OBJECT_MAPPER.readValue(createFromAll.getFirstRecord().value(), CreateEvent.class); - assertEquals(createEvent, createEventFromMessage); - - ConsumerTask createFromCreate = this.kafkaCompanion.consumeStrings().fromTopics( - this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, - 1 - ); - createFromCreate.awaitCompletion(); - assertEquals(1, createFromCreate.count()); - createEventFromMessage = ObjectUtils.OBJECT_MAPPER.readValue(createFromCreate.getFirstRecord().value(), CreateEvent.class); - assertEquals(createEvent, createEventFromMessage); + EventNotificationWrapper createEventFromMessage = ObjectUtils.OBJECT_MAPPER.readValue(createFromAll.getFirstRecord().value(), EventNotificationWrapper.class); + assertEquals(createEvent, createEventFromMessage.getEvent()); + + // TODO: more when we want to +// ConsumerTask createFromAllInDb = this.kafkaCompanion.consumeStrings().fromTopics( +// HistoryEventNotificationService.TOPIC_PREPEND + this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC_LABEL, +// 1 +// ); +// createFromAllInDb.awaitCompletion(); +// assertEquals(1, createFromAllInDb.count()); +// createEventFromMessage = ObjectUtils.OBJECT_MAPPER.readValue(createFromAllInDb.getFirstRecord().value(), EventNotificationWrapper.class); +// assertEquals(createEvent, createEventFromMessage.getEvent()); + + //TODO:: cover last type? } //TODO:: test rest } \ No newline at end of file diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/exception/DbDeleteRelationalExceptionTest.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/exception/DbDeleteRelationalExceptionTest.java index eaf29e416..73f0d6df2 100644 --- a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/exception/DbDeleteRelationalExceptionTest.java +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/service/mongo/exception/DbDeleteRelationalExceptionTest.java @@ -26,7 +26,12 @@ public void testMessage(){ FAKER.name().name(), new TreeSet<>(List.of(ObjectId.get())) ); - DbDeleteRelationalException e = new DbDeleteRelationalException(new MainObject(objectId){}, references); + DbDeleteRelationalException e = new DbDeleteRelationalException(new MainObject(objectId){ + @Override + public int getSchemaVersion() { + return 1; + } + }, references); log.info("Error message: {}", e.getMessage()); diff --git a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/testResources/data/TestMainObject.java b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/testResources/data/TestMainObject.java index 4609e3c31..7eb7e1030 100644 --- a/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/testResources/data/TestMainObject.java +++ b/software/oqm-core-api/src/test/java/tech/ebp/oqm/core/api/testResources/data/TestMainObject.java @@ -43,7 +43,12 @@ public TestMainObject(String testField, double floatValue){ this.setTestField(testField); this.setFloatValue(floatValue); } - + + @Override + public int getSchemaVersion() { + return 1; + } + // public TestMainObject(String testField, BigInteger bigIntValue){ // this.setTestField(testField); // this.setBigIntValue(bigIntValue); diff --git a/software/oqm-core-base-station/build.gradle b/software/oqm-core-base-station/build.gradle index fe7b7505e..852b27d70 100644 --- a/software/oqm-core-base-station/build.gradle +++ b/software/oqm-core-base-station/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'io.quarkus' - id "io.freefair.lombok" version "8.6" + id "io.freefair.lombok" version "8.7.1" } group 'com.ebp.openQuarterMaster' @@ -34,7 +34,7 @@ dependencies { implementation 'org.apache.commons:commons-text:1.12.0' implementation group: 'org.jsoup', name: 'jsoup', version: '1.18.1' - implementation 'uk.org.okapibarcode:okapibarcode:0.4.6' + implementation 'uk.org.okapibarcode:okapibarcode:0.4.7' implementation 'com.itextpdf:html2pdf:5.0.5' //webjars @@ -46,7 +46,7 @@ dependencies { testImplementation 'io.rest-assured:rest-assured' testImplementation 'net.datafaker:datafaker:2.3.1' - testImplementation 'com.microsoft.playwright:playwright:1.45.1' + testImplementation 'com.microsoft.playwright:playwright:1.46.0' testImplementation 'com.deque.html.axe-core:playwright:4.9.1' } diff --git a/software/oqm-core-base-station/installerSrc/oqm-core-base_station.service b/software/oqm-core-base-station/installerSrc/oqm-core-base_station.service index a9d559d54..dd6f8953e 100644 --- a/software/oqm-core-base-station/installerSrc/oqm-core-base_station.service +++ b/software/oqm-core-base-station/installerSrc/oqm-core-base_station.service @@ -53,6 +53,7 @@ ExecStart=/bin/bash -c "/usr/bin/docker run --rm --name $CONTAINER_NAME \ -p $(oqm-config -g core.baseStation.httpPort):80 \ -v /etc/oqm/serviceConfig/core/base+station/files:/etc/oqm/serviceConfig/core/base+station/files:ro \ --env-file /tmp/oqm/serviceConfig/core/base+station/base-station-config.list \ + --env-file /tmp/oqm/serviceConfig/core/base+station/user-config.list \ --add-host $(oqm-config -g system.hostname):host-gateway \ $IMAGE_NAME:$IMAGE_VERSION" # Ensure is up diff --git a/software/oqm-core-base-station/src/main/resources/META-INF/resources/res/js/item/extItemSearch.js b/software/oqm-core-base-station/src/main/resources/META-INF/resources/res/js/item/extItemSearch.js index d376bcefd..3d59398ee 100644 --- a/software/oqm-core-base-station/src/main/resources/META-INF/resources/res/js/item/extItemSearch.js +++ b/software/oqm-core-base-station/src/main/resources/META-INF/resources/res/js/item/extItemSearch.js @@ -116,23 +116,33 @@ const ExtItemSearch = { method: "GET", failMessagesDiv: ExtItemSearch.extItemSearchSearchFormMessages, done: async function (data) { + console.log("Got search result."); let imageId; let imageName = resultUnifiedName; if (!data.length) { - console.log("No results for given source. Adding.") + console.log("No results for given source. Adding."); //TODO:: use image add form to add image, come back to this? - let saveImageFail = false; + let filename = new URL(imageUrl).pathname; + filename = filename.substring(filename.lastIndexOf('/') + 1); + if(filename.includes(".")){ + filename = filename.split('.').slice(0, -1).join('.') + } + filename += "."+imageData.split(';')[0].split('/')[1]; + + let addData = new FormData(); + addData.append("fileName", filename); + addData.append("description", ""); + addData.append("source", imageUrl); + addData.append("file", await (await (await fetch(imageData)).blob())); + await Rest.call({ async: false, url: Rest.passRoot + "/media/image", method: "POST", - data: { - title: resultUnifiedName, - source: imageUrl, - imageData: imageData - }, + data: addData, + dataType: false, failMessagesDiv: ExtItemSearch.extItemSearchSearchFormMessages, fail: function () { saveImageFail = true; @@ -214,7 +224,7 @@ const ExtItemSearch = { let newCarImage = newCarImageDir.find("img"); newCarImage.prop("src", imageData); - let useButton = $(''); + let useButton = $(''); useButton.on("click", function () { ExtItemSearch.addOrGetAndSelectImage(curImageLoc, result.unifiedName, imageData); }); diff --git a/software/oqm-core-base-station/src/main/resources/application.yml b/software/oqm-core-base-station/src/main/resources/application.yml index 300c93803..69d09ca59 100644 --- a/software/oqm-core-base-station/src/main/resources/application.yml +++ b/software/oqm-core-base-station/src/main/resources/application.yml @@ -101,7 +101,8 @@ quarkus: body: preallocate-body-buffer: true limits: - max-body-size: 500M + max-body-size: 750M + max-form-attribute-size: 750M auth: proactive: false permission: @@ -122,7 +123,6 @@ quarkus: post-logout-path: /?message=You%20have%20successfully%20logged%20out&messageType=success&messageHeading=Logged%20Out oqmCoreApi: devservice: - coreApiVersion: 2.1.0-DEV certPath: ../../../../dev/devTest-cert-cert.pem certKeyPath: ../../../../dev/devTest-cert-key.pem diff --git a/software/oqm-core-base-station/src/main/resources/templates/webui/mainWebPageTemplate.html b/software/oqm-core-base-station/src/main/resources/templates/webui/mainWebPageTemplate.html index 56c0500b9..7a77b63da 100644 --- a/software/oqm-core-base-station/src/main/resources/templates/webui/mainWebPageTemplate.html +++ b/software/oqm-core-base-station/src/main/resources/templates/webui/mainWebPageTemplate.html @@ -58,7 +58,7 @@