From 2c76a8dfeeaae1e43a5af46f87e86c860d8b6fc6 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Tue, 25 Jun 2024 23:39:25 -0400 Subject: [PATCH 01/58] Core - API - Re-adding code from defunct branch --- .idea/compiler.xml | 53 -------- .../ClassUpgraderNotFoundException.java | 7 ++ .../api/exception/UpgradeFailedException.java | 12 ++ ...ersionBumperListIncontiguousException.java | 8 ++ .../core/api/model/object/Versionable.java | 2 +- .../model/object/upgrade/ObjectUpgrader.java | 119 ++++++++++++++++-- .../object/upgrade/ObjectVersionBumper.java | 27 +++- .../model/object/upgrade/UpgradeResult.java | 18 ++- 8 files changed, 169 insertions(+), 77 deletions(-) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/ClassUpgraderNotFoundException.java create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/UpgradeFailedException.java create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/VersionBumperListIncontiguousException.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml index ef2af761f..f2c80645e 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,59 +8,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file 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..62525d7ad --- /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 Throwable { + 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..2896962c7 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/exception/UpgradeFailedException.java @@ -0,0 +1,12 @@ +package tech.ebp.oqm.core.api.exception; + +import com.fasterxml.jackson.core.JsonProcessingException; + +public class UpgradeFailedException extends RuntimeException { + 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..bf14ead3b --- /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 Exception { + + 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/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/upgrade/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgrader.java index 5647d1e6c..ce6fad072 100644 --- 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 @@ -1,35 +1,130 @@ package tech.ebp.oqm.core.api.model.object.upgrade; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; +import lombok.NonNull; +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.ClassUpgraderNotFoundException; +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.storage.items.AmountItem; -import java.util.Arrays; -import java.util.SortedSet; -import java.util.TreeSet; +import java.time.Duration; +import java.util.*; + +/** + * Handles object upgrading for a particular object. + * @param + */ +@Slf4j public abstract class ObjectUpgrader { + + private static Map, ObjectUpgrader> instanceMap = new HashMap<>(); + + public static void clearInstanceMap(){ + instanceMap = new HashMap<>(); + } + + public static ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { + ObjectUpgrader output = null; + + if(instanceMap.containsKey(clazz)){ + output = (ObjectUpgrader) instanceMap.get(clazz); + } else if(clazz.equals(AmountItem.class)){ + //output = ; TODO + instanceMap.put(clazz, output); + } + //TODO:: rest + + if(output == null) { + throw new ClassUpgraderNotFoundException(clazz); + } + return output; + } + @Getter private final SortedSet> versionBumpers; - + @Getter private final Class objClass; - - public ObjectUpgrader(Class objClass, SortedSet> versionBumpers) { + + protected ObjectUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { this.versionBumpers = versionBumpers; this.objClass = objClass; + + //check that the set is contiguous + int lastVersion = 1; + for(ObjectVersionBumper cur : this.versionBumpers){ + if((lastVersion + 1) != cur.getBumperTo()){ + throw new VersionBumperListIncontiguousException(lastVersion, this.objClass); + } + lastVersion = cur.getBumperTo(); + } } - - public ObjectUpgrader(Class objClass, ObjectVersionBumper ... versionBumpers){ + + protected ObjectUpgrader(Class objClass, ObjectVersionBumper ... versionBumpers) throws VersionBumperListIncontiguousException { this( objClass, new TreeSet<>(Arrays.stream(versionBumpers).toList()) ); } - - + + protected Iterator> getIteratorAtVersion(int curObjVersion){ + int curVersionTo = curObjVersion + 1; + + LinkedList> bumpers = new LinkedList<>(this.versionBumpers); + + while(bumpers.getFirst().getBumperTo() < curVersionTo){ + bumpers.removeFirst(); + } + + return bumpers.iterator(); + } + + public UpgradeResult upgrade(JsonNode oldObj){ - //TODO - return null; + int curVersion = oldObj.get("schemaVersion").asInt(1); + UpgradeResult.Builder resultBuilder = UpgradeResult.builder(); + resultBuilder.oldVersion(curVersion); + + JsonNode upgradedJson = oldObj.deepCopy(); + + StopWatch sw = StopWatch.createStarted(); + Iterator> it = getIteratorAtVersion(curVersion); + while (it.hasNext()){ + ObjectVersionBumper 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.timeToUpgrade(Duration.ofMillis(sw.getTime())); + + return resultBuilder.build(); + } + + public UpgradeResult upgrade(Document oldObj) throws JsonProcessingException { + return this.upgrade( + ObjectUtils.OBJECT_MAPPER.readTree( + oldObj.toJson( + JsonWriterSettings.builder() + .build() + ) + ) + ); } } 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 index ad86c53c8..caf7617b2 100644 --- 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 @@ -4,17 +4,34 @@ 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 ObjectVersionBumper implements Comparable> { - + protected ObjectVersionBumper(int bumperTo) { this.bumperTo = bumperTo; } - + + /** + * Notes what version this bumper bumps the object to. + */ @Getter public final int bumperTo; - - public abstract JsonNode bumpObject(JsonNode node); - + + /** + * 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(ObjectVersionBumper tObjectVersionBumper) { return Integer.compare( 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 index 6c78a9a7f..f15e7c28e 100644 --- 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 @@ -7,18 +7,24 @@ import lombok.NonNull; import tech.ebp.oqm.core.api.model.object.Versionable; +import java.time.Duration; + @Data @AllArgsConstructor @Builder public class UpgradeResult { - + @NotNull @NonNull private T upgradedObject; - + + @NotNull + @NonNull + private Duration timeToUpgrade; + private int oldVersion; - - private boolean wasUpgraded(){ - return this.getOldVersion() < upgradedObject.getObjectVersion(); + + public boolean wasUpgraded(){ + return this.getOldVersion() < upgradedObject.getSchemaVersion(); } -} +} \ No newline at end of file From 2f6334b33c1ab69129beb1c6885999c973891626 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Wed, 26 Jun 2024 00:02:25 -0400 Subject: [PATCH 02/58] Core - API - Added schema versions to all final main objects --- .../oqm/core/api/config/BaseStationInteractingEntity.java | 6 ++++++ .../ebp/oqm/core/api/model/object/FileMainObject.java | 7 ++++++- .../tech/ebp/oqm/core/api/model/object/MainObject.java | 2 +- .../core/api/model/object/history/events/CreateEvent.java | 6 ++++++ .../core/api/model/object/history/events/DeleteEvent.java | 6 ++++++ .../core/api/model/object/history/events/UpdateEvent.java | 6 ++++++ .../events/externalService/ExtServiceAuthEvent.java | 6 ++++++ .../events/externalService/ExtServiceSetupEvent.java | 6 ++++++ .../object/history/events/file/NewFileVersionEvent.java | 6 ++++++ .../model/object/history/events/item/ItemAddEvent.java | 6 ++++++ .../object/history/events/item/ItemCheckinEvent.java | 6 ++++++ .../object/history/events/item/ItemCheckoutEvent.java | 6 ++++++ .../object/history/events/item/ItemLowStockEvent.java | 6 ++++++ .../model/object/history/events/item/ItemSubEvent.java | 6 ++++++ .../object/history/events/item/ItemTransferEvent.java | 6 ++++++ .../history/events/item/expiry/ItemExpiredEvent.java | 6 ++++++ .../events/item/expiry/ItemExpiryWarningEvent.java | 6 ++++++ .../history/events/itemList/ItemListActionAddEvent.java | 7 +++++++ .../events/itemList/ItemListActionDeleteEvent.java | 7 +++++++ .../events/itemList/ItemListActionUpdateEvent.java | 7 +++++++ .../history/events/itemList/ItemListApplyEvent.java | 7 +++++++ .../object/history/events/user/UserDisabledEvent.java | 6 ++++++ .../object/history/events/user/UserEnabledEvent.java | 6 ++++++ .../model/object/history/events/user/UserLoginEvent.java | 6 ++++++ .../interactingEntity/externalService/GeneralService.java | 6 ++++++ .../externalService/plugin/PluginService.java | 6 ++++++ .../api/model/object/interactingEntity/user/User.java | 6 ++++++ .../ebp/oqm/core/api/model/object/itemList/ItemList.java | 6 ++++++ .../oqm/core/api/model/object/storage/ItemCategory.java | 6 ++++++ .../api/model/object/storage/checkout/ItemCheckout.java | 6 ++++++ .../api/model/object/storage/items/ListAmountItem.java | 6 ++++++ .../api/model/object/storage/items/SimpleAmountItem.java | 7 ++++++- .../core/api/model/object/storage/items/TrackedItem.java | 6 ++++++ .../model/object/storage/storageBlock/StorageBlock.java | 8 +++++++- .../ebp/oqm/core/api/model/units/CustomUnitEntry.java | 6 ++++++ .../api/service/serviceState/db/OqmMongoDatabase.java | 6 ++++++ .../java/tech/ebp/oqm/core/api/model/TestMainObject.java | 5 +++++ .../api/model/object/history/ObjectHistoryEventTest.java | 5 +++++ .../mongo/exception/DbDeleteRelationalExceptionTest.java | 7 ++++++- .../oqm/core/api/testResources/data/TestMainObject.java | 7 ++++++- 40 files changed, 238 insertions(+), 6 deletions(-) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/BaseStationInteractingEntity.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/BaseStationInteractingEntity.java index 59dbd47ff..9f5e640b0 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/BaseStationInteractingEntity.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/config/BaseStationInteractingEntity.java @@ -18,6 +18,7 @@ @NoArgsConstructor @BsonDiscriminator public class BaseStationInteractingEntity 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. @@ -59,4 +60,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/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/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..95d30796c 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,9 @@ 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/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/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/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/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); From 7da3f8c4984eadde56cad8d96a9d1f6a3b8f8118 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 1 Jul 2024 19:58:45 -0400 Subject: [PATCH 03/58] Core - API - Fleshed out some upgrader flow --- .../api/exception/UpgradeFailedException.java | 18 +++ ...ersionBumperListIncontiguousException.java | 2 +- .../storage/storageBlock/StorageBlock.java | 1 + .../upgrade/CollectionUpgradeResult.java | 18 +++ ...deResult.java => ObjectUpgradeResult.java} | 11 +- .../object/upgrade/OqmDbUpgradeResult.java | 19 +++ .../object/upgrade/TotalUpgradeResult.java | 19 +++ .../service/mongo/MongoDbAwareService.java | 2 +- .../ObjectUpgradeService.java | 139 ++++++++++++++++++ .../ObjectVersionBumper.java | 2 +- .../upgraders}/ObjectUpgrader.java | 36 +---- .../upgraders/StorageBlockUpgrader.java | 19 +++ 12 files changed, 246 insertions(+), 40 deletions(-) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/CollectionUpgradeResult.java rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/{UpgradeResult.java => ObjectUpgradeResult.java} (65%) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/OqmDbUpgradeResult.java create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/TotalUpgradeResult.java create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/{model/object/upgrade => service/schemaVersioning}/ObjectVersionBumper.java (95%) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/{model/object/upgrade => service/schemaVersioning/upgraders}/ObjectUpgrader.java (72%) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java 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 index 2896962c7..6ad82aafb 100644 --- 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 @@ -3,6 +3,24 @@ 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(), 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 index bf14ead3b..0de092571 100644 --- 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 @@ -1,6 +1,6 @@ package tech.ebp.oqm.core.api.exception; -public class VersionBumperListIncontiguousException extends 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()); 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 95d30796c..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 @@ -98,4 +98,5 @@ public String getLabelText() { 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/UpgradeResult.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgradeResult.java similarity index 65% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/UpgradeResult.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgradeResult.java index f15e7c28e..947e363a0 100644 --- 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/ObjectUpgradeResult.java @@ -1,18 +1,15 @@ 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 lombok.*; import tech.ebp.oqm.core.api.model.object.Versionable; import java.time.Duration; @Data -@AllArgsConstructor +@Setter(AccessLevel.PRIVATE) @Builder -public class UpgradeResult { +public class ObjectUpgradeResult { @NotNull @NonNull @@ -20,7 +17,7 @@ public class UpgradeResult { @NotNull @NonNull - private Duration timeToUpgrade; + private Duration timeTaken; private int oldVersion; 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/service/mongo/MongoDbAwareService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java index 65ab047db..03db26ecf 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 @@ -137,7 +137,7 @@ protected MongoCollection getCollection(DbCacheEntry db) { return this.collections.get(db.getDbId()); } - protected MongoCollection getCollection(String oqmDbIdOrName) { + public MongoCollection getCollection(String oqmDbIdOrName) { return this.getCollection(this.getOqmDatabaseService().getOqmDatabase(oqmDbIdOrName)); } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java new file mode 100644 index 000000000..f7cfe8aa7 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -0,0 +1,139 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.time.StopWatch; +import tech.ebp.oqm.core.api.exception.ClassUpgraderNotFoundException; +import tech.ebp.oqm.core.api.exception.UpgradeFailedException; +import tech.ebp.oqm.core.api.model.object.Versionable; +import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; +import tech.ebp.oqm.core.api.model.object.upgrade.CollectionUpgradeResult; +import tech.ebp.oqm.core.api.model.object.upgrade.OqmDbUpgradeResult; +import tech.ebp.oqm.core.api.model.object.upgrade.TotalUpgradeResult; +import tech.ebp.oqm.core.api.service.mongo.MongoDbAwareService; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.StorageBlockUpgrader; +import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; +import tech.ebp.oqm.core.api.service.serviceState.db.OqmMongoDatabase; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +@ApplicationScoped +@Slf4j +public class ObjectUpgradeService { + + private Map, ObjectUpgrader> upgraderMap; + private OqmDatabaseService oqmDatabaseService; + private List oqmDbServices; + + public ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { + if (!this.upgraderMap.containsKey(clazz)) { + throw new ClassUpgraderNotFoundException(clazz); + } + return (ObjectUpgrader) this.upgraderMap.get(clazz); + } + + private void clearUpgraderMap() { + this.upgraderMap = null; + } + + @Inject + public ObjectUpgradeService( + OqmDatabaseService oqmDatabaseService + ) { + this.oqmDatabaseService = oqmDatabaseService; + + this.upgraderMap = Map.of( + StorageBlock.class, new StorageBlockUpgrader() + ); + ; + } + + public boolean upgradeRan() { + return this.upgraderMap == null; + } + + private CollectionUpgradeResult upgradeOqmCollection(OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { + CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder(); + StopWatch collectionUpgradeTime = StopWatch.createStarted(); + + List> resultMap = new ArrayList<>(); + + MongoCollection collection = service.getCollection(oqmDb.getName()); + ObjectUpgrader objectVersionBumper = this.getInstanceForClass(service.getClazz()); + + for (MongoCursor it = collection.find().cursor(); it.hasNext(); ) { + //TODO:: get as bson, not object. feed to upgrader +// objectVersionBumper.upgrade() + + } + + + + collectionUpgradeTime.stop(); + outputBuilder.timeTaken(Duration.of(collectionUpgradeTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + + return outputBuilder.build(); + } + + + private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { + OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder(); + StopWatch dbUpgradeTime = StopWatch.createStarted(); + + List> resultMap = new ArrayList<>(); + + for (MongoDbAwareService curService : this.oqmDbServices) { + resultMap.add( + CompletableFuture.supplyAsync(() -> { + return upgradeOqmCollection(oqmDb, curService); + }) + ); + } + + dbUpgradeTime.stop(); + outputBuilder.timeTaken(Duration.of(dbUpgradeTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + + return outputBuilder.build(); + } + + public Optional updateSchema() { + if (this.upgradeRan()) { + return Optional.empty(); + } + + 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)); + + return Optional.of(totalResultBuilder.build()); + } +} 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/service/schemaVersioning/ObjectVersionBumper.java similarity index 95% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectVersionBumper.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectVersionBumper.java index caf7617b2..1e8f0c70e 100644 --- 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/service/schemaVersioning/ObjectVersionBumper.java @@ -1,4 +1,4 @@ -package tech.ebp.oqm.core.api.model.object.upgrade; +package tech.ebp.oqm.core.api.service.schemaVersioning; import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; 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/service/schemaVersioning/upgraders/ObjectUpgrader.java similarity index 72% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/model/object/upgrade/ObjectUpgrader.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java index ce6fad072..4f5d91636 100644 --- 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/service/schemaVersioning/upgraders/ObjectUpgrader.java @@ -1,20 +1,19 @@ -package tech.ebp.oqm.core.api.model.object.upgrade; +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.NonNull; 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.ClassUpgraderNotFoundException; 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.storage.items.AmountItem; +import tech.ebp.oqm.core.api.model.object.upgrade.ObjectUpgradeResult; +import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectVersionBumper; import java.time.Duration; import java.util.*; @@ -27,29 +26,6 @@ @Slf4j public abstract class ObjectUpgrader { - private static Map, ObjectUpgrader> instanceMap = new HashMap<>(); - - public static void clearInstanceMap(){ - instanceMap = new HashMap<>(); - } - - public static ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { - ObjectUpgrader output = null; - - if(instanceMap.containsKey(clazz)){ - output = (ObjectUpgrader) instanceMap.get(clazz); - } else if(clazz.equals(AmountItem.class)){ - //output = ; TODO - instanceMap.put(clazz, output); - } - //TODO:: rest - - if(output == null) { - throw new ClassUpgraderNotFoundException(clazz); - } - return output; - } - @Getter private final SortedSet> versionBumpers; @Getter @@ -89,9 +65,9 @@ protected Iterator> getIteratorAtVersion(int curObjVersio } - public UpgradeResult upgrade(JsonNode oldObj){ + public ObjectUpgradeResult upgrade(JsonNode oldObj){ int curVersion = oldObj.get("schemaVersion").asInt(1); - UpgradeResult.Builder resultBuilder = UpgradeResult.builder(); + ObjectUpgradeResult.Builder resultBuilder = ObjectUpgradeResult.builder(); resultBuilder.oldVersion(curVersion); JsonNode upgradedJson = oldObj.deepCopy(); @@ -117,7 +93,7 @@ public UpgradeResult upgrade(JsonNode oldObj){ return resultBuilder.build(); } - public UpgradeResult upgrade(Document oldObj) throws JsonProcessingException { + public ObjectUpgradeResult upgrade(Document oldObj) throws JsonProcessingException { return this.upgrade( ObjectUtils.OBJECT_MAPPER.readTree( oldObj.toJson( diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java new file mode 100644 index 000000000..645a15df7 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java @@ -0,0 +1,19 @@ +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders; + +import lombok.extern.slf4j.Slf4j; +import tech.ebp.oqm.core.api.exception.VersionBumperListIncontiguousException; +import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; + +import java.util.SortedSet; +import java.util.TreeSet; + +@Slf4j +public class StorageBlockUpgrader extends ObjectUpgrader { + + public StorageBlockUpgrader() { + super( + StorageBlock.class, + new TreeSet<>() + ); + } +} From f6d66d49435a63d62712027c29bd810f0b860848 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 13 Jul 2024 21:35:59 -0400 Subject: [PATCH 04/58] Core - API - fleshed out some ideas --- .idea/compiler.xml | 53 +++++++++++++++++++ .../ObjectUpgradeService.java | 32 ++++++----- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f2c80645e..ef2af761f 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -8,6 +8,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java index f7cfe8aa7..012913ba5 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -7,6 +7,7 @@ import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.StopWatch; +import org.bson.Document; import tech.ebp.oqm.core.api.exception.ClassUpgraderNotFoundException; import tech.ebp.oqm.core.api.exception.UpgradeFailedException; import tech.ebp.oqm.core.api.model.object.Versionable; @@ -61,27 +62,34 @@ public boolean upgradeRan() { return this.upgraderMap == null; } - private CollectionUpgradeResult upgradeOqmCollection(OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { - CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder(); - StopWatch collectionUpgradeTime = StopWatch.createStarted(); - - List> resultMap = new ArrayList<>(); - - MongoCollection collection = service.getCollection(oqmDb.getName()); - ObjectUpgrader objectVersionBumper = this.getInstanceForClass(service.getClazz()); + private CollectionUpgradeResult upgradeOqmCollection(MongoCollection collection, Class objectClass) throws ClassUpgraderNotFoundException { + ObjectUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); + CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder() + .collectionName(collection.getNamespace().getCollectionName()); + StopWatch sw = StopWatch.createStarted(); for (MongoCursor it = collection.find().cursor(); it.hasNext(); ) { //TODO:: get as bson, not object. feed to upgrader // objectVersionBumper.upgrade() - } + sw.stop(); + outputBuilder.timeTaken(Duration.of(sw.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + + return outputBuilder.build(); + } + private CollectionUpgradeResult upgradeOqmCollection(OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { + CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder(); + StopWatch collectionUpgradeTime = StopWatch.createStarted(); + List> resultMap = new ArrayList<>(); - collectionUpgradeTime.stop(); - outputBuilder.timeTaken(Duration.of(collectionUpgradeTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + MongoCollection collection = service.getCollection(oqmDb.getName()); - return outputBuilder.build(); + return this.upgradeOqmCollection( + //TODO:: get collection as Document type + , service.getClazz() + ); } From 372bd0c6258901c48152680f4cee59e4dff61ec8 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 15 Jul 2024 18:48:02 -0400 Subject: [PATCH 05/58] Core - API - More tweaks --- .../ObjectUpgradeService.java | 52 +++++++++++++++---- .../upgraders/ObjectUpgrader.java | 2 +- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java index 012913ba5..6f0fb5065 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -1,5 +1,7 @@ package tech.ebp.oqm.core.api.service.schemaVersioning; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.mongodb.client.ClientSession; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; import jakarta.enterprise.context.ApplicationScoped; @@ -8,11 +10,14 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.StopWatch; import org.bson.Document; +import org.bson.codecs.configuration.CodecRegistry; import tech.ebp.oqm.core.api.exception.ClassUpgraderNotFoundException; import tech.ebp.oqm.core.api.exception.UpgradeFailedException; +import tech.ebp.oqm.core.api.model.object.MainObject; import tech.ebp.oqm.core.api.model.object.Versionable; import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; import tech.ebp.oqm.core.api.model.object.upgrade.CollectionUpgradeResult; +import tech.ebp.oqm.core.api.model.object.upgrade.ObjectUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.OqmDbUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.TotalUpgradeResult; import tech.ebp.oqm.core.api.service.mongo.MongoDbAwareService; @@ -27,12 +32,15 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import static com.mongodb.client.model.Filters.eq; + @ApplicationScoped @Slf4j public class ObjectUpgradeService { private Map, ObjectUpgrader> upgraderMap; private OqmDatabaseService oqmDatabaseService; + private CodecRegistry codecRegistry; private List oqmDbServices; public ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { @@ -48,9 +56,11 @@ private void clearUpgraderMap() { @Inject public ObjectUpgradeService( - OqmDatabaseService oqmDatabaseService + OqmDatabaseService oqmDatabaseService, + CodecRegistry codecRegistry ) { this.oqmDatabaseService = oqmDatabaseService; + this.codecRegistry = codecRegistry; this.upgraderMap = Map.of( StorageBlock.class, new StorageBlockUpgrader() @@ -62,23 +72,40 @@ public boolean upgradeRan() { return this.upgraderMap == null; } - private CollectionUpgradeResult upgradeOqmCollection(MongoCollection collection, Class objectClass) throws ClassUpgraderNotFoundException { - ObjectUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); + private CollectionUpgradeResult upgradeOqmCollection(ClientSession cs, MongoCollection documentCollection, MongoCollection typedCollection, Class objectClass) throws ClassUpgraderNotFoundException { + ObjectUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder() - .collectionName(collection.getNamespace().getCollectionName()); + .collectionName(documentCollection.getNamespace().getCollectionName()); StopWatch sw = StopWatch.createStarted(); - for (MongoCursor it = collection.find().cursor(); it.hasNext(); ) { - //TODO:: get as bson, not object. feed to upgrader -// objectVersionBumper.upgrade() + long numUpdated = 0; + + 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)); + outputBuilder.timeTaken(Duration.of(sw.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)) + .numObjectsUpgraded(numUpdated); return outputBuilder.build(); } - private CollectionUpgradeResult upgradeOqmCollection(OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { + private CollectionUpgradeResult upgradeOqmCollection(ClientSession dbCs, OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder(); StopWatch collectionUpgradeTime = StopWatch.createStarted(); @@ -87,18 +114,23 @@ private CollectionUpgradeResult upgradeOqmCollection(OqmMongoDatabase oqmDb, Mon MongoCollection collection = service.getCollection(oqmDb.getName()); return this.upgradeOqmCollection( + dbCs, + service.getCollection(), + service.getCollectionName() //TODO:: get collection as Document type , service.getClazz() ); } - private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { + private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb, ClientSession dbCs) { OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder(); StopWatch dbUpgradeTime = StopWatch.createStarted(); List> resultMap = new ArrayList<>(); + + for (MongoDbAwareService curService : this.oqmDbServices) { resultMap.add( CompletableFuture.supplyAsync(() -> { diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java index 4f5d91636..162168f34 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java @@ -88,7 +88,7 @@ public ObjectUpgradeResult upgrade(JsonNode oldObj){ sw.stop(); resultBuilder.upgradedObject(upgradedObj); - resultBuilder.timeToUpgrade(Duration.ofMillis(sw.getTime())); + resultBuilder.timeTaken(Duration.ofMillis(sw.getTime())); return resultBuilder.build(); } From f5547a31626dee7613c337c307637260034f5698 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Tue, 30 Jul 2024 11:02:45 -0400 Subject: [PATCH 06/58] Core - API - Finished most of db schema update logic --- .../ClassUpgraderNotFoundException.java | 2 +- .../service/mongo/HasParentObjService.java | 2 +- .../service/mongo/MongoDbAwareService.java | 20 +++++-- .../mongo/MongoHistoriedObjectService.java | 2 +- .../service/mongo/MongoHistoryService.java | 6 +- .../api/service/mongo/MongoObjectService.java | 30 +++++----- .../ObjectUpgradeService.java | 60 ++++++++++--------- 7 files changed, 68 insertions(+), 54 deletions(-) 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 index 62525d7ad..054b6a299 100644 --- 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 @@ -1,6 +1,6 @@ package tech.ebp.oqm.core.api.exception; -public class ClassUpgraderNotFoundException extends Throwable { +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/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/MongoDbAwareService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/mongo/MongoDbAwareService.java index 6463520ec..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()); } - - public 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/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java index 6f0fb5065..9cd58beff 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -40,8 +40,7 @@ public class ObjectUpgradeService { private Map, ObjectUpgrader> upgraderMap; private OqmDatabaseService oqmDatabaseService; - private CodecRegistry codecRegistry; - private List oqmDbServices; + private List> oqmDbServices; public ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { if (!this.upgraderMap.containsKey(clazz)) { @@ -56,11 +55,10 @@ private void clearUpgraderMap() { @Inject public ObjectUpgradeService( - OqmDatabaseService oqmDatabaseService, - CodecRegistry codecRegistry + OqmDatabaseService oqmDatabaseService ) { this.oqmDatabaseService = oqmDatabaseService; - this.codecRegistry = codecRegistry; + //TODO:: populate oqmDbServices this.upgraderMap = Map.of( StorageBlock.class, new StorageBlockUpgrader() @@ -105,40 +103,48 @@ private CollectionUpgradeResult upgradeOqmCollection(Clie return outputBuilder.build(); } - private CollectionUpgradeResult upgradeOqmCollection(ClientSession dbCs, OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { - CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder(); - StopWatch collectionUpgradeTime = StopWatch.createStarted(); - - List> resultMap = new ArrayList<>(); - - MongoCollection collection = service.getCollection(oqmDb.getName()); - + private CollectionUpgradeResult upgradeOqmCollection(ClientSession dbCs, OqmMongoDatabase oqmDb, MongoDbAwareService service) throws ClassUpgraderNotFoundException { + String oqmDbId = oqmDb.getId().toHexString(); return this.upgradeOqmCollection( dbCs, - service.getCollection(), - service.getCollectionName() - //TODO:: get collection as Document type - , service.getClazz() + service.getDocumentCollection(oqmDbId), + service.getTypedCollection(oqmDbId), + service.getClazz() ); } - private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb, ClientSession dbCs) { + private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder(); StopWatch dbUpgradeTime = StopWatch.createStarted(); - List> resultMap = new ArrayList<>(); + List> futures = new ArrayList<>(); + ClientSession cs = null; - - - for (MongoDbAwareService curService : this.oqmDbServices) { - resultMap.add( - CompletableFuture.supplyAsync(() -> { - return upgradeOqmCollection(oqmDb, curService); - }) - ); + 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)); From 2d976632e9a9666578ed62c8b66308316613d561 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:52:54 +0000 Subject: [PATCH 07/58] Bump io.quarkiverse.wiremock:quarkus-wiremock Bumps [io.quarkiverse.wiremock:quarkus-wiremock](https://github.com/quarkiverse/quarkus-wiremock) from 1.3.2 to 1.3.3. - [Release notes](https://github.com/quarkiverse/quarkus-wiremock/releases) - [Commits](https://github.com/quarkiverse/quarkus-wiremock/compare/1.3.2...1.3.3) --- updated-dependencies: - dependency-name: io.quarkiverse.wiremock:quarkus-wiremock dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- software/plugins/external-item-search/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/plugins/external-item-search/build.gradle b/software/plugins/external-item-search/build.gradle index dc7d28603..e6fc0ad70 100644 --- a/software/plugins/external-item-search/build.gradle +++ b/software/plugins/external-item-search/build.gradle @@ -26,7 +26,7 @@ dependencies { implementation 'io.quarkus:quarkus-arc' implementation group: 'org.jsoup', name: 'jsoup', version: '1.17.2' - implementation 'io.quarkiverse.wiremock:quarkus-wiremock:1.3.2' + implementation 'io.quarkiverse.wiremock:quarkus-wiremock:1.3.3' testImplementation 'io.quarkus:quarkus-junit5' From 62ef62d896d809aa1943370a468924865cdebdf1 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 3 Aug 2024 15:27:20 -0400 Subject: [PATCH 08/58] Core - Base Station - Fixing adding an image from ext item search --- .../resources/res/js/item/extItemSearch.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) 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..e64704db9 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,26 @@ 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 addData = new FormData(); + addData.append("fileName", resultUnifiedName); + addData.append("description", ""); + addData.append("source", imageUrl); + addData.append("file", imageData); + 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 +217,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); }); From 1495aed17fe87a107413ce6112da49628bfb56bc Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 3 Aug 2024 21:51:46 -0400 Subject: [PATCH 09/58] Core - Base Station - Fixing issue uploading images from external image search --- .../installerSrc/oqm-core-base_station.service | 1 + .../META-INF/resources/res/js/item/extItemSearch.js | 11 +++++++++-- .../src/main/resources/application.yml | 3 ++- 3 files changed, 12 insertions(+), 3 deletions(-) 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 e64704db9..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 @@ -124,11 +124,18 @@ const ExtItemSearch = { //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", resultUnifiedName); + addData.append("fileName", filename); addData.append("description", ""); addData.append("source", imageUrl); - addData.append("file", imageData); + addData.append("file", await (await (await fetch(imageData)).blob())); await Rest.call({ async: false, 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..25f426803 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: From 8aea941aaf47a52aa7b55245c843a59dc755a733 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 3 Aug 2024 21:53:38 -0400 Subject: [PATCH 10/58] Core - API - tweak to limits and more output for image adding. Removed unused dependency --- software/oqm-core-api/build.gradle | 2 -- .../ebp/oqm/core/api/service/mongo/image/ImageService.java | 7 +++++++ software/oqm-core-api/src/main/resources/application.yaml | 3 ++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/software/oqm-core-api/build.gradle b/software/oqm-core-api/build.gradle index d0a593e22..640b72820 100644 --- a/software/oqm-core-api/build.gradle +++ b/software/oqm-core-api/build.gradle @@ -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' 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/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: From fb197acb93d671f1ce917ae0199c0a13a9e3216a Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 3 Aug 2024 22:36:01 -0400 Subject: [PATCH 11/58] Core - API - More logging for upgrader service --- .../schemaVersioning/ObjectUpgradeService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java index 9cd58beff..f5855bf70 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -104,17 +104,22 @@ private CollectionUpgradeResult upgradeOqmCollection(Clie } 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(); - return this.upgradeOqmCollection( + 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(); StopWatch dbUpgradeTime = StopWatch.createStarted(); @@ -148,6 +153,8 @@ private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { dbUpgradeTime.stop(); outputBuilder.timeTaken(Duration.of(dbUpgradeTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + log.info("Done updating oqm database: {}", oqmDb); + return outputBuilder.build(); } @@ -155,6 +162,7 @@ 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(); @@ -180,6 +188,8 @@ public Optional updateSchema() { totalTime.stop(); totalResultBuilder.timeTaken(Duration.of(totalTime.getTime(TimeUnit.MILLISECONDS), ChronoUnit.MILLIS)); + log.info("DONE upgrading the schema held in the Database."); + return Optional.of(totalResultBuilder.build()); } } From be3d4e0395b9492a458de7c78a2f40c149b70fb8 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 01:04:14 -0400 Subject: [PATCH 12/58] Core - API - Now incorporating upgrade process in startup --- .../ebp/oqm/core/api/scheduled/LifecycleBean.java | 15 ++++++++++++++- .../schemaVersioning/ObjectUpgradeService.java | 15 ++++++++++----- .../upgraders/ObjectUpgrader.java | 3 +-- .../{ => upgraders}/ObjectVersionBumper.java | 2 +- .../{ => storageBlock}/StorageBlockUpgrader.java | 5 ++--- 5 files changed, 28 insertions(+), 12 deletions(-) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/{ => upgraders}/ObjectVersionBumper.java (94%) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/{ => storageBlock}/StorageBlockUpgrader.java (77%) 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..8e1f9842d 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.ObjectUpgradeService; 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 + ObjectUpgradeService objectUpgradeService; 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.objectUpgradeService.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/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java index f5855bf70..bd0f9684a 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java @@ -10,7 +10,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.time.StopWatch; import org.bson.Document; -import org.bson.codecs.configuration.CodecRegistry; import tech.ebp.oqm.core.api.exception.ClassUpgraderNotFoundException; import tech.ebp.oqm.core.api.exception.UpgradeFailedException; import tech.ebp.oqm.core.api.model.object.MainObject; @@ -21,8 +20,9 @@ import tech.ebp.oqm.core.api.model.object.upgrade.OqmDbUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.TotalUpgradeResult; import tech.ebp.oqm.core.api.service.mongo.MongoDbAwareService; +import tech.ebp.oqm.core.api.service.mongo.StorageBlockService; import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectUpgrader; -import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.StorageBlockUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.storageBlock.StorageBlockUpgrader; import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; import tech.ebp.oqm.core.api.service.serviceState.db.OqmMongoDatabase; @@ -55,10 +55,14 @@ private void clearUpgraderMap() { @Inject public ObjectUpgradeService( - OqmDatabaseService oqmDatabaseService + OqmDatabaseService oqmDatabaseService, + StorageBlockService storageBlockService ) { this.oqmDatabaseService = oqmDatabaseService; - //TODO:: populate oqmDbServices + //TODO:: populate rest of oqmDbServices + this.oqmDbServices = List.of( + storageBlockService + ); this.upgraderMap = Map.of( StorageBlock.class, new StorageBlockUpgrader() @@ -120,7 +124,8 @@ private CollectionUpgradeResult upgradeOqmCollection(Clie private OqmDbUpgradeResult upgradeOqmDb(OqmMongoDatabase oqmDb) { log.info("Updating schema of oqm database: {}", oqmDb); - OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder(); + OqmDbUpgradeResult.Builder outputBuilder = OqmDbUpgradeResult.builder() + .dbName(oqmDb.getName()); StopWatch dbUpgradeTime = StopWatch.createStarted(); List> futures = new ArrayList<>(); diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java index 162168f34..9349fb75f 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java @@ -13,7 +13,6 @@ 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 tech.ebp.oqm.core.api.service.schemaVersioning.ObjectVersionBumper; import java.time.Duration; import java.util.*; @@ -57,7 +56,7 @@ protected Iterator> getIteratorAtVersion(int curObjVersio LinkedList> bumpers = new LinkedList<>(this.versionBumpers); - while(bumpers.getFirst().getBumperTo() < curVersionTo){ + while(!bumpers.isEmpty() && bumpers.getFirst().getBumperTo() < curVersionTo){ bumpers.removeFirst(); } diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectVersionBumper.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectVersionBumper.java similarity index 94% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectVersionBumper.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectVersionBumper.java index 1e8f0c70e..cfc42109c 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectVersionBumper.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectVersionBumper.java @@ -1,4 +1,4 @@ -package tech.ebp.oqm.core.api.service.schemaVersioning; +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders; import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockUpgrader.java similarity index 77% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockUpgrader.java index 645a15df7..cdb8b6e41 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/StorageBlockUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockUpgrader.java @@ -1,10 +1,9 @@ -package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders; +package tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.storageBlock; import lombok.extern.slf4j.Slf4j; -import tech.ebp.oqm.core.api.exception.VersionBumperListIncontiguousException; import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectUpgrader; -import java.util.SortedSet; import java.util.TreeSet; @Slf4j From 48ccf8f667eac215858f630415d964c256cf476c Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 01:47:50 -0400 Subject: [PATCH 13/58] Core - API - Added caching of version bumper lists in ObjectUpgrader --- .../upgraders/ObjectUpgrader.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java index 9349fb75f..ed4c9ca86 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java @@ -16,6 +16,7 @@ import java.time.Duration; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; /** @@ -29,6 +30,7 @@ public abstract class ObjectUpgrader { private final SortedSet> versionBumpers; @Getter private final Class objClass; + private final Map>> cacheMap = new ConcurrentHashMap<>(); protected ObjectUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { this.versionBumpers = versionBumpers; @@ -51,15 +53,31 @@ protected ObjectUpgrader(Class objClass, ObjectVersionBumper ... versionBu ); } + protected LinkedList> getFromCache(int versionTo){ + if(!this.cacheMap.containsKey(versionTo)){ + return null; + } + return new LinkedList<>(this.cacheMap.get(versionTo)); + } + protected Iterator> getIteratorAtVersion(int curObjVersion){ int curVersionTo = curObjVersion + 1; - LinkedList> bumpers = new LinkedList<>(this.versionBumpers); + LinkedList> bumpers = this.getFromCache(curVersionTo); + + if(bumpers != null){ + return bumpers.iterator(); + } + + bumpers = new LinkedList<>(this.versionBumpers); while(!bumpers.isEmpty() && bumpers.getFirst().getBumperTo() < curVersionTo){ bumpers.removeFirst(); } + this.cacheMap.put(curObjVersion, bumpers); + bumpers = this.getFromCache(curObjVersion); + return bumpers.iterator(); } From ef235a39c676fbe672930f755c1019f373c2e9d6 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 16:58:00 -0400 Subject: [PATCH 14/58] Core - API - Renaming of schema upgrading utilities and addition of method to skip iteration if don't need to --- .../oqm/core/api/scheduled/LifecycleBean.java | 6 +- ...e.java => ObjectSchemaUpgradeService.java} | 59 +++++++++++-------- ...pgrader.java => ObjectSchemaUpgrader.java} | 26 ++++---- ...er.java => ObjectSchemaVersionBumper.java} | 8 +-- .../InventoryItemSchemaUpgrader.java | 19 ++++++ ...r.java => StorageBlockSchemaUpgrader.java} | 6 +- 6 files changed, 77 insertions(+), 47 deletions(-) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/{ObjectUpgradeService.java => ObjectSchemaUpgradeService.java} (79%) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/{ObjectUpgrader.java => ObjectSchemaUpgrader.java} (72%) rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/{ObjectVersionBumper.java => ObjectSchemaVersionBumper.java} (75%) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/inventoryItem/InventoryItemSchemaUpgrader.java rename software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/{StorageBlockUpgrader.java => StorageBlockSchemaUpgrader.java} (70%) 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 8e1f9842d..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 @@ -12,7 +12,7 @@ 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.ObjectUpgradeService; +import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectSchemaUpgradeService; import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; import java.nio.file.Paths; @@ -40,7 +40,7 @@ public class LifecycleBean { OqmDatabaseService dbService; @Inject - ObjectUpgradeService objectUpgradeService; + ObjectSchemaUpgradeService objectSchemaUpgradeService; private ZonedDateTime startDateTime; @@ -101,7 +101,7 @@ void onStart( //ensures we can write to temp dir this.tempFileService.getTempDir("test", "dir"); // Upgrade the db schema - Optional schemaUpgradeResult = this.objectUpgradeService.updateSchema(); + Optional schemaUpgradeResult = this.objectSchemaUpgradeService.updateSchema(); if(schemaUpgradeResult.isEmpty()){ log.warn("Did not upgrade schema at start."); } else { diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java similarity index 79% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java index bd0f9684a..3b1b208cf 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java @@ -14,15 +14,18 @@ import tech.ebp.oqm.core.api.exception.UpgradeFailedException; import tech.ebp.oqm.core.api.model.object.MainObject; import tech.ebp.oqm.core.api.model.object.Versionable; +import tech.ebp.oqm.core.api.model.object.storage.items.InventoryItem; import tech.ebp.oqm.core.api.model.object.storage.storageBlock.StorageBlock; import tech.ebp.oqm.core.api.model.object.upgrade.CollectionUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.ObjectUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.OqmDbUpgradeResult; import tech.ebp.oqm.core.api.model.object.upgrade.TotalUpgradeResult; +import tech.ebp.oqm.core.api.service.mongo.InventoryItemService; import tech.ebp.oqm.core.api.service.mongo.MongoDbAwareService; import tech.ebp.oqm.core.api.service.mongo.StorageBlockService; -import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectUpgrader; -import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.storageBlock.StorageBlockUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectSchemaUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.inventoryItem.InventoryItemSchemaUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.storageBlock.StorageBlockSchemaUpgrader; import tech.ebp.oqm.core.api.service.serviceState.db.OqmDatabaseService; import tech.ebp.oqm.core.api.service.serviceState.db.OqmMongoDatabase; @@ -36,17 +39,17 @@ @ApplicationScoped @Slf4j -public class ObjectUpgradeService { +public class ObjectSchemaUpgradeService { - private Map, ObjectUpgrader> upgraderMap; + private Map, ObjectSchemaUpgrader> upgraderMap; private OqmDatabaseService oqmDatabaseService; private List> oqmDbServices; - public ObjectUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { + public ObjectSchemaUpgrader getInstanceForClass(@NonNull Class clazz) throws ClassUpgraderNotFoundException { if (!this.upgraderMap.containsKey(clazz)) { throw new ClassUpgraderNotFoundException(clazz); } - return (ObjectUpgrader) this.upgraderMap.get(clazz); + return (ObjectSchemaUpgrader) this.upgraderMap.get(clazz); } private void clearUpgraderMap() { @@ -54,20 +57,22 @@ private void clearUpgraderMap() { } @Inject - public ObjectUpgradeService( + public ObjectSchemaUpgradeService( OqmDatabaseService oqmDatabaseService, - StorageBlockService storageBlockService + StorageBlockService storageBlockService, + InventoryItemService inventoryItemService ) { this.oqmDatabaseService = oqmDatabaseService; //TODO:: populate rest of oqmDbServices this.oqmDbServices = List.of( - storageBlockService + storageBlockService, + inventoryItemService ); this.upgraderMap = Map.of( - StorageBlock.class, new StorageBlockUpgrader() + StorageBlock.class, new StorageBlockSchemaUpgrader(), + InventoryItem.class, new InventoryItemSchemaUpgrader() ); - ; } public boolean upgradeRan() { @@ -75,29 +80,31 @@ public boolean upgradeRan() { } private CollectionUpgradeResult upgradeOqmCollection(ClientSession cs, MongoCollection documentCollection, MongoCollection typedCollection, Class objectClass) throws ClassUpgraderNotFoundException { - ObjectUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); + ObjectSchemaUpgrader objectVersionBumper = this.getInstanceForClass(objectClass); CollectionUpgradeResult.Builder outputBuilder = CollectionUpgradeResult.builder() .collectionName(documentCollection.getNamespace().getCollectionName()); StopWatch sw = StopWatch.createStarted(); long numUpdated = 0; - 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() - ); + if(objectVersionBumper.upgradesAvailable()) { + 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); } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); } sw.stop(); diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java similarity index 72% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java index ed4c9ca86..8b7d8ca03 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaUpgrader.java @@ -24,21 +24,21 @@ * @param */ @Slf4j -public abstract class ObjectUpgrader { +public abstract class ObjectSchemaUpgrader { @Getter - private final SortedSet> versionBumpers; + private final SortedSet> versionBumpers; @Getter private final Class objClass; - private final Map>> cacheMap = new ConcurrentHashMap<>(); + private final Map>> cacheMap = new ConcurrentHashMap<>(); - protected ObjectUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { + protected ObjectSchemaUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { this.versionBumpers = versionBumpers; this.objClass = objClass; //check that the set is contiguous int lastVersion = 1; - for(ObjectVersionBumper cur : this.versionBumpers){ + for(ObjectSchemaVersionBumper cur : this.versionBumpers){ if((lastVersion + 1) != cur.getBumperTo()){ throw new VersionBumperListIncontiguousException(lastVersion, this.objClass); } @@ -46,24 +46,24 @@ protected ObjectUpgrader(Class objClass, SortedSet> ve } } - protected ObjectUpgrader(Class objClass, ObjectVersionBumper ... versionBumpers) throws VersionBumperListIncontiguousException { + protected ObjectSchemaUpgrader(Class objClass, ObjectSchemaVersionBumper... versionBumpers) throws VersionBumperListIncontiguousException { this( objClass, new TreeSet<>(Arrays.stream(versionBumpers).toList()) ); } - protected LinkedList> getFromCache(int versionTo){ + protected LinkedList> getFromCache(int versionTo){ if(!this.cacheMap.containsKey(versionTo)){ return null; } return new LinkedList<>(this.cacheMap.get(versionTo)); } - protected Iterator> getIteratorAtVersion(int curObjVersion){ + protected Iterator> getIteratorAtVersion(int curObjVersion){ int curVersionTo = curObjVersion + 1; - LinkedList> bumpers = this.getFromCache(curVersionTo); + LinkedList> bumpers = this.getFromCache(curVersionTo); if(bumpers != null){ return bumpers.iterator(); @@ -90,9 +90,9 @@ public ObjectUpgradeResult upgrade(JsonNode oldObj){ JsonNode upgradedJson = oldObj.deepCopy(); StopWatch sw = StopWatch.createStarted(); - Iterator> it = getIteratorAtVersion(curVersion); + Iterator> it = getIteratorAtVersion(curVersion); while (it.hasNext()){ - ObjectVersionBumper curBumper = it.next(); + ObjectSchemaVersionBumper curBumper = it.next(); upgradedJson = curBumper.bumpObject(upgradedJson); } @@ -120,4 +120,8 @@ public ObjectUpgradeResult upgrade(Document oldObj) throws JsonProcessingExce ) ); } + + 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/ObjectVersionBumper.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java similarity index 75% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectVersionBumper.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java index cfc42109c..bafd957dd 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectVersionBumper.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/ObjectSchemaVersionBumper.java @@ -9,9 +9,9 @@ * * @param */ -public abstract class ObjectVersionBumper implements Comparable> { +public abstract class ObjectSchemaVersionBumper implements Comparable> { - protected ObjectVersionBumper(int bumperTo) { + protected ObjectSchemaVersionBumper(int bumperTo) { this.bumperTo = bumperTo; } @@ -33,10 +33,10 @@ protected ObjectVersionBumper(int bumperTo) { public abstract JsonNode bumpObject(JsonNode oldObj); @Override - public int compareTo(ObjectVersionBumper tObjectVersionBumper) { + public int compareTo(ObjectSchemaVersionBumper tObjectSchemaVersionBumper) { return Integer.compare( this.getBumperTo(), - tObjectVersionBumper.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/StorageBlockUpgrader.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java similarity index 70% rename from software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockUpgrader.java rename to software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java index cdb8b6e41..d6810e26c 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockUpgrader.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/upgraders/storageBlock/StorageBlockSchemaUpgrader.java @@ -2,14 +2,14 @@ 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.ObjectUpgrader; +import tech.ebp.oqm.core.api.service.schemaVersioning.upgraders.ObjectSchemaUpgrader; import java.util.TreeSet; @Slf4j -public class StorageBlockUpgrader extends ObjectUpgrader { +public class StorageBlockSchemaUpgrader extends ObjectSchemaUpgrader { - public StorageBlockUpgrader() { + public StorageBlockSchemaUpgrader() { super( StorageBlock.class, new TreeSet<>() From 64aa8f25cf3469aa996c273eab51e3926632bab5 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 20:07:28 -0400 Subject: [PATCH 15/58] Core - API - Added readiness health check for schema upgrading --- .../api/health/SchemaUpgradeHealthCheck.java | 31 +++++++++++++++++++ .../ObjectSchemaUpgradeService.java | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java 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..d3c3ffd07 --- /dev/null +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java @@ -0,0 +1,31 @@ +package tech.ebp.oqm.core.api.health; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; +import org.eclipse.microprofile.health.Readiness; +import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectSchemaUpgradeService; + +@Readiness +@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/service/schemaVersioning/ObjectSchemaUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java index 3b1b208cf..fd9f81a3e 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java @@ -88,6 +88,7 @@ private CollectionUpgradeResult upgradeOqmCollection(Clie 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(); @@ -117,6 +118,7 @@ private CollectionUpgradeResult upgradeOqmCollection(Clie 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), From 34474b061257f9af2310fa9fd6b6dbc65a834bf2 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 20:27:58 -0400 Subject: [PATCH 16/58] Core - API - Better notion of if the schema has been upgraded --- .../schemaVersioning/ObjectSchemaUpgradeService.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java index fd9f81a3e..9501501c8 100644 --- a/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java +++ b/software/oqm-core-api/src/main/java/tech/ebp/oqm/core/api/service/schemaVersioning/ObjectSchemaUpgradeService.java @@ -44,6 +44,7 @@ public class ObjectSchemaUpgradeService { private Map, 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)) { @@ -75,10 +76,15 @@ InventoryItem.class, new InventoryItemSchemaUpgrader() ); } + public Optional getStartupUpgradeResult() { + return Optional.ofNullable(this.startupUpgradeResult); + } + public boolean upgradeRan() { - return this.upgraderMap == null; + 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() @@ -203,7 +209,8 @@ public Optional updateSchema() { 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 Optional.of(totalResultBuilder.build()); + return this.getStartupUpgradeResult(); } } From 3d977e72ed3bce97f986029c2ecf701abc25f713 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 4 Aug 2024 20:35:10 -0400 Subject: [PATCH 17/58] Core - API - Better naming in schema upgrader --- .../upgraders/ObjectSchemaUpgrader.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 index 8b7d8ca03..1a4bace5a 100644 --- 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 @@ -30,7 +30,7 @@ public abstract class ObjectSchemaUpgrader { private final SortedSet> versionBumpers; @Getter private final Class objClass; - private final Map>> cacheMap = new ConcurrentHashMap<>(); + private final Map>> bumperListCacheMap = new ConcurrentHashMap<>(); protected ObjectSchemaUpgrader(Class objClass, SortedSet> versionBumpers) throws VersionBumperListIncontiguousException { this.versionBumpers = versionBumpers; @@ -53,17 +53,17 @@ protected ObjectSchemaUpgrader(Class objClass, ObjectSchemaVersionBumper.. ); } - protected LinkedList> getFromCache(int versionTo){ - if(!this.cacheMap.containsKey(versionTo)){ + protected LinkedList> getBumperFromCache(int versionTo){ + if(!this.bumperListCacheMap.containsKey(versionTo)){ return null; } - return new LinkedList<>(this.cacheMap.get(versionTo)); + return new LinkedList<>(this.bumperListCacheMap.get(versionTo)); } - protected Iterator> getIteratorAtVersion(int curObjVersion){ + protected Iterator> getBumperIteratorAtVersion(int curObjVersion){ int curVersionTo = curObjVersion + 1; - LinkedList> bumpers = this.getFromCache(curVersionTo); + LinkedList> bumpers = this.getBumperFromCache(curVersionTo); if(bumpers != null){ return bumpers.iterator(); @@ -75,8 +75,8 @@ protected Iterator> getIteratorAtVersion(int curObj bumpers.removeFirst(); } - this.cacheMap.put(curObjVersion, bumpers); - bumpers = this.getFromCache(curObjVersion); + this.bumperListCacheMap.put(curObjVersion, bumpers); + bumpers = this.getBumperFromCache(curObjVersion); return bumpers.iterator(); } @@ -90,7 +90,7 @@ public ObjectUpgradeResult upgrade(JsonNode oldObj){ JsonNode upgradedJson = oldObj.deepCopy(); StopWatch sw = StopWatch.createStarted(); - Iterator> it = getIteratorAtVersion(curVersion); + Iterator> it = getBumperIteratorAtVersion(curVersion); while (it.hasNext()){ ObjectSchemaVersionBumper curBumper = it.next(); From 46b7ed15ca5b0a9c39eb6bc1e68f1747ee243270 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 00:55:16 +0000 Subject: [PATCH 18/58] Bump org.jsoup:jsoup in /software/plugins/external-item-search Bumps [org.jsoup:jsoup](https://github.com/jhy/jsoup) from 1.17.2 to 1.18.1. - [Release notes](https://github.com/jhy/jsoup/releases) - [Changelog](https://github.com/jhy/jsoup/blob/master/CHANGES.md) - [Commits](https://github.com/jhy/jsoup/compare/jsoup-1.17.2...jsoup-1.18.1) --- updated-dependencies: - dependency-name: org.jsoup:jsoup dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/plugins/external-item-search/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/plugins/external-item-search/build.gradle b/software/plugins/external-item-search/build.gradle index e6fc0ad70..412f043c9 100644 --- a/software/plugins/external-item-search/build.gradle +++ b/software/plugins/external-item-search/build.gradle @@ -25,7 +25,7 @@ dependencies { implementation 'io.quarkus:quarkus-container-image-jib' implementation 'io.quarkus:quarkus-arc' - implementation group: 'org.jsoup', name: 'jsoup', version: '1.17.2' + implementation group: 'org.jsoup', name: 'jsoup', version: '1.18.1' implementation 'io.quarkiverse.wiremock:quarkus-wiremock:1.3.3' From f0557d2e26cacc6884c77f78346c9af0fe541f04 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 5 Aug 2024 21:11:06 -0400 Subject: [PATCH 19/58] Core Depot - added initial helm charts --- software/oqm-depot/depot-helm/.helmignore | 23 +++++++++++++++ software/oqm-depot/depot-helm/Chart.yaml | 28 +++++++++++++++++++ software/oqm-depot/depot-helm/README.md | 15 ++++++++++ .../oqm-depot/depot-helm/templates/NOTES.txt | 6 ++++ .../templates/core-depot-deployment.yaml | 27 ++++++++++++++++++ .../templates/core-depot-ingress.yaml | 16 +++++++++++ .../templates/core-depot-service.yaml | 19 +++++++++++++ software/oqm-depot/depot-helm/values.yaml | 8 ++++++ 8 files changed, 142 insertions(+) create mode 100644 software/oqm-depot/depot-helm/.helmignore create mode 100644 software/oqm-depot/depot-helm/Chart.yaml create mode 100644 software/oqm-depot/depot-helm/README.md create mode 100644 software/oqm-depot/depot-helm/templates/NOTES.txt create mode 100644 software/oqm-depot/depot-helm/templates/core-depot-deployment.yaml create mode 100644 software/oqm-depot/depot-helm/templates/core-depot-ingress.yaml create mode 100644 software/oqm-depot/depot-helm/templates/core-depot-service.yaml create mode 100644 software/oqm-depot/depot-helm/values.yaml diff --git a/software/oqm-depot/depot-helm/.helmignore b/software/oqm-depot/depot-helm/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/software/oqm-depot/depot-helm/.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-depot/depot-helm/Chart.yaml b/software/oqm-depot/depot-helm/Chart.yaml new file mode 100644 index 000000000..ed35776bb --- /dev/null +++ b/software/oqm-depot/depot-helm/Chart.yaml @@ -0,0 +1,28 @@ +apiVersion: v2 +name: oqm-core-depot +description: A chart to stand up the Depot core 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.0.0" + +maintainers: + - email: greg@epic-breakfast-productions.tech + name: Greg Stewart diff --git a/software/oqm-depot/depot-helm/README.md b/software/oqm-depot/depot-helm/README.md new file mode 100644 index 000000000..907fc1b98 --- /dev/null +++ b/software/oqm-depot/depot-helm/README.md @@ -0,0 +1,15 @@ +# OQM Depot Helm Charts + +## Requirements: + + + +#### 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-depot/depot-helm/templates/NOTES.txt b/software/oqm-depot/depot-helm/templates/NOTES.txt new file mode 100644 index 000000000..deb70038f --- /dev/null +++ b/software/oqm-depot/depot-helm/templates/NOTES.txt @@ -0,0 +1,6 @@ +Thank you for installing {{ .Chart.Name }}; {{ .Chart.Description }} + +Your release is named "{{ .Release.Name }}". + +For issues with this chart, please let us know on our GitHub: https://github.com/Epic-Breakfast-Productions/OpenQuarterMaster/issues +For questions or discussion, also visit our GitHub: https://github.com/Epic-Breakfast-Productions/OpenQuarterMaster/discussions diff --git a/software/oqm-depot/depot-helm/templates/core-depot-deployment.yaml b/software/oqm-depot/depot-helm/templates/core-depot-deployment.yaml new file mode 100644 index 000000000..b8e8ffb85 --- /dev/null +++ b/software/oqm-depot/depot-helm/templates/core-depot-deployment.yaml @@ -0,0 +1,27 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: {{ .Release.Name }}-oqm-core-depot + name: {{ .Release.Name }}-oqm-core-depot +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Release.Name }}-oqm-core-depot + template: + metadata: + labels: + app: {{ .Release.Name }}-oqm-core-depot + spec: + containers: + - image: "docker.io/ebprod/oqm-core-depot:{{ .Values.service.core.depot.imageTag }}" + name: {{ .Release.Name }}-oqm-core-depot + resources: {} + ports: + - name: http + containerPort: 80 + protocol: TCP + - name: https + containerPort: 443 + protocol: TCP diff --git a/software/oqm-depot/depot-helm/templates/core-depot-ingress.yaml b/software/oqm-depot/depot-helm/templates/core-depot-ingress.yaml new file mode 100644 index 000000000..ba937d43b --- /dev/null +++ b/software/oqm-depot/depot-helm/templates/core-depot-ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ .Release.Name }}-oqm-core-depot-ingress +spec: + rules: + - host: {{ .Release.Name }}-oqm-core-depot # the hostname to respond to (possibly include whole domain name of stack + servcice name) + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ .Release.Name }}-oqm-core-depot + port: + number: 80 \ No newline at end of file diff --git a/software/oqm-depot/depot-helm/templates/core-depot-service.yaml b/software/oqm-depot/depot-helm/templates/core-depot-service.yaml new file mode 100644 index 000000000..dda1da19a --- /dev/null +++ b/software/oqm-depot/depot-helm/templates/core-depot-service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app: {{ .Release.Name }}-oqm-core-depot + name: {{ .Release.Name }}-oqm-core-depot +spec: + ports: + - port: {{ .Values.service.core.depot.service.httpPort }} + protocol: TCP + targetPort: 80 + name: http + - port: {{ .Values.service.core.depot.service.httpsPort }} + protocol: TCP + targetPort: 443 + name: https + selector: + app: {{ .Release.Name }}-oqm-core-depot + type: NodePort diff --git a/software/oqm-depot/depot-helm/values.yaml b/software/oqm-depot/depot-helm/values.yaml new file mode 100644 index 000000000..6b2f0087b --- /dev/null +++ b/software/oqm-depot/depot-helm/values.yaml @@ -0,0 +1,8 @@ +# Service configs +service: + core: + depot: + imageTag: "1.1.4-dev" + service: + httpPort: 80 + httpsPort: 443 \ No newline at end of file From 8e26d0a1487720ee6b569ff4272d74fcd5167ccc Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 5 Aug 2024 21:22:16 -0400 Subject: [PATCH 20/58] Core - API - Added preliminary helm chart --- .../oqm-core-api-chart/.helmignore | 23 ++++ .../oqm-core-api-chart/Chart.yaml | 24 ++++ .../oqm-core-api/oqm-core-api-chart/README.md | 19 ++++ .../templates/infra-mongodb-crd.yaml | 31 +++++ .../oqm-core-api-chart/values.yaml | 107 ++++++++++++++++++ 5 files changed, 204 insertions(+) create mode 100644 software/oqm-core-api/oqm-core-api-chart/.helmignore create mode 100644 software/oqm-core-api/oqm-core-api-chart/Chart.yaml create mode 100644 software/oqm-core-api/oqm-core-api-chart/README.md create mode 100644 software/oqm-core-api/oqm-core-api-chart/templates/infra-mongodb-crd.yaml create mode 100644 software/oqm-core-api/oqm-core-api-chart/values.yaml 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: {} From d27532269342a053fdc9ecb4d5e625907699a632 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 5 Aug 2024 23:06:05 -0400 Subject: [PATCH 21/58] Core - API - changed the readiness health check to startup --- .../ebp/oqm/core/api/health/SchemaUpgradeHealthCheck.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 index d3c3ffd07..c83545a74 100644 --- 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 @@ -2,13 +2,10 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import org.eclipse.microprofile.health.HealthCheck; -import org.eclipse.microprofile.health.HealthCheckResponse; -import org.eclipse.microprofile.health.HealthCheckResponseBuilder; -import org.eclipse.microprofile.health.Readiness; +import org.eclipse.microprofile.health.*; import tech.ebp.oqm.core.api.service.schemaVersioning.ObjectSchemaUpgradeService; -@Readiness +@Startup @ApplicationScoped public class SchemaUpgradeHealthCheck implements HealthCheck { From a1aaffc6b96db5fbe63abbe708fb6912786c00f9 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Thu, 8 Aug 2024 15:49:39 -0400 Subject: [PATCH 22/58] Plugin - Mss Controller - Added TestSerialPortManager --- .../pom.xml | 14 ++ .../rest/NetworkModuleInterface.java | 1 + .../serial/TestSerialPortManager.java | 154 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml index 47f880a52..9949bf3b5 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml @@ -18,6 +18,7 @@ 3.12.3 true 3.2.5 + 1.18.34 @@ -33,6 +34,12 @@ + + org.projectlombok + lombok + ${lombok.version} + provided + tech.ebp.oqm.plugin mss-controller-plugin-lib @@ -95,6 +102,13 @@ maven-compiler-plugin ${compiler-plugin.version} + + + org.projectlombok + lombok + ${lombok.version} + + -parameters diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java index 7252d5fe0..fc6ee48ad 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java @@ -6,4 +6,5 @@ @RequestScoped public class NetworkModuleInterface extends MssModuleInterface { //TODO:: REST endpoint, etc + } diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java new file mode 100644 index 000000000..cf5b25739 --- /dev/null +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java @@ -0,0 +1,154 @@ +package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.serial; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import tech.ebp.oqm.lib.moduleDriver.interaction.serial.SerialPortWrapper; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +public class TestSerialPortManager { + + public static final String NUM_SERIAL_PORTS_ARG = "externalAuth"; + private static final String[] NEW_SERIAL_COMMAND = { + "socat", "-d", "-d", "pty,raw,echo=0", "pty,raw,echo=0" + }; + private static final Pattern DEVICE_FIND_PATTERN = Pattern.compile("\\/\\w+\\/\\w+\\/\\w+$"); + + private int numPorts = 1; + @Getter + private Collection portObjects = new ArrayList<>(); + + + private static String parseOutPort(String logLine) { + Matcher matcher = DEVICE_FIND_PATTERN.matcher(logLine); + if (!matcher.find()) { + throw new IllegalStateException("Unable to find device from log line: \"" + logLine + "\""); + } + return matcher.group(); + } + + public String createNewHardware() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(NEW_SERIAL_COMMAND); + log.info("Starting new Socat process."); + // pb.inheritIO(); //debugging + Process p = pb.start(); + log.info("Started new socat process. pid: {}, normTerm: {}", p.pid(), p.supportsNormalTermination()); + + InputStream inputStream = p.getInputStream(); + InputStream errStream = p.getErrorStream(); + OutputStream outStream = p.getOutputStream(); + + String portOne; + String portTwo; + /* + Output should look like this: + 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/6 + 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/7 + 2022/04/07 11:46:05 socat[23094] N starting data transfer loop with FDs [5,5] and [7,7] + */ + try ( + Scanner scanner = new Scanner(errStream); + ) { + portOne = parseOutPort(scanner.nextLine()); + portTwo = parseOutPort(scanner.nextLine()); + } + log.info("Got ports: {} (for hw impl) and {} (to connect to)", portOne, portTwo); + + ReferenceStorageHardwareImplementation rhwi = new ReferenceStorageHardwareImplementation( + new SerialPortWrapper(portOne) + ); + + this.portObjects.add( + new PortObjects( + portTwo, + p, + rhwi, + inputStream, + outStream, + errStream + ) + ); + + return portTwo; + } + + public List createNewHardware(int numPorts) throws IOException, InterruptedException { + log.info("Creating {} new serial ports.", numPorts); + List output = new ArrayList<>(); + + for (int i = 0; i < numPorts; i++) { + output.add(this.createNewHardware()); + } + + return output; + } + + public void processHw() throws InterruptedException { + for (PortObjects cur : this.portObjects) { + log.info( + "Processed {} lines at {}", + cur.getHw().processPortData(), + cur.getOutSerialPort() + ); + } + } + + @SneakyThrows + public Map start() { + log.info("STARTING test lifecycle resources."); + Map configOverride = new HashMap<>(); + + List testSerialPorts = this.createNewHardware(this.numPorts); + configOverride.put("serial.extraPorts", StringUtils.joinWith(",", testSerialPorts.toArray())); + + log.info("Config overrides: {}", configOverride); + return configOverride; + } + + public void stop() { + for (PortObjects cur : this.portObjects) { + try { + cur.close(); + } catch(IOException e) { + log.error("FAILED to close port: {}", cur); + } + } + } + + public void init(Map initArgs) { + this.numPorts = Integer.parseInt(initArgs.getOrDefault(NUM_SERIAL_PORTS_ARG, Integer.toString(this.numPorts))); + } + + @Data + @AllArgsConstructor + public static class PortObjects implements Closeable { + + private final String outSerialPort; + private final Process process; + private final ReferenceStorageHardwareImplementation hw; + private final InputStream inputStream; + private final OutputStream outputStream; + private final InputStream errStream; + + @SneakyThrows + @Override + public void close() throws IOException { + log.info("Closing out test serial port."); + this.hw.close(); + this.process.destroy(); + this.process.waitFor(); + log.info("Exited socat with code {}", this.process.exitValue()); + } + } +} From db3f58aabb8f40f1d8b38a212291900d864b1e7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:14:06 +0000 Subject: [PATCH 23/58] Bump io.freefair.lombok in /software/plugins/external-item-search Bumps [io.freefair.lombok](https://github.com/freefair/gradle-plugins) from 8.6 to 8.7.1. - [Release notes](https://github.com/freefair/gradle-plugins/releases) - [Commits](https://github.com/freefair/gradle-plugins/compare/8.6...8.7.1) --- updated-dependencies: - dependency-name: io.freefair.lombok dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/plugins/external-item-search/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/plugins/external-item-search/build.gradle b/software/plugins/external-item-search/build.gradle index 412f043c9..4125c273a 100644 --- a/software/plugins/external-item-search/build.gradle +++ b/software/plugins/external-item-search/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" } repositories { From a718e7c63c7a1e479fe7137e2478121c22b5afae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:33:42 +0000 Subject: [PATCH 24/58] Bump org.apache.commons:commons-lang3 in /software/oqm-core-api Bumps org.apache.commons:commons-lang3 from 3.15.0 to 3.16.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-lang3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/oqm-core-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-api/build.gradle b/software/oqm-core-api/build.gradle index 640b72820..b00c95934 100644 --- a/software/oqm-core-api/build.gradle +++ b/software/oqm-core-api/build.gradle @@ -50,7 +50,7 @@ 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-lang3:3.16.0' implementation 'org.apache.commons:commons-compress:1.26.2' implementation 'org.apache.commons:commons-csv:1.11.0' implementation 'org.apache.commons:commons-io:1.3.2' From 0fe858f0242af6f5aa87fdd7b8d02e2446a14dbb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:33:46 +0000 Subject: [PATCH 25/58] Bump io.freefair.lombok from 8.6 to 8.7.1 in /software/oqm-core-api Bumps [io.freefair.lombok](https://github.com/freefair/gradle-plugins) from 8.6 to 8.7.1. - [Release notes](https://github.com/freefair/gradle-plugins/releases) - [Commits](https://github.com/freefair/gradle-plugins/compare/8.6...8.7.1) --- updated-dependencies: - dependency-name: io.freefair.lombok dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/oqm-core-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-api/build.gradle b/software/oqm-core-api/build.gradle index 640b72820..c76455eba 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' From 4c248146818b6214342180d63dcf92de6caab227 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:55:04 +0000 Subject: [PATCH 26/58] Bump uk.org.okapibarcode:okapibarcode in /software/oqm-core-base-station Bumps [uk.org.okapibarcode:okapibarcode](https://github.com/woo-j/OkapiBarcode) from 0.4.6 to 0.4.7. - [Commits](https://github.com/woo-j/OkapiBarcode/compare/v0.4.6...v0.4.7) --- updated-dependencies: - dependency-name: uk.org.okapibarcode:okapibarcode dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- software/oqm-core-base-station/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-base-station/build.gradle b/software/oqm-core-base-station/build.gradle index fe7b7505e..66ff2c928 100644 --- a/software/oqm-core-base-station/build.gradle +++ b/software/oqm-core-base-station/build.gradle @@ -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 From 80320359d64cffefb0876aa257189eca96b87907 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:55:08 +0000 Subject: [PATCH 27/58] Bump io.freefair.lombok in /software/oqm-core-base-station Bumps [io.freefair.lombok](https://github.com/freefair/gradle-plugins) from 8.6 to 8.7.1. - [Release notes](https://github.com/freefair/gradle-plugins/releases) - [Commits](https://github.com/freefair/gradle-plugins/compare/8.6...8.7.1) --- updated-dependencies: - dependency-name: io.freefair.lombok dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/oqm-core-base-station/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-base-station/build.gradle b/software/oqm-core-base-station/build.gradle index fe7b7505e..6559d0317 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' From f7193afe8cdc16671369b68892e42c0038c7ef81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 20:55:11 +0000 Subject: [PATCH 28/58] Bump com.microsoft.playwright:playwright Bumps [com.microsoft.playwright:playwright](https://github.com/microsoft/playwright-java) from 1.45.1 to 1.46.0. - [Release notes](https://github.com/microsoft/playwright-java/releases) - [Commits](https://github.com/microsoft/playwright-java/compare/v1.45.1...v1.46.0) --- updated-dependencies: - dependency-name: com.microsoft.playwright:playwright dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/oqm-core-base-station/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-base-station/build.gradle b/software/oqm-core-base-station/build.gradle index fe7b7505e..f436a38f1 100644 --- a/software/oqm-core-base-station/build.gradle +++ b/software/oqm-core-base-station/build.gradle @@ -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' } From 324a8366656f3f8f5aa8d3336938deacaff5d806 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 12 Aug 2024 22:13:16 -0400 Subject: [PATCH 29/58] Plugin - Mss Controller - First baseline, fairly untested, test module server impl --- .../pom.xml | 9 + .../interfaces/MssModuleInterface.java | 10 -- .../interfaces/TestModuleInterface.java | 14 ++ .../rest/NetworkModuleInterface.java | 3 +- .../serial/SerialModuleInterface.java | 60 ++++++- .../interfaces/serial/TestSerialPort.java | 143 ++++++++++++++++ .../serial/TestSerialPortManager.java | 154 ------------------ .../module/TestModuleImpl.java | 32 ++++ .../src/main/resources/application.properties | 2 + 9 files changed, 258 insertions(+), 169 deletions(-) delete mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/MssModuleInterface.java create mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/TestModuleInterface.java create mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPort.java delete mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java create mode 100644 software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/module/TestModuleImpl.java diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml index 9949bf3b5..cf4f7f1da 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/pom.xml @@ -61,6 +61,15 @@ io.quarkus quarkus-arc + + io.quarkus + quarkus-scheduler + + + com.fazecast + jSerialComm + 2.10.1 + io.quarkus diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/MssModuleInterface.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/MssModuleInterface.java deleted file mode 100644 index e5491860e..000000000 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/MssModuleInterface.java +++ /dev/null @@ -1,10 +0,0 @@ -package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces; - -import tech.ebp.oqm.plugin.mssController.lib.command.MssCommand; - -public abstract class MssModuleInterface { - - protected MssCommand handle(final MssCommand command) { - return null;//todo - } -} diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/TestModuleInterface.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/TestModuleInterface.java new file mode 100644 index 000000000..f35f06780 --- /dev/null +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/TestModuleInterface.java @@ -0,0 +1,14 @@ +package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces; + +import com.fasterxml.jackson.core.JsonProcessingException; +import tech.ebp.oqm.plugin.mssController.lib.command.MssCommand; + +import java.util.Optional; + +/** + * Might not need this after all + */ +public interface TestModuleInterface { + Optional getNextCommand() throws JsonProcessingException; + void sendCommand(Object object); +} diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java index fc6ee48ad..8ab75cd4b 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/rest/NetworkModuleInterface.java @@ -1,10 +1,9 @@ package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.rest; import jakarta.enterprise.context.RequestScoped; -import tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.MssModuleInterface; @RequestScoped -public class NetworkModuleInterface extends MssModuleInterface { +public class NetworkModuleInterface { //TODO:: REST endpoint, etc } diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/SerialModuleInterface.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/SerialModuleInterface.java index 1b48945f3..d819ea464 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/SerialModuleInterface.java +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/SerialModuleInterface.java @@ -1,9 +1,63 @@ package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.serial; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.scheduler.Scheduled; +import io.quarkus.scheduler.ScheduledExecution; +import io.quarkus.scheduler.Scheduler; +import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; -import tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.MssModuleInterface; +import jakarta.inject.Inject; +import lombok.extern.slf4j.Slf4j; +import tech.ebp.oqm.plugin.mssController.lib.command.MssCommand; +import tech.ebp.oqm.plugin.mssController.lib.command.response.CommandResponse; +import tech.ebp.oqm.plugin.mssController.testModuleServer.config.ModuleConfig; +import tech.ebp.oqm.plugin.mssController.testModuleServer.module.TestModuleImpl; +import java.io.IOException; +import java.util.Optional; + +@Slf4j @ApplicationScoped -public class SerialModuleInterface extends MssModuleInterface { - //TODO:: socat, etc +public class SerialModuleInterface { + + @Inject + TestModuleImpl testModuleImpl; + @Inject + ObjectMapper objectMapper; + TestSerialPort testSerialPort; + + @Inject + Scheduler scheduler; + + @PostConstruct + public void init(ModuleConfig config) throws IOException { + if(config.type() == ModuleConfig.TestModuleType.SERIAL){ + return; + } + + this.testSerialPort = new TestSerialPort(this.objectMapper); + + this.scheduler.newJob("Serial Port Processing") + .setInterval("0.25s") + .setTask(this::process) + .setConcurrentExecution(Scheduled.ConcurrentExecution.SKIP); + } + + public void process(ScheduledExecution scheduledExecution) { + log.debug("In process loop."); + + try { + Optional command = this.testSerialPort.getNextCommand(); + if(command.isEmpty()){ + log.debug("No command found."); + return; + } + CommandResponse response = this.testModuleImpl.process(command.get()); + this.testSerialPort.sendCommand(response); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + log.debug("Done with process loop."); + } } diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPort.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPort.java new file mode 100644 index 000000000..2cfe8af3d --- /dev/null +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPort.java @@ -0,0 +1,143 @@ +package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.serial; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fazecast.jSerialComm.SerialPort; +import lombok.*; +import lombok.extern.slf4j.Slf4j; +import tech.ebp.oqm.plugin.mssController.lib.command.MssCommand; +import tech.ebp.oqm.plugin.mssController.lib.command.response.CommandResponse; +import tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.TestModuleInterface; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +public class TestSerialPort implements Closeable, TestModuleInterface { + private static final String[] NEW_SERIAL_COMMAND = { + "socat", "-d", "-d", "pty,raw,echo=0", "pty,raw,echo=0" + }; + private static final Pattern DEVICE_FIND_PATTERN = Pattern.compile("\\/\\w+\\/\\w+\\/\\w+$"); + + private static String parseOutPort(String logLine) { + Matcher matcher = DEVICE_FIND_PATTERN.matcher(logLine); + if (!matcher.find()) { + throw new IllegalStateException("Unable to find device from log line: \"" + logLine + "\""); + } + return matcher.group(); + } + + private final ObjectMapper objectMapper; + private final Process process; + private final InputStream inputStream; + private final OutputStream outputStream; + private final InputStream errStream; + + private final String mssModulePortLocation; + @Getter + private final String mssConnectionPortLocation; + + private SerialPort mssModuleSerialPort; + + public TestSerialPort(ObjectMapper objectMapper) throws IOException { + this.objectMapper = objectMapper; + ProcessBuilder pb = new ProcessBuilder(NEW_SERIAL_COMMAND); + log.info("Starting new Socat process."); + // pb.inheritIO(); //debugging + this.process = pb.start(); + log.info("Started new socat process. pid: {}, normTerm: {}", this.process.pid(), this.process.supportsNormalTermination()); + + this.inputStream = this.process.getInputStream(); + this.errStream = this.process.getErrorStream(); + this.outputStream = this.process.getOutputStream(); + + /* + Output should look like this: + 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/6 + 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/7 + 2022/04/07 11:46:05 socat[23094] N starting data transfer loop with FDs [5,5] and [7,7] + */ + try ( + Scanner scanner = new Scanner(errStream); + ) { + this.mssModulePortLocation = parseOutPort(scanner.nextLine()); + this.mssConnectionPortLocation = parseOutPort(scanner.nextLine()); + } + + this.mssModuleSerialPort = SerialPort.getCommPort(this.mssModulePortLocation); + + log.info("Got ports: {} (for hw impl) and {} (to connect to)", mssModulePortLocation, mssConnectionPortLocation); + } + + private String readLine() { + //TODO:: smarter way to accomplish? Scanner? + StringBuilder sb = new StringBuilder(); + byte[] buffer = new byte[1]; + log.info("Trying to read a line from serial port..."); + + boolean run = true; + do { + int read = this.mssModuleSerialPort.readBytes(buffer, 1); + // log.debug("Read {} bytes ({})", read, (char)buffer[0]); + if (read > 0) { + if (buffer[0] == '\n') { + log.info("Got Newline."); + break; + } + //ignore carriage returns + if (buffer[0] == '\r') { + continue; + } + sb.append((char) buffer[0]); + } else { + run = false; + } + } while (run); + + String output = sb.toString(); + log.info("Got line: \"{}\"", output); + return output; + } + + public synchronized Optional getNextCommand() throws JsonProcessingException { + log.info("Processing port data for test hardware {}", this.mssModuleSerialPort.getSystemPortPath()); + + String curLine = this.readLine(); + + if(curLine.isBlank()){ + return Optional.empty(); + } + MssCommand command = this.objectMapper.readValue(curLine, MssCommand.class); + + return Optional.of(command); + } + + public void sendCommand(Object object) { + byte[] buff = new byte[0]; + try { + if(object instanceof String){ + buff = ((String) object).getBytes(); + }else { + buff = this.objectMapper.writeValueAsBytes(object); + } + } catch(JsonProcessingException e) { + throw new RuntimeException("Somehow failed to write json of command.", e); + } + log.debug("Writing command to serial port: {}", new String(buff)); + this.mssModuleSerialPort.writeBytes(buff, buff.length); + } + + @SneakyThrows + @Override + public void close() throws IOException { + log.info("Closing out test serial port."); + this.process.destroy(); + this.process.waitFor(); + log.info("Exited socat with code {}", this.process.exitValue()); + } +} diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java deleted file mode 100644 index cf5b25739..000000000 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/interfaces/serial/TestSerialPortManager.java +++ /dev/null @@ -1,154 +0,0 @@ -package tech.ebp.oqm.plugin.mssController.testModuleServer.interfaces.serial; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import tech.ebp.oqm.lib.moduleDriver.interaction.serial.SerialPortWrapper; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -@Slf4j -public class TestSerialPortManager { - - public static final String NUM_SERIAL_PORTS_ARG = "externalAuth"; - private static final String[] NEW_SERIAL_COMMAND = { - "socat", "-d", "-d", "pty,raw,echo=0", "pty,raw,echo=0" - }; - private static final Pattern DEVICE_FIND_PATTERN = Pattern.compile("\\/\\w+\\/\\w+\\/\\w+$"); - - private int numPorts = 1; - @Getter - private Collection portObjects = new ArrayList<>(); - - - private static String parseOutPort(String logLine) { - Matcher matcher = DEVICE_FIND_PATTERN.matcher(logLine); - if (!matcher.find()) { - throw new IllegalStateException("Unable to find device from log line: \"" + logLine + "\""); - } - return matcher.group(); - } - - public String createNewHardware() throws IOException, InterruptedException { - ProcessBuilder pb = new ProcessBuilder(NEW_SERIAL_COMMAND); - log.info("Starting new Socat process."); - // pb.inheritIO(); //debugging - Process p = pb.start(); - log.info("Started new socat process. pid: {}, normTerm: {}", p.pid(), p.supportsNormalTermination()); - - InputStream inputStream = p.getInputStream(); - InputStream errStream = p.getErrorStream(); - OutputStream outStream = p.getOutputStream(); - - String portOne; - String portTwo; - /* - Output should look like this: - 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/6 - 2022/04/07 11:46:05 socat[23094] N PTY is /dev/pts/7 - 2022/04/07 11:46:05 socat[23094] N starting data transfer loop with FDs [5,5] and [7,7] - */ - try ( - Scanner scanner = new Scanner(errStream); - ) { - portOne = parseOutPort(scanner.nextLine()); - portTwo = parseOutPort(scanner.nextLine()); - } - log.info("Got ports: {} (for hw impl) and {} (to connect to)", portOne, portTwo); - - ReferenceStorageHardwareImplementation rhwi = new ReferenceStorageHardwareImplementation( - new SerialPortWrapper(portOne) - ); - - this.portObjects.add( - new PortObjects( - portTwo, - p, - rhwi, - inputStream, - outStream, - errStream - ) - ); - - return portTwo; - } - - public List createNewHardware(int numPorts) throws IOException, InterruptedException { - log.info("Creating {} new serial ports.", numPorts); - List output = new ArrayList<>(); - - for (int i = 0; i < numPorts; i++) { - output.add(this.createNewHardware()); - } - - return output; - } - - public void processHw() throws InterruptedException { - for (PortObjects cur : this.portObjects) { - log.info( - "Processed {} lines at {}", - cur.getHw().processPortData(), - cur.getOutSerialPort() - ); - } - } - - @SneakyThrows - public Map start() { - log.info("STARTING test lifecycle resources."); - Map configOverride = new HashMap<>(); - - List testSerialPorts = this.createNewHardware(this.numPorts); - configOverride.put("serial.extraPorts", StringUtils.joinWith(",", testSerialPorts.toArray())); - - log.info("Config overrides: {}", configOverride); - return configOverride; - } - - public void stop() { - for (PortObjects cur : this.portObjects) { - try { - cur.close(); - } catch(IOException e) { - log.error("FAILED to close port: {}", cur); - } - } - } - - public void init(Map initArgs) { - this.numPorts = Integer.parseInt(initArgs.getOrDefault(NUM_SERIAL_PORTS_ARG, Integer.toString(this.numPorts))); - } - - @Data - @AllArgsConstructor - public static class PortObjects implements Closeable { - - private final String outSerialPort; - private final Process process; - private final ReferenceStorageHardwareImplementation hw; - private final InputStream inputStream; - private final OutputStream outputStream; - private final InputStream errStream; - - @SneakyThrows - @Override - public void close() throws IOException { - log.info("Closing out test serial port."); - this.hw.close(); - this.process.destroy(); - this.process.waitFor(); - log.info("Exited socat with code {}", this.process.exitValue()); - } - } -} diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/module/TestModuleImpl.java b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/module/TestModuleImpl.java new file mode 100644 index 000000000..525ee8c34 --- /dev/null +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/java/tech/ebp/oqm/plugin/mssController/testModuleServer/module/TestModuleImpl.java @@ -0,0 +1,32 @@ +package tech.ebp.oqm.plugin.mssController.testModuleServer.module; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import lombok.Getter; +import tech.ebp.oqm.plugin.mssController.lib.command.MssCommand; +import tech.ebp.oqm.plugin.mssController.lib.command.response.CommandResponse; +import tech.ebp.oqm.plugin.mssController.lib.command.response.CommandResponseStatus; +import tech.ebp.oqm.plugin.mssController.lib.command.response.ModuleInfo; +import tech.ebp.oqm.plugin.mssController.testModuleServer.config.ModuleConfig; + +@ApplicationScoped +public class TestModuleImpl { + + @Getter + private ModuleInfo moduleInfo; + + @Inject + public TestModuleImpl(ModuleConfig moduleConfig) { + this.moduleInfo = new ModuleInfo( + moduleConfig.specVersion(), + moduleConfig.serialId(), + moduleConfig.manufactureDate(), + moduleConfig.numBlocks() + ); + } + + public CommandResponse process(MssCommand command) { + //TODO:: this + return new CommandResponse(CommandResponseStatus.OK, ""); + } +} diff --git a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/resources/application.properties b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/resources/application.properties index e69de29bb..70882b76a 100644 --- a/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/resources/application.properties +++ b/software/plugins/mss-controller/mss-controller-plugin-testModuleServer/src/main/resources/application.properties @@ -0,0 +1,2 @@ + +moduleConfig.type=serial \ No newline at end of file From 927fc095e6af514be1fab18d1ca7339053058223 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Mon, 12 Aug 2024 22:58:55 -0400 Subject: [PATCH 30/58] Core - Base Station - Minor tweaks to playwright setup --- .../baseStation/testResources/ui/PlaywrightSetup.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/software/oqm-core-base-station/src/test/java/tech/ebp/oqm/core/baseStation/testResources/ui/PlaywrightSetup.java b/software/oqm-core-base-station/src/test/java/tech/ebp/oqm/core/baseStation/testResources/ui/PlaywrightSetup.java index 4a5d1a89c..644ba5446 100644 --- a/software/oqm-core-base-station/src/test/java/tech/ebp/oqm/core/baseStation/testResources/ui/PlaywrightSetup.java +++ b/software/oqm-core-base-station/src/test/java/tech/ebp/oqm/core/baseStation/testResources/ui/PlaywrightSetup.java @@ -31,9 +31,14 @@ public static synchronized PlaywrightSetup getInstance() { { log.info("Setting up playwright for UI tests."); - playwright = Playwright.create(); + try { + this.playwright = Playwright.create(); + } catch (Throwable e){ + log.error("Failed creating new playwright: ", e); + throw e; + } //TODO:: choose browser based on config - browser = playwright.firefox().launch(); + this.browser = playwright.firefox().launch(); log.info("DONE setting up playwright."); } From 8d3226fc0ff7b98f68b4f8977634ffc2086aca11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 04:00:58 +0000 Subject: [PATCH 31/58] Bump org.apache.commons:commons-compress in /software/oqm-core-api Bumps org.apache.commons:commons-compress from 1.26.2 to 1.27.0. --- updated-dependencies: - dependency-name: org.apache.commons:commons-compress dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- software/oqm-core-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/software/oqm-core-api/build.gradle b/software/oqm-core-api/build.gradle index d79274958..067b852fb 100644 --- a/software/oqm-core-api/build.gradle +++ b/software/oqm-core-api/build.gradle @@ -51,7 +51,7 @@ dependencies { implementation 'tech.uom.lib:uom-lib-jackson:2.1' implementation 'org.apache.commons:commons-lang3:3.16.0' - implementation 'org.apache.commons:commons-compress:1.26.2' + 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' From 7878b306cdb35abaf00c7535978d5a3d8a6b5bfc Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Tue, 13 Aug 2024 23:48:11 -0400 Subject: [PATCH 32/58] Lib - CoreAPI - Quarkus - Added what is maybe probably a working kafka server --- .../core-api-lib-quarkus/deployment/pom.xml | 8 +- .../CoreApiLibDevserviceConfig.java | 7 ++ .../CoreApiLibQuarkusProcessor.java | 76 +++++++++++++------ software/libs/core-api-lib-quarkus/pom.xml | 2 +- .../libs/core-api-lib-quarkus/runtime/pom.xml | 2 +- 5 files changed, 69 insertions(+), 26 deletions(-) diff --git a/software/libs/core-api-lib-quarkus/deployment/pom.xml b/software/libs/core-api-lib-quarkus/deployment/pom.xml index 7c0526de9..ef2eeb55f 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,12 @@ + + org.testcontainers + kafka + 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..1de73b0d4 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 @@ -8,6 +8,7 @@ import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem; import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; +import org.testcontainers.containers.KafkaContainer; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.containers.Network; import org.testcontainers.utility.DockerImageName; @@ -21,23 +22,23 @@ import java.util.Map; class CoreApiLibQuarkusProcessor { - + private static final String FEATURE = "core-api-lib-quarkus"; private static final String MONGODB_DEVSERVICE_HOSTNAME = "mongodbserver"; - + @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 +46,7 @@ HealthBuildItem addHealthCheck(CoreApiLibBuildTimeConfig buildTimeConfig) { buildTimeConfig.healthEnabled ); } - + @BuildStep(onlyIfNot = IsNormal.class, onlyIf = GlobalDevServicesConfig.Enabled.class) public List createContainer( LaunchModeBuildItem launchMode, @@ -53,41 +54,66 @@ 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) {//optionally, kafka + KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) + .withKraft() + .withNetwork(Network.SHARED); + kafka.start(); + + kafkaConnectionInfo.putAll(Map.of( + "quarkus.reactive-messaging.health.enabled", "true", + "mp.messaging.outgoing.events-outgoing.bootstrap.servers", 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 +131,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("mp.messaging.outgoing.events-outgoing.bootstrap.servers")); + } + 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 From 505aeff4dcbd16c715cff14174ba438d9aeef9b6 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Wed, 14 Aug 2024 00:12:41 -0400 Subject: [PATCH 33/58] Plugin - Alert Messenger - Initial dump of Quarkus project --- .../plugins/alert-messenger/.dockerignore | 5 + software/plugins/alert-messenger/.gitignore | 39 ++++ software/plugins/alert-messenger/README.md | 75 +++++++ software/plugins/alert-messenger/build.gradle | 39 ++++ .../plugins/alert-messenger/gradle.properties | 6 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + software/plugins/alert-messenger/gradlew | 185 ++++++++++++++++++ software/plugins/alert-messenger/gradlew.bat | 104 ++++++++++ .../plugins/alert-messenger/settings.gradle | 11 ++ .../src/main/docker/Dockerfile.jvm | 97 +++++++++ .../src/main/docker/Dockerfile.legacy-jar | 93 +++++++++ .../src/main/docker/Dockerfile.native | 27 +++ .../src/main/docker/Dockerfile.native-micro | 30 +++ .../openQuarterMaster/GreetingResource.java | 16 ++ .../MyMessagingApplication.java | 43 ++++ .../src/main/resources/application.properties | 3 + .../openQuarterMaster/GreetingResourceIT.java | 8 + .../GreetingResourceTest.java | 20 ++ .../MyMessagingApplicationTest.java | 25 +++ 20 files changed, 831 insertions(+) create mode 100644 software/plugins/alert-messenger/.dockerignore create mode 100644 software/plugins/alert-messenger/.gitignore create mode 100644 software/plugins/alert-messenger/README.md create mode 100644 software/plugins/alert-messenger/build.gradle create mode 100644 software/plugins/alert-messenger/gradle.properties create mode 100644 software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.jar create mode 100644 software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.properties create mode 100755 software/plugins/alert-messenger/gradlew create mode 100755 software/plugins/alert-messenger/gradlew.bat create mode 100644 software/plugins/alert-messenger/settings.gradle create mode 100644 software/plugins/alert-messenger/src/main/docker/Dockerfile.jvm create mode 100644 software/plugins/alert-messenger/src/main/docker/Dockerfile.legacy-jar create mode 100644 software/plugins/alert-messenger/src/main/docker/Dockerfile.native create mode 100644 software/plugins/alert-messenger/src/main/docker/Dockerfile.native-micro create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/GreetingResource.java create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/MyMessagingApplication.java create mode 100644 software/plugins/alert-messenger/src/main/resources/application.properties create mode 100644 software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java create mode 100644 software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java create mode 100644 software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java diff --git a/software/plugins/alert-messenger/.dockerignore b/software/plugins/alert-messenger/.dockerignore new file mode 100644 index 000000000..4361d2fb3 --- /dev/null +++ b/software/plugins/alert-messenger/.dockerignore @@ -0,0 +1,5 @@ +* +!build/*-runner +!build/*-runner.jar +!build/lib/* +!build/quarkus-app/* \ No newline at end of file diff --git a/software/plugins/alert-messenger/.gitignore b/software/plugins/alert-messenger/.gitignore new file mode 100644 index 000000000..216783d79 --- /dev/null +++ b/software/plugins/alert-messenger/.gitignore @@ -0,0 +1,39 @@ +# Gradle +.gradle/ +build/ + +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode +.factorypath + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Local environment +.env + +# Plugin directory +/.quarkus/cli/plugins/ diff --git a/software/plugins/alert-messenger/README.md b/software/plugins/alert-messenger/README.md new file mode 100644 index 000000000..d7be1aa53 --- /dev/null +++ b/software/plugins/alert-messenger/README.md @@ -0,0 +1,75 @@ +# oqm-plugin-alert_messenger + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: . + +## Running the application in dev mode + +You can run your application in dev mode that enables live coding using: + +```shell script +./gradlew quarkusDev +``` + +> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at . + +## Packaging and running the application + +The application can be packaged using: + +```shell script +./gradlew build +``` + +It produces the `quarkus-run.jar` file in the `build/quarkus-app/` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `build/quarkus-app/lib/` directory. + +The application is now runnable using `java -jar build/quarkus-app/quarkus-run.jar`. + +If you want to build an _über-jar_, execute the following command: + +```shell script +./gradlew build -Dquarkus.package.jar.type=uber-jar +``` + +The application, packaged as an _über-jar_, is now runnable using `java -jar build/*-runner.jar`. + +## Creating a native executable + +You can create a native executable using: + +```shell script +./gradlew build -Dquarkus.native.enabled=true +``` + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: + +```shell script +./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true +``` + +You can then execute your native executable with: `./build/oqm-plugin-alert_messenger-1.0.0-SNAPSHOT-runner` + +If you want to learn more about building native executables, please consult . + +## Related Guides + +- REST ([guide](https://quarkus.io/guides/rest)): A Jakarta REST implementation utilizing build time processing and Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it. +- REST Jackson ([guide](https://quarkus.io/guides/rest#json-serialisation)): Jackson serialization support for Quarkus REST. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it +- Messaging - Kafka Connector ([guide](https://quarkus.io/guides/kafka-getting-started)): Connect to Kafka with Reactive Messaging + +## Provided Code + +### Messaging codestart + +Use Quarkus Messaging + +[Related Apache Kafka guide section...](https://quarkus.io/guides/kafka-reactive-getting-started) + + +### REST + +Easily start your REST Web Services + +[Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources) diff --git a/software/plugins/alert-messenger/build.gradle b/software/plugins/alert-messenger/build.gradle new file mode 100644 index 000000000..3ed748920 --- /dev/null +++ b/software/plugins/alert-messenger/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' + id 'io.quarkus' +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") + implementation 'io.quarkus:quarkus-rest' + implementation 'io.quarkus:quarkus-rest-jackson' + implementation 'io.quarkus:quarkus-messaging-kafka' + implementation 'io.quarkus:quarkus-arc' + testImplementation 'io.quarkus:quarkus-junit5' + testImplementation 'io.rest-assured:rest-assured' +} + +group 'tech.ebp.openQuarterMaster' +version '1.0.0-SNAPSHOT' + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +test { + systemProperty "java.util.logging.manager", "org.jboss.logmanager.LogManager" +} +compileJava { + options.encoding = 'UTF-8' + options.compilerArgs << '-parameters' +} + +compileTestJava { + options.encoding = 'UTF-8' +} diff --git a/software/plugins/alert-messenger/gradle.properties b/software/plugins/alert-messenger/gradle.properties new file mode 100644 index 000000000..014c72716 --- /dev/null +++ b/software/plugins/alert-messenger/gradle.properties @@ -0,0 +1,6 @@ +#Gradle properties +quarkusPluginId=io.quarkus +quarkusPluginVersion=3.13.2 +quarkusPlatformGroupId=io.quarkus.platform +quarkusPlatformArtifactId=quarkus-bom +quarkusPlatformVersion=3.13.2 \ No newline at end of file diff --git a/software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.jar b/software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.properties b/software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..0d1842103 --- /dev/null +++ b/software/plugins/alert-messenger/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/software/plugins/alert-messenger/gradlew b/software/plugins/alert-messenger/gradlew new file mode 100755 index 000000000..fbd7c5158 --- /dev/null +++ b/software/plugins/alert-messenger/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/software/plugins/alert-messenger/gradlew.bat b/software/plugins/alert-messenger/gradlew.bat new file mode 100755 index 000000000..5093609d5 --- /dev/null +++ b/software/plugins/alert-messenger/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/software/plugins/alert-messenger/settings.gradle b/software/plugins/alert-messenger/settings.gradle new file mode 100644 index 000000000..505e11164 --- /dev/null +++ b/software/plugins/alert-messenger/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + mavenLocal() + } + plugins { + id "${quarkusPluginId}" version "${quarkusPluginVersion}" + } +} +rootProject.name='oqm-plugin-alert_messenger' diff --git a/software/plugins/alert-messenger/src/main/docker/Dockerfile.jvm b/software/plugins/alert-messenger/src/main/docker/Dockerfile.jvm new file mode 100644 index 000000000..1efeb53da --- /dev/null +++ b/software/plugins/alert-messenger/src/main/docker/Dockerfile.jvm @@ -0,0 +1,97 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/oqm-plugin-alert_messenger-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger-jvm +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger-jvm +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 build/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 build/quarkus-app/*.jar /deployments/ +COPY --chown=185 build/quarkus-app/app/ /deployments/app/ +COPY --chown=185 build/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] + diff --git a/software/plugins/alert-messenger/src/main/docker/Dockerfile.legacy-jar b/software/plugins/alert-messenger/src/main/docker/Dockerfile.legacy-jar new file mode 100644 index 000000000..6628ae05b --- /dev/null +++ b/software/plugins/alert-messenger/src/main/docker/Dockerfile.legacy-jar @@ -0,0 +1,93 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.package.jar.type=legacy-jar +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/oqm-plugin-alert_messenger-legacy-jar . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger-legacy-jar +# +# If you want to include the debug port into your docker image +# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005. +# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005 +# when running the container +# +# Then run the container using : +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger-legacy-jar +# +# This image uses the `run-java.sh` script to run the application. +# This scripts computes the command line to execute your Java application, and +# includes memory/GC tuning. +# You can configure the behavior using the following environment properties: +# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") +# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options +# in JAVA_OPTS (example: "-Dsome.property=foo") +# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is +# used to calculate a default maximal heap memory based on a containers restriction. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio +# of the container available memory as set here. The default is `50` which means 50% +# of the available memory is used as an upper boundary. You can skip this mechanism by +# setting this value to `0` in which case no `-Xmx` option is added. +# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This +# is used to calculate a default initial heap memory based on the maximum heap memory. +# If used in a container without any memory constraints for the container then this +# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio +# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` +# is used as the initial heap size. You can skip this mechanism by setting this value +# to `0` in which case no `-Xms` option is added (example: "25") +# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. +# This is used to calculate the maximum value of the initial heap memory. If used in +# a container without any memory constraints for the container then this option has +# no effect. If there is a memory constraint then `-Xms` is limited to the value set +# here. The default is 4096MB which means the calculated value of `-Xms` never will +# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") +# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output +# when things are happening. This option, if set to true, will set +# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). +# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: +# true"). +# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). +# - CONTAINER_CORE_LIMIT: A calculated core limit as described in +# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") +# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). +# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. +# (example: "20") +# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. +# (example: "40") +# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. +# (example: "4") +# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus +# previous GC times. (example: "90") +# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") +# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") +# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should +# contain the necessary JRE command-line options to specify the required GC, which +# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). +# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") +# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") +# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be +# accessed directly. (example: "foo.example.com,bar.example.com") +# +### +FROM registry.access.redhat.com/ubi8/openjdk-21:1.19 + +ENV LANGUAGE='en_US:en' + + +COPY build/lib/* /deployments/lib/ +COPY build/*-runner.jar /deployments/quarkus-run.jar + +EXPOSE 8080 +USER 185 +ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" + +ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ] diff --git a/software/plugins/alert-messenger/src/main/docker/Dockerfile.native b/software/plugins/alert-messenger/src/main/docker/Dockerfile.native new file mode 100644 index 000000000..6fb684bf2 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/docker/Dockerfile.native @@ -0,0 +1,27 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/oqm-plugin-alert_messenger . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal:8.9 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/software/plugins/alert-messenger/src/main/docker/Dockerfile.native-micro b/software/plugins/alert-messenger/src/main/docker/Dockerfile.native-micro new file mode 100644 index 000000000..5521d18f2 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/docker/Dockerfile.native-micro @@ -0,0 +1,30 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. +# It uses a micro base image, tuned for Quarkus native executables. +# It reduces the size of the resulting container image. +# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. +# +# Before building the container image run: +# +# ./gradlew build -Dquarkus.native.enabled=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/oqm-plugin-alert_messenger . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/oqm-plugin-alert_messenger +# +### +FROM quay.io/quarkus/quarkus-micro-image:2.0 +WORKDIR /work/ +RUN chown 1001 /work \ + && chmod "g+rwX" /work \ + && chown 1001:root /work +COPY --chown=1001:root build/*-runner /work/application + +EXPOSE 8080 +USER 1001 + +ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/GreetingResource.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/GreetingResource.java new file mode 100644 index 000000000..03f81592a --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/GreetingResource.java @@ -0,0 +1,16 @@ +package tech.ebp.openQuarterMaster; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "Hello from Quarkus REST"; + } +} diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/MyMessagingApplication.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/MyMessagingApplication.java new file mode 100644 index 000000000..2528bf833 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/MyMessagingApplication.java @@ -0,0 +1,43 @@ +package tech.ebp.openQuarterMaster; + +import io.quarkus.runtime.StartupEvent; +import org.eclipse.microprofile.reactive.messaging.*; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import java.util.stream.Stream; + +@ApplicationScoped +public class MyMessagingApplication { + + @Inject + @Channel("words-out") + Emitter emitter; + + /** + * Sends message to the "words-out" channel, can be used from a JAX-RS resource or any bean of your application. + * Messages are sent to the broker. + **/ + void onStart(@Observes StartupEvent ev) { + Stream.of("Hello", "with", "Quarkus", "Messaging", "message").forEach(string -> emitter.send(string)); + } + + /** + * Consume the message from the "words-in" channel, uppercase it and send it to the uppercase channel. + * Messages come from the broker. + **/ + @Incoming("words-in") + @Outgoing("uppercase") + public Message toUpperCase(Message message) { + return message.withPayload(message.getPayload().toUpperCase()); + } + + /** + * Consume the uppercase channel (in-memory) and print the messages. + **/ + @Incoming("uppercase") + public void sink(String word) { + System.out.println(">> " + word); + } +} diff --git a/software/plugins/alert-messenger/src/main/resources/application.properties b/software/plugins/alert-messenger/src/main/resources/application.properties new file mode 100644 index 000000000..d8f24b759 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/resources/application.properties @@ -0,0 +1,3 @@ +mp.messaging.incoming.words-in.topic=words +mp.messaging.outgoing.words-out.topic=words +mp.messaging.incoming.words-in.auto.offset.reset=earliest diff --git a/software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java b/software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java new file mode 100644 index 000000000..0c8e465be --- /dev/null +++ b/software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java @@ -0,0 +1,8 @@ +package tech.ebp.openQuarterMaster; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +class GreetingResourceIT extends GreetingResourceTest { + // Execute the same tests but in packaged mode. +} diff --git a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java new file mode 100644 index 000000000..9966eea61 --- /dev/null +++ b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java @@ -0,0 +1,20 @@ +package tech.ebp.openQuarterMaster; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +class GreetingResourceTest { + @Test + void testHelloEndpoint() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello from Quarkus REST")); + } + +} \ No newline at end of file diff --git a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java new file mode 100644 index 000000000..554aca2ef --- /dev/null +++ b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java @@ -0,0 +1,25 @@ +package tech.ebp.openQuarterMaster; + +import io.quarkus.test.common.WithTestResource; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.quarkus.test.junit.QuarkusTest; + +import org.eclipse.microprofile.reactive.messaging.Message; +import org.junit.jupiter.api.Test; + +import jakarta.inject.Inject; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@QuarkusTest +class MyMessagingApplicationTest { + + @Inject + MyMessagingApplication application; + + @Test + void test() { + assertEquals("HELLO", application.toUpperCase(Message.of("Hello")).getPayload()); + assertEquals("BONJOUR", application.toUpperCase(Message.of("bonjour")).getPayload()); + } +} From 094ad028dc473542c4e29f2b2b874ba0f5983c02 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Wed, 14 Aug 2024 00:35:03 -0400 Subject: [PATCH 34/58] Plugin - Alert Messenger - Initial adding of oqm core api library --- software/plugins/alert-messenger/build.gradle | 3 ++- .../src/main/resources/application.properties | 3 --- .../src/main/resources/application.yaml | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) delete mode 100644 software/plugins/alert-messenger/src/main/resources/application.properties create mode 100644 software/plugins/alert-messenger/src/main/resources/application.yaml diff --git a/software/plugins/alert-messenger/build.gradle b/software/plugins/alert-messenger/build.gradle index 3ed748920..f26034338 100644 --- a/software/plugins/alert-messenger/build.gradle +++ b/software/plugins/alert-messenger/build.gradle @@ -13,7 +13,8 @@ dependencies { implementation 'io.quarkus:quarkus-rest' implementation 'io.quarkus:quarkus-rest-jackson' implementation 'io.quarkus:quarkus-messaging-kafka' - implementation 'io.quarkus:quarkus-arc' + implementation 'io.quarkus:quarkus-config-yaml' + implementation 'tech.ebp.oqm.lib:core-api-lib-quarkus:2.2.0-SNAPSHOT' testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' } diff --git a/software/plugins/alert-messenger/src/main/resources/application.properties b/software/plugins/alert-messenger/src/main/resources/application.properties deleted file mode 100644 index d8f24b759..000000000 --- a/software/plugins/alert-messenger/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -mp.messaging.incoming.words-in.topic=words -mp.messaging.outgoing.words-out.topic=words -mp.messaging.incoming.words-in.auto.offset.reset=earliest diff --git a/software/plugins/alert-messenger/src/main/resources/application.yaml b/software/plugins/alert-messenger/src/main/resources/application.yaml new file mode 100644 index 000000000..4127982d4 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/resources/application.yaml @@ -0,0 +1,17 @@ + +quarkus: + oqmCoreApi: + devservice: + enableKafka: true + +mp: + messaging: + incoming: + words-in: + topic: words + auto: + offset: + reset: earliest + outgoing: + words-out: + topic: words From e4d8b197d1cb4231b2a7598f312cae7bca189a12 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Wed, 14 Aug 2024 20:43:04 -0400 Subject: [PATCH 35/58] Core - API - Added an prepend for message topics --- .../HistoryEventNotificationService.java | 14 ++++++++++++-- .../mongo/MongoHistoriedObjectServiceTest.java | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) 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..4ecf220e4 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 @@ -25,6 +25,7 @@ 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 TOPIC_PREPEND = "oqm-core-"; public static final String ALL_EVENT_TOPIC = "all-events"; @ConfigProperty(name = "mp.messaging.outgoing.events-outgoing.bootstrap.servers") @@ -61,13 +62,21 @@ void sendEventOutgoing(EventNotificationWrapper notificationWrapper) { } log.info("Sending event to external channels: {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); try { + this.outgoingEventEmitter.send( + Message.of( + notificationWrapper.getEvent() + ).addMetadata( + OutgoingKafkaRecordMetadata.builder() + .withTopic(TOPIC_PREPEND + ALL_EVENT_TOPIC) + .build() + )); this.outgoingEventEmitter.send( Message.of( notificationWrapper.getEvent() ).addMetadata( OutgoingKafkaRecordMetadata.builder() .withTopic( - (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + notificationWrapper.getObjectName() + "-" + notificationWrapper.getEvent().getType() + TOPIC_PREPEND + (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + notificationWrapper.getObjectName() + "-" + notificationWrapper.getEvent().getType() ) .build() )); @@ -76,7 +85,8 @@ void sendEventOutgoing(EventNotificationWrapper notificationWrapper) { notificationWrapper.getEvent() ).addMetadata( OutgoingKafkaRecordMetadata.builder() - .withTopic((notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC) + .withTopic( + TOPIC_PREPEND + (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC) .build() )); log.debug("Sent event to external channels: {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); 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..481046d47 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 @@ -75,7 +75,7 @@ public void testAdd() throws JsonProcessingException { 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.TOPIC_PREPEND + this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, 1 ); createFromAll.awaitCompletion(); @@ -84,7 +84,7 @@ public void testAdd() throws JsonProcessingException { assertEquals(createEvent, createEventFromMessage); ConsumerTask createFromCreate = this.kafkaCompanion.consumeStrings().fromTopics( - this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, + HistoryEventNotificationService.TOPIC_PREPEND + this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, 1 ); createFromCreate.awaitCompletion(); From 3abf183804d278dc2311d1d6e522d252dbf6cbe3 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Wed, 14 Aug 2024 22:16:09 -0400 Subject: [PATCH 36/58] Plugin - Alert Messenger - Setup of test resources and initial integrations --- .../src/main/resources/application.yml | 1 - software/plugins/alert-messenger/build.gradle | 5 + .../plugins/alert-messenger/dev/README.md | 11 + .../alert-messenger/dev/devTest-cert-cert.pem | 22 + .../alert-messenger/dev/devTest-cert-key.pem | 28 + .../alert-messenger/dev/developers.jpeg | Bin 0 -> 4965 bytes .../alert-messenger/dev/oqm-realm.json | 2390 +++++++++++++++++ .../openQuarterMaster/GreetingResource.java | 16 - .../MyMessagingApplication.java | 43 - .../plugin/alertMessenger/TestConsumer.java | 17 + .../src/main/resources/application.yaml | 46 +- .../GreetingResourceIT.java | 2 +- .../GreetingResourceTest.java | 20 - .../MyMessagingApplicationTest.java | 25 - .../src/test/resources/application.yaml | 23 + 15 files changed, 2535 insertions(+), 114 deletions(-) create mode 100644 software/plugins/alert-messenger/dev/README.md create mode 100644 software/plugins/alert-messenger/dev/devTest-cert-cert.pem create mode 100644 software/plugins/alert-messenger/dev/devTest-cert-key.pem create mode 100644 software/plugins/alert-messenger/dev/developers.jpeg create mode 100644 software/plugins/alert-messenger/dev/oqm-realm.json delete mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/GreetingResource.java delete mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/openQuarterMaster/MyMessagingApplication.java create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java rename software/plugins/alert-messenger/src/native-test/java/tech/ebp/{openQuarterMaster => oqm}/GreetingResourceIT.java (84%) delete mode 100644 software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java delete mode 100644 software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java create mode 100644 software/plugins/alert-messenger/src/test/resources/application.yaml 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 25f426803..69d09ca59 100644 --- a/software/oqm-core-base-station/src/main/resources/application.yml +++ b/software/oqm-core-base-station/src/main/resources/application.yml @@ -123,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/plugins/alert-messenger/build.gradle b/software/plugins/alert-messenger/build.gradle index f26034338..8c7be2991 100644 --- a/software/plugins/alert-messenger/build.gradle +++ b/software/plugins/alert-messenger/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'io.quarkus' + id "io.freefair.lombok" version "8.7.1" } repositories { @@ -13,8 +14,12 @@ dependencies { implementation 'io.quarkus:quarkus-rest' implementation 'io.quarkus:quarkus-rest-jackson' implementation 'io.quarkus:quarkus-messaging-kafka' + implementation 'io.quarkus:quarkus-smallrye-health' + implementation 'io.quarkus:quarkus-smallrye-openapi' implementation 'io.quarkus:quarkus-config-yaml' implementation 'tech.ebp.oqm.lib:core-api-lib-quarkus:2.2.0-SNAPSHOT' + implementation 'org.jboss.slf4j:slf4j-jboss-logmanager' + testImplementation 'io.quarkus:quarkus-junit5' testImplementation 'io.rest-assured:rest-assured' } diff --git a/software/plugins/alert-messenger/dev/README.md b/software/plugins/alert-messenger/dev/README.md new file mode 100644 index 000000000..56f4cf44c --- /dev/null +++ b/software/plugins/alert-messenger/dev/README.md @@ -0,0 +1,11 @@ +# Dev Files + +These files are just used for development purposes. + +To regenerate keys: + +``` +openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout devTest-cert-key.pem -out devTest-cert-cert.pem -subj "/C=US/ST=TestState/L=TestTown/O=OQM/CN=localhost" +``` + +Remember to update the realm cert when you upgrade the cert diff --git a/software/plugins/alert-messenger/dev/devTest-cert-cert.pem b/software/plugins/alert-messenger/dev/devTest-cert-cert.pem new file mode 100644 index 000000000..e49c7a909 --- /dev/null +++ b/software/plugins/alert-messenger/dev/devTest-cert-cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDjTCCAnWgAwIBAgIUVGR2mw8+0KhIDl05mmSm1gZPFfQwDQYJKoZIhvcNAQEL +BQAwVjELMAkGA1UEBhMCVVMxEjAQBgNVBAgMCVRlc3RTdGF0ZTERMA8GA1UEBwwI +VGVzdFRvd24xDDAKBgNVBAoMA09RTTESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0 +MDIwNzA4MDUzOFoXDTM0MDIwNDA4MDUzOFowVjELMAkGA1UEBhMCVVMxEjAQBgNV +BAgMCVRlc3RTdGF0ZTERMA8GA1UEBwwIVGVzdFRvd24xDDAKBgNVBAoMA09RTTES +MBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAxGy/mH9wx26jPJKnetLEmSGcGkOmfzlWERAw8m+7erJXF1BgE8htTrrh6RJK +USedhCpY3jrkCNbS/mvO+AJyxUOfLEBv3KAAXuZYvtFNo9CoY1vtaVemFmONje4j +OIoURccuE+RFIhiH0EvLKsQF+a94K2DcuObDZHKcU2gVJ+J1/hcKSK5W3ilJNqJR +Z2GL482ImlBxMluWwnJQxX3dA9C/7nd2r+1s7j5bbStvI7HMn41aj/aps/2l92AW +qErq9lZojb9CZPigMH5KAd1nXr0qJ9srPwK3MilCBK/tqxiKhc27NSa9amxmzQrc +nTPSsQ0/1KL4c45/l6v1jwAFrQIDAQABo1MwUTAdBgNVHQ4EFgQUZQgkSnGsP6Mc +kynYGaOWJYkwQbswHwYDVR0jBBgwFoAUZQgkSnGsP6MckynYGaOWJYkwQbswDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEArPlxP5eHtpTSCk/ANJiu +5DuuHGp4w8Wr/Vcbd2N5owAx5R7sD0JeMYToNOA4ecLZCdc9/pnrCNjBN5QAWOWm +OC1cUMusODJZTwLarH3aDiOMrLnc3uzNoMYsV51zEfdrTk4o0qc71mkUXzMt8TY1 +0m2mVgWZFRlsykhMpjmTN4Ph/Gz2Y+MSSHbBQ4PRSkcZxZQYvroju1BwT1yW2eLX +1OBX2WLMaYSm7KPNCad3zc9XRHJFuANWvQdmsoRc2trirVh8X2IcMnMHb/WARLRA +eYApCXlXPGh5nwbKe6l8UN02/+M766GZH/w6wLnjn6z/xDZVVGubkZgI6y2v516+ +9Q== +-----END CERTIFICATE----- diff --git a/software/plugins/alert-messenger/dev/devTest-cert-key.pem b/software/plugins/alert-messenger/dev/devTest-cert-key.pem new file mode 100644 index 000000000..b4c62e7ef --- /dev/null +++ b/software/plugins/alert-messenger/dev/devTest-cert-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDEbL+Yf3DHbqM8 +kqd60sSZIZwaQ6Z/OVYREDDyb7t6slcXUGATyG1OuuHpEkpRJ52EKljeOuQI1tL+ +a874AnLFQ58sQG/coABe5li+0U2j0KhjW+1pV6YWY42N7iM4ihRFxy4T5EUiGIfQ +S8sqxAX5r3grYNy45sNkcpxTaBUn4nX+FwpIrlbeKUk2olFnYYvjzYiaUHEyW5bC +clDFfd0D0L/ud3av7WzuPlttK28jscyfjVqP9qmz/aX3YBaoSur2VmiNv0Jk+KAw +fkoB3WdevSon2ys/ArcyKUIEr+2rGIqFzbs1Jr1qbGbNCtydM9KxDT/Uovhzjn+X +q/WPAAWtAgMBAAECggEABPVGitBR77nee4CrZ/ezKqNxbqLnDuI3MvwQODtTPjPQ +TpVPJbHrp4Ai+mLvTiWngBlIKdQxQtNlXajHEycFl3hvTvI4+7RFJYKOcv3/I4Tu +taa7f3TglS+OM4OG+4fzgSB6Pcau4SG3dhBGynitE1IyXji2M3B553R1AQa80uTT +up17Aa9szQlF6YV8ZHJz02ydFJu+r+f305EYu1S0Z/U1R3mIjCkNXQCkw7RZ0qxO +r9aLYXhk8rWEGsOwgRtwnnwjepXeXWI1Ca41WCc1/LLodTmXawCsyeYY2VoYEneR +Ao0V0KlgQx7B/EsUiqk8/Gvcp8ZPb4x12FRP8B4DcwKBgQD/yz2YS+Ss0mlX3B5N +BFlRC6RvawVCFjx+m+7XboHnuf+wikBGp8RT+OuXuzFj+Wc8hoNeHdohDzJENs7q +NZrFYnh1+WDv/QGf9KdmrntQspCiLHTINR0B/LYFec9k907eVOmUKerho3f/M0Ab +hvpYaQBK+WaF253aop53r6ag8wKBgQDElUMy5Q/YlBL09mbLXxYRToxuFDiuZibc +gyZkgdkjYQeMpWaXcnJ8zlMv95XBM85fe8OhOUBdhgpOG+bxB/7tffEVMn5+eJz+ +274Wl/QCUaMQao0crwtEbOwbOzT5cODONkrzpvGBpasQ6hRt7iX2U1LHbX4+aqZV +KXoiGqNm3wKBgGky5AUjiUuOSabJr2iLVlRfjmQIRqRUUtLbPJI7L4/mzgKECUVF +sBe88t93LCvqoYuh4pstec1I26p0RArMuvdctSAHzNdGXYm0a7huH+cjWRppYCoK +tgBgN1fvLd1fXH9RurKlvqTHvw0kvcPUclcz79vl0EVS+gu1/6hHhCcDAoGBAJj0 +SkTfVWT+UYGn9nNmmJT+uOUtdqy7bqFEqiqpnXmZlXYpe5l6wvm4z6ES0sJwvLIu +ahiXoy0hjgMYUqhXwFKpG1uS3jkpP35NG6oYsRLc0jODtCgNSocC+PJ3LtCms0O/ +mrHZwy9M571RZHPkSEVQr6fb2c5WzPSWQSEn+NuhAoGACgLLLWNhx6etLEVVQBdW +HPe4yFlOdEkPNmQdU0JlRl5DdFQWxsWRUJWopS/0K/bmUDNZ5+95leVJ/kpCrRhL +MuohkBpw9jCMXcdw1jHWVQGAXhCnmDES2Obv2Rvgmy9XUno/xOtabjw8n0lvt0Le +sncB7P++ck6iDWRd/v9h4LA= +-----END PRIVATE KEY----- diff --git a/software/plugins/alert-messenger/dev/developers.jpeg b/software/plugins/alert-messenger/dev/developers.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..24daf42e56146f025a9d052f4f91492ebfc027a8 GIT binary patch literal 4965 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 emitter; - - /** - * Sends message to the "words-out" channel, can be used from a JAX-RS resource or any bean of your application. - * Messages are sent to the broker. - **/ - void onStart(@Observes StartupEvent ev) { - Stream.of("Hello", "with", "Quarkus", "Messaging", "message").forEach(string -> emitter.send(string)); - } - - /** - * Consume the message from the "words-in" channel, uppercase it and send it to the uppercase channel. - * Messages come from the broker. - **/ - @Incoming("words-in") - @Outgoing("uppercase") - public Message toUpperCase(Message message) { - return message.withPayload(message.getPayload().toUpperCase()); - } - - /** - * Consume the uppercase channel (in-memory) and print the messages. - **/ - @Incoming("uppercase") - public void sink(String word) { - System.out.println(">> " + word); - } -} diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java new file mode 100644 index 000000000..ef8e40076 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java @@ -0,0 +1,17 @@ +package tech.ebp.oqm.plugin.alertMessenger; + + +import jakarta.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.eclipse.microprofile.reactive.messaging.Message; + +@Slf4j +@ApplicationScoped +public class TestConsumer { + + @Incoming("events-incoming") + public void toUpperCase(Message message) { + log.info("Received message {}", message); + } +} diff --git a/software/plugins/alert-messenger/src/main/resources/application.yaml b/software/plugins/alert-messenger/src/main/resources/application.yaml index 4127982d4..7ef38c370 100644 --- a/software/plugins/alert-messenger/src/main/resources/application.yaml +++ b/software/plugins/alert-messenger/src/main/resources/application.yaml @@ -3,15 +3,45 @@ quarkus: oqmCoreApi: devservice: enableKafka: true + coreApiVersion: 2.1.3-DEV + certPath: ../../../../dev/devTest-cert-cert.pem + certKeyPath: ../../../../dev/devTest-cert-key.pem + keycloak: + devservices: + # Required to export realm + start-command: "start-dev --http-enabled=true --hostname-strict=false --hostname-strict-https=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" + realm-path: "../../../../dev/oqm-realm.json" + realm-name: "oqm" + resource-aliases: + systemCert: ../../../../dev/devTest-cert-cert.pem + systemCertKey: ../../../../dev/devTest-cert-key.pem + resource-mappings: + systemCert: /opt/keycloak/certs/devTest-cert-cert.pem + systemCertKey: /opt/keycloak/certs/devTest-cert-key.pem mp: messaging: incoming: - words-in: - topic: words - auto: - offset: - reset: earliest - outgoing: - words-out: - topic: words + events-incoming: + topic: oqm-core-all-events + connector: smallrye-kafka + +"%dev": + quarkus: + http: + port: 8080 + ssl-port: 8443 + ssl: + certificate: + files: ../../../../dev/devTest-cert-cert.pem + key-files: ../../../../dev/devTest-cert-key.pem + oidc: + client-id: oqm-app + credentials: + secret: "**********" + mp: + messaging: + incoming: + events-incoming: + bootstrap: + servers: ${devservice.kafka.bootstrapServers} \ No newline at end of file diff --git a/software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java b/software/plugins/alert-messenger/src/native-test/java/tech/ebp/oqm/GreetingResourceIT.java similarity index 84% rename from software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java rename to software/plugins/alert-messenger/src/native-test/java/tech/ebp/oqm/GreetingResourceIT.java index 0c8e465be..e5b57fb08 100644 --- a/software/plugins/alert-messenger/src/native-test/java/tech/ebp/openQuarterMaster/GreetingResourceIT.java +++ b/software/plugins/alert-messenger/src/native-test/java/tech/ebp/oqm/GreetingResourceIT.java @@ -1,4 +1,4 @@ -package tech.ebp.openQuarterMaster; +package tech.ebp.oqm; import io.quarkus.test.junit.QuarkusIntegrationTest; diff --git a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java deleted file mode 100644 index 9966eea61..000000000 --- a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/GreetingResourceTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package tech.ebp.openQuarterMaster; - -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -@QuarkusTest -class GreetingResourceTest { - @Test - void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("Hello from Quarkus REST")); - } - -} \ No newline at end of file diff --git a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java b/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java deleted file mode 100644 index 554aca2ef..000000000 --- a/software/plugins/alert-messenger/src/test/java/tech/ebp/openQuarterMaster/MyMessagingApplicationTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package tech.ebp.openQuarterMaster; - -import io.quarkus.test.common.WithTestResource; -import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; -import io.quarkus.test.junit.QuarkusTest; - -import org.eclipse.microprofile.reactive.messaging.Message; -import org.junit.jupiter.api.Test; - -import jakarta.inject.Inject; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -@QuarkusTest -class MyMessagingApplicationTest { - - @Inject - MyMessagingApplication application; - - @Test - void test() { - assertEquals("HELLO", application.toUpperCase(Message.of("Hello")).getPayload()); - assertEquals("BONJOUR", application.toUpperCase(Message.of("bonjour")).getPayload()); - } -} diff --git a/software/plugins/alert-messenger/src/test/resources/application.yaml b/software/plugins/alert-messenger/src/test/resources/application.yaml new file mode 100644 index 000000000..253f4b06b --- /dev/null +++ b/software/plugins/alert-messenger/src/test/resources/application.yaml @@ -0,0 +1,23 @@ +quarkus: + oqmCoreApi: + refreshDbCacheFrequency: "5m" + devservice: + certPath: dev/devTest-cert-cert.pem + certKeyPath: dev/devTest-cert-key.pem + oidc: + client-id: oqm-app + credentials: + secret: "**********" + # auth-server-url: http://localhost:8115/realms/oqm + keycloak: + devservices: + # Required to export realm + start-command: "start-dev --http-enabled=true --hostname-strict=false --hostname-strict-https=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" + realm-path: "dev/oqm-realm.json" + realm-name: "oqm" + resource-aliases: + systemCert: dev/devTest-cert-cert.pem + systemCertKey: dev/devTest-cert-key.pem + resource-mappings: + systemCert: /opt/keycloak/certs/devTest-cert-cert.pem + systemCertKey: /opt/keycloak/certs/devTest-cert-key.pem \ No newline at end of file From f61d05e5f707234bdbb49cca51e273f9708b975f Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Thu, 15 Aug 2024 01:59:21 -0400 Subject: [PATCH 37/58] Plugin - Alert Messenger - Very close to working test --- software/plugins/alert-messenger/build.gradle | 5 ++ .../plugin/alertMessenger/TestConsumer.java | 10 +++- .../livecycle/LifecycleBean.java | 55 ++++++++++++++++++ .../src/main/resources/application.yaml | 4 +- .../plugin/alertManager/TestConsumerTest.java | 58 +++++++++++++++++++ .../src/test/resources/application.yaml | 10 +++- 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/livecycle/LifecycleBean.java create mode 100644 software/plugins/alert-messenger/src/test/java/tech/ebp/oqm/plugin/alertManager/TestConsumerTest.java diff --git a/software/plugins/alert-messenger/build.gradle b/software/plugins/alert-messenger/build.gradle index 8c7be2991..f1515de67 100644 --- a/software/plugins/alert-messenger/build.gradle +++ b/software/plugins/alert-messenger/build.gradle @@ -17,6 +17,11 @@ dependencies { implementation 'io.quarkus:quarkus-smallrye-health' implementation 'io.quarkus:quarkus-smallrye-openapi' implementation 'io.quarkus:quarkus-config-yaml' + implementation 'io.quarkus:quarkus-resteasy-reactive-qute' + implementation 'io.quarkus:quarkus-oidc' + + implementation 'io.quarkus:quarkus-qute' + implementation 'tech.ebp.oqm.lib:core-api-lib-quarkus:2.2.0-SNAPSHOT' implementation 'org.jboss.slf4j:slf4j-jboss-logmanager' diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java index ef8e40076..e61e74825 100644 --- a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/TestConsumer.java @@ -2,16 +2,24 @@ import jakarta.enterprise.context.ApplicationScoped; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Message; +import java.util.concurrent.CompletionStage; + @Slf4j @ApplicationScoped public class TestConsumer { + @Getter + private boolean received = false; + @Incoming("events-incoming") - public void toUpperCase(Message message) { + public CompletionStage toUpperCase(Message message) { log.info("Received message {}", message); + received = true; + return message.ack(); } } diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/livecycle/LifecycleBean.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/livecycle/LifecycleBean.java new file mode 100644 index 000000000..20f75915b --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/livecycle/LifecycleBean.java @@ -0,0 +1,55 @@ +package tech.ebp.oqm.plugin.alertMessenger.livecycle; + +import io.quarkus.runtime.StartupEvent; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.config.ConfigProvider; + +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 +@Slf4j +public class LifecycleBean { + + public static void logConfig(){ + if (log.isDebugEnabled()) { + TreeMap configMap = new TreeMap<>(); + + for(String curProp : ConfigProvider.getConfig().getPropertyNames()){ + String value; + try { + value = ConfigProvider.getConfig().getValue(curProp, String.class); + } catch(NoSuchElementException e) { + value = ""; + } + configMap.put(curProp, value); + } + + StringBuilder sb = new StringBuilder(); + for (String curProp : configMap.keySet()) { + + sb.append('\t'); + sb.append(curProp); + sb.append('='); + sb.append(configMap.get(curProp)); + sb.append(System.lineSeparator()); + } + log.debug("Configuration: \n{}", sb); + } + } + + void onStart( + @Observes + StartupEvent ev + ) { + logConfig(); + } + +} diff --git a/software/plugins/alert-messenger/src/main/resources/application.yaml b/software/plugins/alert-messenger/src/main/resources/application.yaml index 7ef38c370..eb327affb 100644 --- a/software/plugins/alert-messenger/src/main/resources/application.yaml +++ b/software/plugins/alert-messenger/src/main/resources/application.yaml @@ -1,5 +1,7 @@ quarkus: + log: + level: DEBUG oqmCoreApi: devservice: enableKafka: true @@ -9,7 +11,7 @@ quarkus: keycloak: devservices: # Required to export realm - start-command: "start-dev --http-enabled=true --hostname-strict=false --hostname-strict-https=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" + start-command: "start-dev --http-enabled=true --hostname-strict=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" realm-path: "../../../../dev/oqm-realm.json" realm-name: "oqm" resource-aliases: diff --git a/software/plugins/alert-messenger/src/test/java/tech/ebp/oqm/plugin/alertManager/TestConsumerTest.java b/software/plugins/alert-messenger/src/test/java/tech/ebp/oqm/plugin/alertManager/TestConsumerTest.java new file mode 100644 index 000000000..ff31623d5 --- /dev/null +++ b/software/plugins/alert-messenger/src/test/java/tech/ebp/oqm/plugin/alertManager/TestConsumerTest.java @@ -0,0 +1,58 @@ +package tech.ebp.oqm.plugin.alertManager; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.OqmDatabaseService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.restClient.OqmCoreApiClientService; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.sso.KcClientAuthService; +import tech.ebp.oqm.plugin.alertMessenger.TestConsumer; + +import java.time.LocalDateTime; + +@Slf4j +@QuarkusTest +public class TestConsumerTest { + + @Inject + OqmDatabaseService oqmDatabaseService; + @Inject + KcClientAuthService serviceAccountService; + @RestClient + OqmCoreApiClientService coreApiClientService; + @Inject + ObjectMapper objectMapper; + + @Inject + TestConsumer testConsumer; + + @Test + public void test() throws InterruptedException { + String newid = this.coreApiClientService + .storageBlockAdd( + this.serviceAccountService.getAuthString(), + "default", + this.objectMapper.createObjectNode() + .put("label", "new block") + ) + .await().indefinitely(); + + log.info("Id of new block: {}", newid); + + LocalDateTime timeout = LocalDateTime.now().plusSeconds(10); + while(!this.testConsumer.isReceived()){ + if(LocalDateTime.now().isAfter(timeout)){ + Assertions.fail("Failed to receive message."); + } + + Thread.sleep(250); + } + } + +} diff --git a/software/plugins/alert-messenger/src/test/resources/application.yaml b/software/plugins/alert-messenger/src/test/resources/application.yaml index 253f4b06b..a4e28b831 100644 --- a/software/plugins/alert-messenger/src/test/resources/application.yaml +++ b/software/plugins/alert-messenger/src/test/resources/application.yaml @@ -12,7 +12,7 @@ quarkus: keycloak: devservices: # Required to export realm - start-command: "start-dev --http-enabled=true --hostname-strict=false --hostname-strict-https=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" + start-command: "start-dev --http-enabled=true --hostname-strict=false --https-certificate-file=/opt/keycloak/certs/devTest-cert-cert.pem --https-certificate-key-file=/opt/keycloak/certs/devTest-cert-key.pem" realm-path: "dev/oqm-realm.json" realm-name: "oqm" resource-aliases: @@ -20,4 +20,10 @@ quarkus: systemCertKey: dev/devTest-cert-key.pem resource-mappings: systemCert: /opt/keycloak/certs/devTest-cert-cert.pem - systemCertKey: /opt/keycloak/certs/devTest-cert-key.pem \ No newline at end of file + systemCertKey: /opt/keycloak/certs/devTest-cert-key.pem +mp: + messaging: + incoming: + events-incoming: + bootstrap: + servers: ${devservice.kafka.bootstrapServers} \ No newline at end of file From 9e4ea212e46b777dbba22f89924fceac4c766907 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Thu, 15 Aug 2024 16:38:42 -0400 Subject: [PATCH 38/58] Plugin - Alert Messenger - Now has basic example of ui handling --- software/plugins/alert-messenger/build.gradle | 3 +- .../alertMessenger/interfaces/ui/Home.java | 41 +++++++ .../interfaces/ui/UiInterface.java | 115 ++++++++++++++++++ .../plugin/alertMessenger/model/UserInfo.java | 20 +++ .../plugin/alertMessenger/utils/JwtUtils.java | 36 ++++++ .../resources/META-INF/resources/favicon.ico | Bin 0 -> 142910 bytes .../src/main/resources/application.yaml | 8 ++ .../templates/webui/pages/index.html | 13 ++ 8 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/Home.java create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/UiInterface.java create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/model/UserInfo.java create mode 100644 software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/utils/JwtUtils.java create mode 100644 software/plugins/alert-messenger/src/main/resources/META-INF/resources/favicon.ico create mode 100644 software/plugins/alert-messenger/src/main/resources/templates/webui/pages/index.html diff --git a/software/plugins/alert-messenger/build.gradle b/software/plugins/alert-messenger/build.gradle index f1515de67..f640e8a28 100644 --- a/software/plugins/alert-messenger/build.gradle +++ b/software/plugins/alert-messenger/build.gradle @@ -13,13 +13,12 @@ dependencies { implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}") implementation 'io.quarkus:quarkus-rest' implementation 'io.quarkus:quarkus-rest-jackson' + implementation 'io.quarkus:quarkus-rest-qute' implementation 'io.quarkus:quarkus-messaging-kafka' implementation 'io.quarkus:quarkus-smallrye-health' implementation 'io.quarkus:quarkus-smallrye-openapi' implementation 'io.quarkus:quarkus-config-yaml' - implementation 'io.quarkus:quarkus-resteasy-reactive-qute' implementation 'io.quarkus:quarkus-oidc' - implementation 'io.quarkus:quarkus-qute' implementation 'tech.ebp.oqm.lib:core-api-lib-quarkus:2.2.0-SNAPSHOT' diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/Home.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/Home.java new file mode 100644 index 000000000..b5961d652 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/Home.java @@ -0,0 +1,41 @@ +package tech.ebp.oqm.plugin.alertMessenger.interfaces.ui; + +import io.quarkus.qute.Location; +import io.quarkus.qute.Template; +import jakarta.annotation.security.PermitAll; +import jakarta.annotation.security.RolesAllowed; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.openapi.annotations.tags.Tag; +import org.eclipse.microprofile.openapi.annotations.tags.Tags; + +@Slf4j +@Path("/") +@Tags({@Tag(name = "UI")}) +@RequestScoped +@Produces(MediaType.TEXT_HTML) +public class Home extends UiInterface { + + @Getter + @Inject + @Location("webui/pages/index") + Template pageTemplate; + + @GET + @RolesAllowed("inventoryView") + @Produces(MediaType.TEXT_HTML) + public Response index() { + log.info("Got index page"); + return Response.ok( + this.setupPageTemplate(this.pageTemplate) + ).build(); + } + +} diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/UiInterface.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/UiInterface.java new file mode 100644 index 000000000..b2d76bea5 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/interfaces/ui/UiInterface.java @@ -0,0 +1,115 @@ +package tech.ebp.oqm.plugin.alertMessenger.interfaces.ui; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import io.quarkus.oidc.IdToken; +import io.quarkus.qute.Template; +import io.quarkus.qute.TemplateInstance; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.ws.rs.CookieParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.SecurityContext; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.microprofile.jwt.JsonWebToken; +import tech.ebp.oqm.lib.core.api.quarkus.runtime.OqmDatabaseService; +import tech.ebp.oqm.plugin.alertMessenger.model.UserInfo; +import tech.ebp.oqm.plugin.alertMessenger.utils.JwtUtils; + +@Slf4j +public abstract class UiInterface { + + /** + * For normal use, don't use this. use the `oqmDatabases` + */ + @Inject + @Getter(AccessLevel.PROTECTED) + OqmDatabaseService oqmDatabaseService; + + @Getter(AccessLevel.PROTECTED) + @Inject + @IdToken + JsonWebToken idToken; + + @Getter(AccessLevel.PROTECTED) + @Inject + JsonWebToken accessToken; + + @Getter(AccessLevel.PROTECTED) + @Context + SecurityContext securityContext; + + @Getter(AccessLevel.PROTECTED) + UserInfo userInfo; + + @CookieParam("oqmDb") + String oqmDb; + + @Getter(AccessLevel.PROTECTED) + ArrayNode oqmDatabases; + + protected boolean hasIdToken() { + return this.getIdToken() != null && + this.getIdToken() + .getClaimNames() != null; + } + + protected boolean hasAccessToken() { + return this.getAccessToken() != null && this.getAccessToken().getClaimNames() != null; + } + + /** + * When hit from bare API call with just bearer, token will be access token. + *

+ * When hit from ui, idToken. + * + * @return + */ + protected JsonWebToken getUserToken() { + if (this.hasIdToken()) { + log.debug("Had id token"); + return this.getIdToken(); + } + if (this.hasAccessToken()) { + log.debug("Had access token"); + return this.getAccessToken(); + } + return null; + } + + protected String getUserTokenStr() { + return this.getAccessToken().getRawToken(); + } + + protected String getBearerHeaderStr() { + return "Bearer " + this.getUserTokenStr(); + } + + @PostConstruct + void initialLogAndEntityProcess() { + this.oqmDatabases = this.oqmDatabaseService.getDatabases(); + this.userInfo = JwtUtils.getUserInfo(this.getUserToken()); + + log.info("User roles: {}", this.userInfo.getRoles()); + } + + public String getSelectedDb() { + if (this.oqmDb == null || this.oqmDb.isBlank()) { + if(this.oqmDatabases == null || this.oqmDatabases.isEmpty()){ + throw new IllegalStateException("Cannot have no databases."); + } + //TODO: this but smarter? + return this.getOqmDatabases().get(0).get("id").asText(); + } + return this.oqmDb; + } + + protected TemplateInstance setupPageTemplate(Template template) { + return template + .data("userInfo", this.getUserInfo()) + .data("oqmDbs", this.getOqmDatabases()) + .data("selectedOqmDb", this.getSelectedDb()) + ; + } +} diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/model/UserInfo.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/model/UserInfo.java new file mode 100644 index 000000000..0840a7799 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/model/UserInfo.java @@ -0,0 +1,20 @@ +package tech.ebp.oqm.plugin.alertMessenger.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserInfo { + private String id; + private String name; + private String username; + private String email; + private Set roles; +} diff --git a/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/utils/JwtUtils.java b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/utils/JwtUtils.java new file mode 100644 index 000000000..516b5333c --- /dev/null +++ b/software/plugins/alert-messenger/src/main/java/tech/ebp/oqm/plugin/alertMessenger/utils/JwtUtils.java @@ -0,0 +1,36 @@ +package tech.ebp.oqm.plugin.alertMessenger.utils; + +import org.eclipse.microprofile.jwt.Claims; +import org.eclipse.microprofile.jwt.JsonWebToken; +import tech.ebp.oqm.plugin.alertMessenger.model.UserInfo; + +import java.util.Set; + +public class JwtUtils { + + public static String getId(JsonWebToken jwt){ + return jwt.getClaim(Claims.sub); + } + public static String getName(JsonWebToken jwt){ + return jwt.getClaim("name"); + } + public static String getEmail(JsonWebToken jwt){ + return jwt.getClaim(Claims.email); + } + public static String getUserName(JsonWebToken jwt){ + return jwt.getClaim(Claims.preferred_username); + } + public static Set getRoles(JsonWebToken jwt){ + return jwt.getGroups(); + } + + public static UserInfo getUserInfo(JsonWebToken jwt){ + return UserInfo.builder() + .id(getId(jwt)) + .name(getName(jwt)) + .username(getUserName(jwt)) + .email(getEmail(jwt)) + .roles(getRoles(jwt)) + .build(); + } +} diff --git a/software/plugins/alert-messenger/src/main/resources/META-INF/resources/favicon.ico b/software/plugins/alert-messenger/src/main/resources/META-INF/resources/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a00ed96f34452a3e9deda3017b2cd94a189bc3cf GIT binary patch literal 142910 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 + + + Welcome! + + + +

+ Welcome, {userInfo.getName()}! +

+ + + \ No newline at end of file From e77d2e8dd16cbda702647ccd0b42a682ecd1d1af Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Thu, 15 Aug 2024 16:50:08 -0400 Subject: [PATCH 39/58] Plugin - Alert Messenger - Added database selection to ui --- .../META-INF/resources/res/js/OqmDb.js | 18 ++++++++++++++++++ .../res/lib/js-cookie/3.0.5/js.cookie.min.js | 2 ++ .../resources/templates/webui/pages/index.html | 17 +++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/js/OqmDb.js create mode 100644 software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/lib/js-cookie/3.0.5/js.cookie.min.js diff --git a/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/js/OqmDb.js b/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/js/OqmDb.js new file mode 100644 index 000000000..909bf32e1 --- /dev/null +++ b/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/js/OqmDb.js @@ -0,0 +1,18 @@ +const OqmDbUtils = { + navDbSelectForm: $("#navDbSelectForm"), + navDatabaseSelector: $("#navDatabaseSelector"), + newDbSelected: function (){ + let newDbId = this.navDatabaseSelector.val(); + console.log("Input to select database changed. New selection: ", newDbId); + + if(confirm("Are you sure you want to swap databases?")){ + console.log("User confirmed database switch."); + + Cookies.set('oqmDb', newDbId, {expires: 365, sameSite: 'strict'}); + PageMessages.reloadPageWithMessage("Successfully swapped databases.", "success"); + } else { + console.log("User canceled db swap."); + this.navDbSelectForm.trigger('reset'); + } + } +} \ No newline at end of file diff --git a/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/lib/js-cookie/3.0.5/js.cookie.min.js b/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/lib/js-cookie/3.0.5/js.cookie.min.js new file mode 100644 index 000000000..962d48d0e --- /dev/null +++ b/software/plugins/alert-messenger/src/main/resources/META-INF/resources/res/lib/js-cookie/3.0.5/js.cookie.min.js @@ -0,0 +1,2 @@ +/*! js-cookie v3.0.5 | MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t Welcome, {userInfo.getName()}! +
+

+ Database selected: +

+ + + + + \ No newline at end of file From 18a5ee12bf8ec5ed9df74ce27d70db8115aa1d5f Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sat, 17 Aug 2024 01:10:53 -0400 Subject: [PATCH 40/58] Core - API - Refactored all message topic variable --- .../HistoryEventNotificationService.java | 7 ++++--- .../mongo/MongoHistoriedObjectServiceTest.java | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) 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 4ecf220e4..a3dc877e7 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 @@ -26,7 +26,8 @@ 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 TOPIC_PREPEND = "oqm-core-"; - public static final String ALL_EVENT_TOPIC = "all-events"; + 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; @@ -67,7 +68,7 @@ void sendEventOutgoing(EventNotificationWrapper notificationWrapper) { notificationWrapper.getEvent() ).addMetadata( OutgoingKafkaRecordMetadata.builder() - .withTopic(TOPIC_PREPEND + ALL_EVENT_TOPIC) + .withTopic(ALL_EVENT_TOPIC) .build() )); this.outgoingEventEmitter.send( @@ -86,7 +87,7 @@ void sendEventOutgoing(EventNotificationWrapper notificationWrapper) { ).addMetadata( OutgoingKafkaRecordMetadata.builder() .withTopic( - TOPIC_PREPEND + (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC) + TOPIC_PREPEND + (notificationWrapper.getDatabase() == null? "" : notificationWrapper.getDatabase().toHexString() + "-") + ALL_EVENT_TOPIC_LABEL) .build() )); log.debug("Sent event to external channels: {}/{}", notificationWrapper.getClass().getSimpleName(), notificationWrapper.getEvent().getId()); 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 481046d47..fa0ab62b4 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,10 @@ 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.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,18 +71,28 @@ public void testAdd() throws JsonProcessingException { assertEquals(objectId, createEvent.getObjectId()); assertNotNull(createEvent.getEntity()); assertEquals(testUser.getId(), createEvent.getEntity()); - + + ConsumerTask createFromAll = this.kafkaCompanion.consumeStrings().fromTopics( - HistoryEventNotificationService.TOPIC_PREPEND + 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 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(), CreateEvent.class); + assertEquals(createEvent, createEventFromMessage); ConsumerTask createFromCreate = this.kafkaCompanion.consumeStrings().fromTopics( - HistoryEventNotificationService.TOPIC_PREPEND + this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC, + HistoryEventNotificationService.TOPIC_PREPEND + this.oqmDatabaseService.getDatabaseCache().getFromName(DEFAULT_TEST_DB_NAME).get().getDbId().toHexString() + "-" + HistoryEventNotificationService.ALL_EVENT_TOPIC_LABEL, 1 ); createFromCreate.awaitCompletion(); From a4fc4523688d29e04cca0f51cd5a3d30911af8ff Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 18 Aug 2024 19:14:38 -0400 Subject: [PATCH 41/58] Lib - CoreAPI - Quarkus - Attempts at making a working kafka devservice --- .../core-api-lib-quarkus/deployment/pom.xml | 6 +++ .../CoreApiLibDevserviceConfig.java | 2 +- .../CoreApiLibQuarkusProcessor.java | 41 +++++++++++++++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/software/libs/core-api-lib-quarkus/deployment/pom.xml b/software/libs/core-api-lib-quarkus/deployment/pom.xml index ef2eeb55f..b4dc932a4 100644 --- a/software/libs/core-api-lib-quarkus/deployment/pom.xml +++ b/software/libs/core-api-lib-quarkus/deployment/pom.xml @@ -78,6 +78,12 @@ 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 bb18b2844..5d9f38cab 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 @@ -22,7 +22,7 @@ public class CoreApiLibDevserviceConfig { * Enables kafka. */ @ConfigItem(name="enableKafka") - @WithDefault("false") + @WithDefault("true") public boolean enableKafka; /** 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 1de73b0d4..867659c19 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 @@ -11,6 +11,7 @@ import org.testcontainers.containers.KafkaContainer; 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; @@ -24,7 +25,8 @@ 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() { @@ -75,15 +77,38 @@ public List createContainer( .toBuildItem() ); } - if (config.devservice.enableKafka) {//optionally, kafka - KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) - .withKraft() - .withNetwork(Network.SHARED); + if (config.devservice.enableKafka) { + //TODO:: allow connection from core api devservice, and the using app. Update hostname, replace in bootstrap? + RedpandaContainer kafka = new RedpandaContainer(DockerImageName.parse("docker.redpanda.com/redpandadata/redpanda:v23.1.2")) + .withNetwork(Network.SHARED) + .withAccessToHost(true) + .withNetworkAliases(KAFKA_DEVSERVICE_HOSTNAME) +// .withListener(() -> "external://"+KAFKA_DEVSERVICE_HOSTNAME + ":9092") + .withListener(() -> KAFKA_DEVSERVICE_HOSTNAME + ":9092") + .withListener(() -> KAFKA_DEVSERVICE_HOSTNAME + ":9093") + + // { {external:{host: localhost, port: 32884}}, {internal:{host: 127.0.0.1, port: 9093}}} +// .withEnv("REDPANDA_KAFKA_ADVERTISED_LISTENERS", String.format( +// "internal://%s:9093,external://%s:9092", +// KAFKA_DEVSERVICE_HOSTNAME, +// KAFKA_DEVSERVICE_HOSTNAME +// )) + ; +// KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0")) +// .withKraft() +// .withNetwork(Network.SHARED) +// .withAccessToHost(true) +// .withNetworkAliases(KAFKA_DEVSERVICE_HOSTNAME) +// .withListener(() -> KAFKA_DEVSERVICE_HOSTNAME + ":9092") +// ; kafka.start(); + //TODO:: fix; RP advertises localhost for listeners despite supplying the correct hostname as config + String bootstrapServers = kafka.getBootstrapServers(); kafkaConnectionInfo.putAll(Map.of( "quarkus.reactive-messaging.health.enabled", "true", - "mp.messaging.outgoing.events-outgoing.bootstrap.servers", kafka.getBootstrapServers(), + "mp.messaging.outgoing.events-outgoing.bootstrap.servers", String.format("PLAINTEXT://%s:%d", KAFKA_DEVSERVICE_HOSTNAME, 9092), + "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" @@ -138,8 +163,8 @@ public List createContainer( 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("mp.messaging.outgoing.events-outgoing.bootstrap.servers")); + if (!kafkaConnectionInfo.isEmpty()) { + props.put("devservice.kafka.bootstrapServers", kafkaConnectionInfo.get("devservice.kafka.bootstrapServers")); } output.add(new DevServicesResultBuildItem.RunningDevService( From 276cdaaca473f699d4c7152713df16d9e4ab3354 Mon Sep 17 00:00:00 2001 From: GregJohnStewart Date: Sun, 18 Aug 2024 19:18:21 -0400 Subject: [PATCH 42/58] Core - Base Station - Minor tweak to db switch form --- .../src/main/resources/templates/webui/mainWebPageTemplate.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@