From 55594392d162ee2b91e360abaf4e4f0cfe30281f Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Thu, 9 Oct 2025 12:01:16 -0500 Subject: [PATCH 001/112] Create makefile.yml --- .github/workflows/makefile.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/makefile.yml diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml new file mode 100644 index 000000000..e60fbe0f3 --- /dev/null +++ b/.github/workflows/makefile.yml @@ -0,0 +1,27 @@ +name: Makefile CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: configure + run: ./configure + + - name: Install dependencies + run: make + + - name: Run check + run: make check + + - name: Run distcheck + run: make distcheck From e758e9abf6cb9e202e1f9a122ad0d6fee60ca1f0 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 10 Nov 2025 12:25:28 -0600 Subject: [PATCH 002/112] :tada: Add basic models that represent certain mqtt events/topics published. Reuse jackson for json serialization from API. #2 --- .../logr/common/mqtt/constants/MqttTopic.java | 26 ++++++++ .../aps/logr/common/mqtt/model/AddEvent.java | 25 ++++++++ .../logr/common/mqtt/model/DeleteEvent.java | 25 ++++++++ .../logr/common/mqtt/model/LogEntryEvent.java | 25 ++++++++ .../aps/logr/common/mqtt/model/MqttEvent.java | 60 +++++++++++++++++++ .../logr/common/mqtt/model/UpdateEvent.java | 25 ++++++++ 6 files changed, 186 insertions(+) create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/AddEvent.java create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/DeleteEvent.java create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/UpdateEvent.java diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java new file mode 100644 index 000000000..7ca319aae --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.constants; + +/** + * + * @author djarosz + */ +public enum MqttTopic { + UPDATE("bely/update"), + ADD("bely/add"), + DELETE("bely/delete"), + LOGENTRY("bely/logEntry"); + + private final String value; + + MqttTopic(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/AddEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/AddEvent.java new file mode 100644 index 000000000..cc30cadbc --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/AddEvent.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; + +/** + * + * @author djarosz + */ +public class AddEvent extends MqttEvent { + + public AddEvent(CdbEntity entity, String description) { + super(entity, description); + } + + @Override + public MqttTopic getTopic() { + return MqttTopic.ADD; + } + +} diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/DeleteEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/DeleteEvent.java new file mode 100644 index 000000000..aef3acaad --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/DeleteEvent.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; + +/** + * + * @author djarosz + */ +public class DeleteEvent extends MqttEvent { + + public DeleteEvent(CdbEntity entity, String description) { + super(entity, description); + } + + @Override + public MqttTopic getTopic() { + return MqttTopic.DELETE; + } + +} diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java new file mode 100644 index 000000000..64e3d7ed7 --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; + +/** + * + * @author djarosz + */ +public class LogEntryEvent extends MqttEvent { + + public LogEntryEvent(CdbEntity entity, String description) { + super(entity, description); + } + + @Override + public MqttTopic getTopic() { + return MqttTopic.LOGENTRY; + } + +} diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java new file mode 100644 index 000000000..bfb643646 --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; + +/** + * + * @author djarosz + */ +public abstract class MqttEvent { + + Object entityId; + String entityName; + String description; + + public MqttEvent(CdbEntity entity, String description) { + this.entityId = entity.getId(); + this.entityName = entity.getClass().getSimpleName(); + this.description = description; + } + + @JsonIgnore + public abstract MqttTopic getTopic(); + + public Object getEntityId() { + return entityId; + } + + public void setEntityId(Object entityId) { + this.entityId = entityId; + } + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String toJson() throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.writeValueAsString(this); + } +} diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/UpdateEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/UpdateEvent.java new file mode 100644 index 000000000..bf4e9fc34 --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/UpdateEvent.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; + +/** + * + * @author djarosz + */ +public class UpdateEvent extends MqttEvent { + + public UpdateEvent(CdbEntity entity, String description) { + super(entity, description); + } + + @Override + public MqttTopic getTopic() { + return MqttTopic.UPDATE; + } + +} From 8f651d95475f33910f73569a1fa2c2deae7b846d Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 10 Nov 2025 12:27:43 -0600 Subject: [PATCH 003/112] :wastebasket: No need for github actions. Not ready yet. --- .github/workflows/makefile.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/makefile.yml diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml deleted file mode 100644 index e60fbe0f3..000000000 --- a/.github/workflows/makefile.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Makefile CI - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: configure - run: ./configure - - - name: Install dependencies - run: make - - - name: Run check - run: make check - - - name: Run distcheck - run: make distcheck From 143913edd0fcf5cc2ecbd78d1e5ae9e5e0bc4c9b Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 10 Nov 2025 12:33:21 -0600 Subject: [PATCH 004/112] :sparkles: Add support for basic events such as add update delete. #2 --- .../utilities/CdbEntityControllerUtility.java | 292 ++++++++++-------- 1 file changed, 166 insertions(+), 126 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java index 14bd21d11..21e8c5ab3 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java @@ -4,7 +4,14 @@ */ package gov.anl.aps.logr.portal.controllers.utilities; +import com.fasterxml.jackson.core.JsonProcessingException; +import fish.payara.cloud.connectors.mqtt.api.MQTTConnection; +import fish.payara.cloud.connectors.mqtt.api.MQTTConnectionFactory; import gov.anl.aps.logr.common.exceptions.CdbException; +import gov.anl.aps.logr.common.mqtt.model.AddEvent; +import gov.anl.aps.logr.common.mqtt.model.DeleteEvent; +import gov.anl.aps.logr.common.mqtt.model.UpdateEvent; +import gov.anl.aps.logr.common.mqtt.model.MqttEvent; import gov.anl.aps.logr.common.utilities.StringUtility; import gov.anl.aps.logr.portal.constants.SystemLogLevel; import gov.anl.aps.logr.portal.model.db.beans.CdbEntityFacade; @@ -13,6 +20,7 @@ import gov.anl.aps.logr.portal.model.db.entities.PropertyValue; import gov.anl.aps.logr.portal.model.db.entities.UserInfo; import gov.anl.aps.logr.portal.utilities.SearchResult; +import gov.anl.aps.logr.portal.utilities.SessionUtility; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -23,43 +31,64 @@ import org.apache.logging.log4j.Logger; /** - * Controller utility provides unified functionality for managing entities - * to be used from view controllers as well as API endpoints. + * Controller utility provides unified functionality for managing entities to be + * used from view controllers as well as API endpoints. * * @author darek - * @param Database mapped class of the entity. - * @param Database facade provides communication to database. + * @param Database mapped class of the entity. + * @param Database facade provides communication to database. */ public abstract class CdbEntityControllerUtility> { - + private static final Logger logger = LogManager.getLogger(CdbEntityControllerUtility.class.getName()); - - LogControllerUtility logControllerUtility; - + + protected void publishMqttEvent(MqttEvent event) { + MQTTConnectionFactory mqttFactory = SessionUtility.fetchMQTTConnectionFactory(); + String jsonMessage; + + try { + jsonMessage = event.toJson(); + } catch (JsonProcessingException ex) { + logger.error(ex); + return; + } + + if (mqttFactory == null) { + logger.warn("MQTT not configured. Skipping event: " + jsonMessage); + return; + } + MQTTConnection connection = mqttFactory.getConnection(); + try { + connection.publish(event.getTopic().getValue(), jsonMessage.getBytes(), 0, false); + } catch (Exception ex) { + } + } + /** * Abstract method for returning entity DB facade. * * @return entity DB facade */ protected abstract FacadeType getEntityDbFacade(); - + /** * Abstract method for creating new entity instance. * * @return created entity instance */ - public abstract EntityType createEntityInstance(UserInfo sessionUser); - + public abstract EntityType createEntityInstance(UserInfo sessionUser); + public EntityType create(EntityType entity, UserInfo createdByUserInfo) throws CdbException, RuntimeException { - try { + try { prepareEntityInsert(entity, createdByUserInfo); getEntityDbFacade().create(entity); - + addCreatedSystemLog(entity, createdByUserInfo); entity.setPersitanceErrorMessage(null); - + publishMqttEvent(new AddEvent(entity, "Add action completed")); + clearCaches(); - return entity; + return entity; } catch (CdbException ex) { logger.error("Could not create " + getDisplayEntityTypeName() + ": " + ex.getMessage()); addCreatedWarningSystemLog(ex, entity, createdByUserInfo); @@ -73,15 +102,18 @@ public EntityType create(EntityType entity, UserInfo createdByUserInfo) throws C throw ex; } } - + public void createList(List entities, UserInfo createdByUserInfo) throws CdbException, RuntimeException { try { for (EntityType entity : entities) { prepareEntityInsert(entity, createdByUserInfo); } - getEntityDbFacade().create(entities); - - addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Created " + entities.size() + " entities.", createdByUserInfo); + getEntityDbFacade().create(entities); + + addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Created " + entities.size() + " entities.", createdByUserInfo); + for (EntityType entity : entities) { + publishMqttEvent(new AddEvent(entity, "Add action completed")); + } setPersistenceErrorMessageForList(entities, null); clearCaches(); } catch (CdbException ex) { @@ -95,59 +127,62 @@ public void createList(List entities, UserInfo createdByUserInfo) th setPersistenceErrorMessageForList(entities, ex.getMessage()); addCdbEntityWarningSystemLog("Failed to create list of entities: " + getDisplayEntityTypeName(), ex, null, createdByUserInfo); throw ex; - } + } } - + public EntityType update(EntityType entity, UserInfo updatedByUserInfo) throws CdbException, RuntimeException { - try { + try { logger.debug("Updating " + getDisplayEntityTypeName() + " " + getEntityInstanceName(entity)); prepareEntityUpdate(entity, updatedByUserInfo); EntityType updatedEntity = getEntityDbFacade().edit(entity); addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Updated: " + entity.getSystemLogString(), updatedByUserInfo); + publishMqttEvent(new UpdateEvent(entity, "Update action completed")); entity.setPersitanceErrorMessage(null); - + clearCaches(); - return updatedEntity; + return updatedEntity; } catch (CdbException ex) { entity.setPersitanceErrorMessage(ex.getMessage()); addCdbEntityWarningSystemLog("Failed to update", ex, entity, updatedByUserInfo); logger.error("Could not update " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + ex.getMessage()); + + getEntityInstanceName(entity) + ": " + ex.getMessage()); throw ex; } catch (RuntimeException ex) { - Throwable t = ExceptionUtils.getRootCause(ex); + Throwable t = ExceptionUtils.getRootCause(ex); logger.error("Could not update " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + t.getMessage()); + + getEntityInstanceName(entity) + ": " + t.getMessage()); addCdbEntityWarningSystemLog("Failed to update", ex, entity, updatedByUserInfo); entity.setPersitanceErrorMessage(t.getMessage()); throw ex; - } + } } - + public EntityType updateOnRemoval(EntityType entity, UserInfo updatedByUserInfo) throws CdbException, RuntimeException { try { logger.debug("Updating " + getDisplayEntityTypeName() + " " + getEntityInstanceName(entity)); prepareEntityUpdateOnRemoval(entity); EntityType updatedEntity = getEntityDbFacade().edit(entity); clearCaches(); - - return updatedEntity; + + publishMqttEvent(new UpdateEvent(entity, "Update on removal action completed")); + + return updatedEntity; } catch (CdbException ex) { entity.setPersitanceErrorMessage(ex.getMessage()); addCdbEntityWarningSystemLog("Failed to update", ex, entity, updatedByUserInfo); logger.error("Could not update " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + ex.getMessage()); + + getEntityInstanceName(entity) + ": " + ex.getMessage()); throw ex; } catch (RuntimeException ex) { - Throwable t = ExceptionUtils.getRootCause(ex); + Throwable t = ExceptionUtils.getRootCause(ex); logger.error("Could not update " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + t.getMessage()); + + getEntityInstanceName(entity) + ": " + t.getMessage()); addCdbEntityWarningSystemLog("Failed to update", ex, entity, updatedByUserInfo); entity.setPersitanceErrorMessage(t.getMessage()); throw ex; - } + } } - + public void updateList(List entities, UserInfo updatedByUserInfo) throws CdbException, RuntimeException { try { for (EntityType entity : entities) { @@ -155,9 +190,10 @@ public void updateList(List entities, UserInfo updatedByUserInfo) th prepareEntityUpdate(entity, updatedByUserInfo); } getEntityDbFacade().edit(entities); - for (EntityType entity : entities) { + for (EntityType entity : entities) { entity.setPersitanceErrorMessage(null); addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Updated: " + entity.getSystemLogString(), updatedByUserInfo); + publishMqttEvent(new UpdateEvent(entity, "Update action completed")); } clearCaches(); } catch (CdbException ex) { @@ -168,35 +204,37 @@ public void updateList(List entities, UserInfo updatedByUserInfo) th } catch (RuntimeException ex) { Throwable t = ExceptionUtils.getRootCause(ex); logger.error("Could not update list of " + getDisplayEntityTypeName() + ": " + t.getMessage()); - addCdbEntityWarningSystemLog("Failed to update list of " + getDisplayEntityTypeName(), ex, null, updatedByUserInfo); + addCdbEntityWarningSystemLog("Failed to update list of " + getDisplayEntityTypeName(), ex, null, updatedByUserInfo); setPersistenceErrorMessageForList(entities, t.getMessage()); throw ex; - } + } } - + public void destroy(EntityType entity, UserInfo destroyedByUserInfo) throws CdbException, RuntimeException { try { prepareEntityDestroy(entity, destroyedByUserInfo); getEntityDbFacade().remove(entity); - - addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Deleted: " + entity.getSystemLogString(), destroyedByUserInfo); + + addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Deleted: " + entity.getSystemLogString(), destroyedByUserInfo); + publishMqttEvent(new DeleteEvent(entity, "Delete action completed")); + clearCaches(); } catch (CdbException ex) { entity.setPersitanceErrorMessage(ex.getMessage()); addCdbEntityWarningSystemLog("Failed to destroy", ex, entity, destroyedByUserInfo); logger.error("Could not destroy " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + ex.getMessage()); + + getEntityInstanceName(entity) + ": " + ex.getMessage()); throw ex; } catch (RuntimeException ex) { - Throwable t = ExceptionUtils.getRootCause(ex); + Throwable t = ExceptionUtils.getRootCause(ex); logger.error("Could not destroy " + getDisplayEntityTypeName() + " " - + getEntityInstanceName(entity)+ ": " + t.getMessage()); + + getEntityInstanceName(entity) + ": " + t.getMessage()); addCdbEntityWarningSystemLog("Failed to destroy", ex, entity, destroyedByUserInfo); entity.setPersitanceErrorMessage(t.getMessage()); throw ex; - } + } } - + public void destroyList( List entities, EntityType updateEntity, UserInfo destroyedByUserInfo) @@ -216,6 +254,9 @@ public void destroyList( getEntityDbFacade().remove(entities, updateEntity); addCdbEntitySystemLog(SystemLogLevel.entityInfo, "Deleted: " + entities.size() + " entities.", destroyedByUserInfo); + for (EntityType entity : entities) { + publishMqttEvent(new DeleteEvent(entity, "Delete action completed")); + } setPersistenceErrorMessageForList(entities, null); clearCaches(); } catch (CdbException ex) { @@ -228,15 +269,15 @@ public void destroyList( setPersistenceErrorMessageForList(entities, ex.getMessage()); addCdbEntityWarningSystemLog("Failed to delete list of " + getDisplayEntityTypeName(), ex, updateEntity, destroyedByUserInfo); throw ex; - } + } } - + /** - * On database operation clear cache of related cached entity when needed. + * On database operation clear cache of related cached entity when needed. */ - protected void clearCaches() { + protected void clearCaches() { } - + /** * Find entity instance by id. * @@ -246,46 +287,46 @@ protected void clearCaches() { public EntityType findById(Integer id) { return getEntityDbFacade().find(id); } - + /** - * Used by import framework. Looks up entity by path. Default implementation - * raises exception. Subclasses should override to provide support for lookup - * by path. + * Used by import framework. Looks up entity by path. Default implementation + * raises exception. Subclasses should override to provide support for + * lookup by path. */ public EntityType findByPath(String path) throws CdbException { throw new CdbException("controller utility does not support lookup by path"); } - + public String getEntityInstanceName(EntityType entity) { if (entity != null) { - return entity.toString(); + return entity.toString(); } - return ""; - } - - public abstract String getEntityTypeName(); - + return ""; + } + + public abstract String getEntityTypeName(); + public String getDisplayEntityTypeName() { String entityTypeName = getEntityTypeName(); - - entityTypeName = entityTypeName.substring(0, 1).toUpperCase() + entityTypeName.substring(1); - - String displayEntityTypeName = ""; - - int prevEnd = 0; + + entityTypeName = entityTypeName.substring(0, 1).toUpperCase() + entityTypeName.substring(1); + + String displayEntityTypeName = ""; + + int prevEnd = 0; for (int i = 1; i < entityTypeName.length(); i++) { - Character c = entityTypeName.charAt(i); + Character c = entityTypeName.charAt(i); if (Character.isUpperCase(c)) { - displayEntityTypeName += entityTypeName.substring(prevEnd, i) + " "; - prevEnd = i; + displayEntityTypeName += entityTypeName.substring(prevEnd, i) + " "; + prevEnd = i; } } - - displayEntityTypeName += entityTypeName.substring(prevEnd); - - return displayEntityTypeName; + + displayEntityTypeName += entityTypeName.substring(prevEnd); + + return displayEntityTypeName; } - + /** * Prepare entity insert. * @@ -294,9 +335,9 @@ public String getDisplayEntityTypeName() { * @param entity entity instance * @throws CdbException in case of any errors */ - protected void prepareEntityInsert(EntityType entity, UserInfo userInfo) throws CdbException { + protected void prepareEntityInsert(EntityType entity, UserInfo userInfo) throws CdbException { } - + /** * Prepare entity update. * @@ -305,24 +346,24 @@ protected void prepareEntityInsert(EntityType entity, UserInfo userInfo) throws * @param entity entity instance * @throws CdbException in case of any errors */ - protected void prepareEntityUpdate(EntityType entity, UserInfo updatedByUser) throws CdbException { + protected void prepareEntityUpdate(EntityType entity, UserInfo updatedByUser) throws CdbException { } - + protected void prepareEntityUpdateOnRemoval(EntityType entity) throws CdbException { } - - protected void prepareEntityDestroy(EntityType entity, UserInfo userInfo) throws CdbException { + + protected void prepareEntityDestroy(EntityType entity, UserInfo userInfo) throws CdbException { } - + protected void addCreatedSystemLog(EntityType entity, UserInfo createdByUserInfo) throws CdbException { - String message = "Created: " + entity.getSystemLogString(); - addCdbEntitySystemLog(SystemLogLevel.entityInfo, message, createdByUserInfo); + String message = "Created: " + entity.getSystemLogString(); + addCdbEntitySystemLog(SystemLogLevel.entityInfo, message, createdByUserInfo); } - + protected void addCreatedWarningSystemLog(Exception exception, EntityType entity, UserInfo createdByUserInfo) throws CdbException { addCdbEntityWarningSystemLog("Failed to create", exception, entity, createdByUserInfo); } - + /** * Allows the controller to quickly add a warning log entry while * automatically appending appropriate info. @@ -342,8 +383,8 @@ protected void addCdbEntityWarningSystemLog(String warningMessage, Exception exc addCdbEntitySystemLog(SystemLogLevel.entityWarning, warningMessage, sessionUser); } - - /** + + /** * Allows the controller to quickly add a log entry to system logs with * current session user stamp. * @@ -351,7 +392,7 @@ protected void addCdbEntityWarningSystemLog(String warningMessage, Exception exc * @param message * @param sessionUser */ - protected void addCdbEntitySystemLog(SystemLogLevel logLevel, String message, UserInfo sessionUser) throws CdbException { + protected void addCdbEntitySystemLog(SystemLogLevel logLevel, String message, UserInfo sessionUser) throws CdbException { if (sessionUser != null) { String username = sessionUser.getUsername(); message = "User: " + username + " | " + message; @@ -359,36 +400,36 @@ protected void addCdbEntitySystemLog(SystemLogLevel logLevel, String message, Us LogControllerUtility logControllerUtility = LogControllerUtility.getSystemLogInstance(); logControllerUtility.addSystemLog(logLevel, message); } - + protected void setPersistenceErrorMessageForList(List entities, String msg) { for (EntityType entity : entities) { entity.setPersitanceErrorMessage(msg); } - } + } public List getAllEntities() { return getEntityDbFacade().findAll(); } - + public List searchEntities(String searchString, Map searchOpts) { - return searchEntities(searchString); + return searchEntities(searchString); } - + public List searchEntities(String searchString) { - return getEntityDbFacade().searchEntities(searchString); + return getEntityDbFacade().searchEntities(searchString); } - + public String generatePatternString(String searchString) { - String patternString; - if (searchString.contains("?") || searchString.contains("*")) { - patternString = searchString.replace("*", ".*"); + String patternString; + if (searchString.contains("?") || searchString.contains("*")) { + patternString = searchString.replace("*", ".*"); patternString = patternString.replace("?", "."); } else { - patternString = Pattern.quote(searchString); + patternString = Pattern.quote(searchString); } - return patternString; + return patternString; } - + public Pattern getSearchPattern(String patternString, boolean caseInsensitive) { Pattern searchPattern; if (caseInsensitive) { @@ -396,56 +437,55 @@ public Pattern getSearchPattern(String patternString, boolean caseInsensitive) { } else { searchPattern = Pattern.compile(patternString); } - - return searchPattern; + + return searchPattern; } - + public LinkedList performEntitySearch(String searchString, boolean caseInsensitive) { - return performEntitySearch(searchString, null, caseInsensitive); + return performEntitySearch(searchString, null, caseInsensitive); } - + /** * Search all entities for a given string. * * @param searchString search string * @param caseInsensitive use case insensitive search - * @return + * @return */ public LinkedList performEntitySearch(String searchString, Map searchOpts, boolean caseInsensitive) { - LinkedList searchResultList = new LinkedList<>(); - if (searchString == null || searchString.isEmpty()) { + LinkedList searchResultList = new LinkedList<>(); + if (searchString == null || searchString.isEmpty()) { return searchResultList; } - + // Start new search String patternString = generatePatternString(searchString); - Pattern searchPattern = getSearchPattern(patternString, caseInsensitive); - - + Pattern searchPattern = getSearchPattern(patternString, caseInsensitive); + List allObjectList = searchEntities(searchString, searchOpts); for (EntityType entity : allObjectList) { try { SearchResult searchResult = entity.createSearchResultInfo(searchPattern); - if (!searchResult.isEmpty()) { - searchResultList.add(searchResult); + if (!searchResult.isEmpty()) { + searchResultList.add(searchResult); } } catch (RuntimeException ex) { logger.warn("Could not search entity " + entity.toString() + " (Error: " + ex.toString() + ")"); } } - - return searchResultList; + + return searchResultList; } - + public PropertyValue preparePropertyTypeValueAdd(EntityType cdbDomainEntity, PropertyType propertyType) { return preparePropertyTypeValueAdd(cdbDomainEntity, propertyType, propertyType.getDefaultValue(), null); } public PropertyValue preparePropertyTypeValueAdd(EntityType cdbDomainEntity, - PropertyType propertyType, String propertyValueString, String tag) { + PropertyType propertyType, String propertyValueString, String tag) { // Implement in controller with entity info. - return null; + return null; } public PropertyValue preparePropertyTypeValueAdd(EntityType cdbEntity, @@ -474,7 +514,7 @@ public PropertyValue preparePropertyTypeValueAdd(EntityType cdbEntity, } public PropertyType prepareCableEndDesignationPropertyType() { - + PropertyTypeControllerUtility propertyTypeControllerUtility = new PropertyTypeControllerUtility(); PropertyType propertyType = propertyTypeControllerUtility.createEntityInstance(null); @@ -488,7 +528,7 @@ public PropertyType prepareCableEndDesignationPropertyType() { logger.error(ex.getMessage()); return null; } - + return propertyType; } @@ -510,5 +550,5 @@ public String getDisplayItemConnectorsLabel() { String labelString = StringUtility.capitalize(getDisplayItemConnectorName()); return labelString + "s"; } - + } From 6f7e60e3f7e47f814fa0c1e65256d0b86da15d1e Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 10 Nov 2025 12:35:43 -0600 Subject: [PATCH 005/112] :boom: Add a session utility call that fetches configured mqtt connection factory from payara. #2 --- .../logr/portal/utilities/SessionUtility.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java index 0a6256779..b05acb786 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java @@ -4,16 +4,12 @@ */ package gov.anl.aps.logr.portal.utilities; +import fish.payara.cloud.connectors.mqtt.api.MQTTConnectionFactory; import gov.anl.aps.logr.portal.model.db.entities.UserInfo; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import java.util.Stack; -import java.util.logging.Level; import javax.faces.application.FacesMessage; import javax.faces.application.NavigationHandler; import javax.faces.application.ViewHandler; @@ -47,6 +43,7 @@ public class SessionUtility { private static final String MODULE_NAME_LOOKUP = "java:module/ModuleName"; private static final String JAVA_LOOKUP_START = "java:global/"; private static String FACADE_LOOKUP_STRING_START = null; + private static final String BELY_MQTT_NAME = "bely/MQTT/factory"; private static final String USER_SESSION_COOKIE_KEY = "USERSESSIONID"; @@ -133,7 +130,7 @@ public static UserInfo getUser() { public static String getRemoteAddress() { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(); - return request.getRemoteAddr(); + return request.getRemoteAddr(); } public static String getSessionCookie() { @@ -141,10 +138,10 @@ public static String getSessionCookie() { Cookie cookie = (Cookie) cookieMap.get(USER_SESSION_COOKIE_KEY); - if (cookie != null) { + if (cookie != null) { String value = cookie.getValue(); - - return value; + + return value; } return null; @@ -323,6 +320,18 @@ public static Object findBean(String beanName) { return (Object) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class); } + public static MQTTConnectionFactory fetchMQTTConnectionFactory() { + try { + InitialContext context = new InitialContext(); + + return (MQTTConnectionFactory) context.lookup(BELY_MQTT_NAME); + } catch (NamingException ex) { + logger.error(ex); + } + return null; + + } + public static Object findFacade(String facadeName) { try { InitialContext context = new InitialContext(); From 27386c61e300ece4ab291075b75723e4f15325b4 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:20:35 -0600 Subject: [PATCH 006/112] Standardize mqtt event to use CdbEnttiy that can be specified in class extension. #2 --- .../aps/logr/common/mqtt/model/MqttEvent.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java index bfb643646..eaf12c91e 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/MqttEvent.java @@ -14,35 +14,30 @@ * * @author djarosz */ -public abstract class MqttEvent { +public abstract class MqttEvent { - Object entityId; - String entityName; + Entity entity; String description; - public MqttEvent(CdbEntity entity, String description) { - this.entityId = entity.getId(); - this.entityName = entity.getClass().getSimpleName(); + public MqttEvent(Entity entity, String description) { + this.entity = entity; this.description = description; } @JsonIgnore public abstract MqttTopic getTopic(); - public Object getEntityId() { - return entityId; + @JsonIgnore + public CdbEntity getEntity() { + return entity; } - public void setEntityId(Object entityId) { - this.entityId = entityId; + public Object getEntityId() { + return entity.getId(); } public String getEntityName() { - return entityName; - } - - public void setEntityName(String entityName) { - this.entityName = entityName; + return entity.getClass().getSimpleName(); } public String getDescription() { From 4acfb0770d4d93378840a16f9d350feec6728277 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:20:59 -0600 Subject: [PATCH 007/112] Add details for the log entry and log reply events. #2 --- .../logr/common/mqtt/model/LogEntryEvent.java | 107 +++++++++++++++++- .../common/mqtt/model/ReplyLogEntryEvent.java | 47 ++++++++ 2 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/ReplyLogEntryEvent.java diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java index 64e3d7ed7..9e3444e6f 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/LogEntryEvent.java @@ -4,17 +4,30 @@ */ package gov.anl.aps.logr.common.mqtt.model; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; -import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; +import gov.anl.aps.logr.portal.model.db.entities.EntityInfo; +import gov.anl.aps.logr.portal.model.db.entities.ItemDomainLogbook; +import gov.anl.aps.logr.portal.model.db.entities.Log; +import java.util.Date; /** * * @author djarosz */ -public class LogEntryEvent extends MqttEvent { +public class LogEntryEvent extends MqttEvent { - public LogEntryEvent(CdbEntity entity, String description) { + LogbookInfo parentLogbookInfo; + LogInfo logInfo; + + public LogEntryEvent(ItemDomainLogbook parentLogbook, Log entity, String description) { super(entity, description); + this.logInfo = new LogInfo(entity); + + if (parentLogbook != null) { + parentLogbookInfo = new LogbookInfo(parentLogbook); + } } @Override @@ -22,4 +35,92 @@ public MqttTopic getTopic() { return MqttTopic.LOGENTRY; } + public LogInfo getLogInfo() { + return logInfo; + } + + public LogbookInfo getParentLogbookInfo() { + return parentLogbookInfo; + } + + protected class LogInfo { + + Log log; + + public LogInfo(Log log) { + this.log = log; + } + + public Integer getId() { + return log.getId(); + } + + public String getEnteredByUsername() { + return log.getEnteredByUsername(); + } + + public String getLastModifiedByUsername() { + return log.getLastModifiedByUsername(); + } + + @JsonFormat(shape = JsonFormat.Shape.STRING) + public Date getEnteredOnDateTime() { + return log.getEnteredOnDateTime(); + } + + @JsonFormat(shape = JsonFormat.Shape.STRING) + public Date getLastModifiedOnDateTime() { + return log.getLastModifiedOnDateTime(); + } + } + + private class LogbookInfo { + + ItemDomainLogbook parentLogbook; + + public LogbookInfo(ItemDomainLogbook parentLogbook) { + this.parentLogbook = parentLogbook; + } + + public Integer getId() { + return parentLogbook.getId(); + } + + public String getName() { + return parentLogbook.getName(); + } + + @JsonIgnore + public EntityInfo getEntityInfo() { + return parentLogbook.getEntityInfo(); + } + + public String getCreatedByUsername() { + return getEntityInfo().getCreatedByUsername(); + } + + public String getLastModifiedByUsername() { + return getEntityInfo().getLastModifiedByUsername(); + } + + public String getOwnerUsername() { + return getEntityInfo().getOwnerUsername(); + } + + public String getOwnerUserGroupName() { + return getEntityInfo().getOwnerUserGroupName(); + } + + @JsonFormat(shape = JsonFormat.Shape.STRING) + public Date getEnteredOnDateTime() { + return getEntityInfo().getCreatedOnDateTime(); + } + + @JsonFormat(shape = JsonFormat.Shape.STRING) + public Date getLastModifiedOnDateTime() { + return getEntityInfo().getLastModifiedOnDateTime(); + } + + } + } diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/ReplyLogEntryEvent.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/ReplyLogEntryEvent.java new file mode 100644 index 000000000..7aff7b0f3 --- /dev/null +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/model/ReplyLogEntryEvent.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) UChicago Argonne, LLC. All rights reserved. + * See LICENSE file. + */ +package gov.anl.aps.logr.common.mqtt.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import gov.anl.aps.logr.common.mqtt.constants.MqttTopic; +import gov.anl.aps.logr.portal.model.db.entities.ItemDomainLogbook; +import gov.anl.aps.logr.portal.model.db.entities.Log; + +/** + * + * @author djarosz + */ +public class ReplyLogEntryEvent extends LogEntryEvent { + + LogInfo parentlogInfo; + + public ReplyLogEntryEvent(ItemDomainLogbook parentLogbook, Log entity, String description) { + super(parentLogbook, entity, description); + + Log parentLogObject = getParentLogObject(); + + if (parentLogObject != null) { + parentlogInfo = new LogInfo(parentLogObject); + } + } + + @Override + public MqttTopic getTopic() { + return MqttTopic.LOGENTRYREPLY; + } + + @JsonIgnore + public Log getParentLogObject() { + if (entity == null) { + return null; + } + return entity.getParentLog(); + } + + public LogInfo getParentlogInfo() { + return parentlogInfo; + } + +} From 55de95386eca97dee6440b8fec6baa33379674bf Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:21:43 -0600 Subject: [PATCH 008/112] Add a hint for the framework to correctly resolve the mqtt connection factory. #2 --- .../gov/anl/aps/logr/portal/utilities/SessionUtility.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java index b05acb786..00e10d9af 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/utilities/SessionUtility.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Stack; +import javax.annotation.Resource; import javax.faces.application.FacesMessage; import javax.faces.application.NavigationHandler; import javax.faces.application.ViewHandler; @@ -49,6 +50,10 @@ public class SessionUtility { private static final Logger logger = LogManager.getLogger(SessionUtility.class.getName()); + // Instructs the framework of the class to resolve for the mqtt connection factory. + @Resource(lookup = BELY_MQTT_NAME) + MQTTConnectionFactory factory; + public SessionUtility() { } @@ -323,8 +328,9 @@ public static Object findBean(String beanName) { public static MQTTConnectionFactory fetchMQTTConnectionFactory() { try { InitialContext context = new InitialContext(); + MQTTConnectionFactory result = (MQTTConnectionFactory) context.lookup(BELY_MQTT_NAME); - return (MQTTConnectionFactory) context.lookup(BELY_MQTT_NAME); + return result; } catch (NamingException ex) { logger.error(ex); } From afadac135eb0c4c06afeaf7f16cc4e915f3d3845 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:25:22 -0600 Subject: [PATCH 009/112] Handle no notificaton for system logs. #2 --- .../utilities/LogControllerUtility.java | 56 ++++++++++++------- .../logr/portal/model/db/entities/Log.java | 17 +++++- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java index f6a7b5ad0..51ae5e2e2 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java @@ -5,10 +5,12 @@ package gov.anl.aps.logr.portal.controllers.utilities; import gov.anl.aps.logr.common.exceptions.CdbException; +import gov.anl.aps.logr.common.mqtt.model.MqttEvent; import gov.anl.aps.logr.portal.constants.SystemLogLevel; import gov.anl.aps.logr.portal.model.db.beans.LogFacade; import gov.anl.aps.logr.portal.model.db.beans.LogLevelFacade; import gov.anl.aps.logr.portal.model.db.beans.UserInfoFacade; +import gov.anl.aps.logr.portal.model.db.entities.CdbEntity; import gov.anl.aps.logr.portal.model.db.entities.Log; import gov.anl.aps.logr.portal.model.db.entities.LogLevel; import gov.anl.aps.logr.portal.model.db.entities.UserInfo; @@ -19,47 +21,47 @@ * * @author darek */ -public class LogControllerUtility extends CdbEntityControllerUtility{ - +public class LogControllerUtility extends CdbEntityControllerUtility { + @EJB - private LogFacade logFacade; - + private LogFacade logFacade; + @EJB private LogLevelFacade logLevelFacade; @EJB private UserInfoFacade userInfoFacade; - + private final String DEFAULT_SYSTEM_ADMIN_USERNAME = "logr"; - + private static LogControllerUtility systemLogInstance; public LogControllerUtility() { if (logFacade == null) { - logFacade = LogFacade.getInstance(); + logFacade = LogFacade.getInstance(); } if (logLevelFacade == null) { logLevelFacade = LogLevelFacade.getInstance(); } if (userInfoFacade == null) { - userInfoFacade = UserInfoFacade.getInstance(); + userInfoFacade = UserInfoFacade.getInstance(); } } @Override - protected LogFacade getEntityDbFacade() { - return logFacade; + protected LogFacade getEntityDbFacade() { + return logFacade; } - + public static synchronized LogControllerUtility getSystemLogInstance() { if (systemLogInstance == null) { systemLogInstance = new LogControllerUtility(); } return systemLogInstance; } - + public void addSystemLog(SystemLogLevel systemlogLevel, String logMessage) throws CdbException { - String logLevelName = systemlogLevel.toString(); + String logLevelName = systemlogLevel.toString(); UserInfo enteredByUser = userInfoFacade.findByUsername(DEFAULT_SYSTEM_ADMIN_USERNAME); if (enteredByUser == null) { throw new CdbException("User '" + DEFAULT_SYSTEM_ADMIN_USERNAME + "' needs to be in the system. Please notify system administrator."); @@ -79,28 +81,29 @@ public void addSystemLog(SystemLogLevel systemlogLevel, String logMessage) throw newSystemLog.setText(logMessage); newSystemLog.setEnteredOnDateTime(enteredOnDateTime); newSystemLog.setEnteredByUser(enteredByUser); - + newSystemLog.markAsSystemLog(); + create(newSystemLog, enteredByUser); } @Override protected void prepareEntityUpdate(Log entity, UserInfo updatedByUser) throws CdbException { super.prepareEntityUpdate(entity, updatedByUser); - + Date lastModifiedOnDate = new Date(); entity.setLastModifiedOnDateTime(lastModifiedOnDate); - entity.setLastModifiedByUser(updatedByUser); + entity.setLastModifiedByUser(updatedByUser); } @Override protected void prepareEntityInsert(Log entity, UserInfo userInfo) throws CdbException { super.prepareEntityInsert(entity, userInfo); - + Date enteredOnDateTime = entity.getEnteredOnDateTime(); entity.setLastModifiedOnDateTime(enteredOnDateTime); entity.setLastModifiedByUser(userInfo); } - + protected Log createEntityInstance() { return new Log(); } @@ -110,7 +113,7 @@ protected void addCdbEntitySystemLog(SystemLogLevel logLevel, String message, Us // No need to create system logs. return; } - + @Override protected void addCreatedSystemLog(Log entity, UserInfo createdByUserInfo) { // No need to create a system log when creating a log. @@ -122,7 +125,7 @@ protected void addCreatedWarningSystemLog(Exception exception, Log entity, UserI // No need to create a system log when creating a log. return; } - + @Override public String getEntityTypeName() { return "log"; @@ -130,7 +133,18 @@ public String getEntityTypeName() { @Override public Log createEntityInstance(UserInfo sessionUser) { - return new Log(); + return new Log(); + } + + @Override + protected void publishMqttEvent(MqttEvent event) { + CdbEntity entity = event.getEntity(); + if (entity instanceof Log) { + if (((Log) entity).isSystemLog()) { + return; + } + } + super.publishMqttEvent(event); } diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java index 684f35dcc..6e799ea12 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java @@ -121,6 +121,8 @@ public class Log extends CdbEntity implements Serializable { private transient String addedReactionsString; private transient List childLogListReversed = null; + + private transient boolean isSystemLog = false; public Log() { } @@ -186,6 +188,10 @@ public void setLastModifiedOnDateTime(Date lastModifiedOnDateTime) { public UserInfo getLastModifiedByUser() { return lastModifiedByUser; } + + public String getLastModifiedByUsername() { + return lastModifiedByUser.getUsername(); + } public void setLastModifiedByUser(UserInfo lastModifiedByUser) { this.lastModifiedByUser = lastModifiedByUser; @@ -383,6 +389,15 @@ public void setAddedReactionsString(String addedReactionsString) { this.addedReactionsString = addedReactionsString; } + @JsonIgnore + public boolean isSystemLog() { + return isSystemLog; + } + + public void markAsSystemLog() { + isSystemLog = true; + } + @Override public boolean equals(Object object) { // TODO: Warning - this method won't work in the case the id fields are not set @@ -398,7 +413,7 @@ public boolean equals(Object object) { @Override public String toString() { - return "gov.anl.aps.cdb.portal.model.db.entities.Log[ id=" + id + " ]"; + return "gov.anl.aps.cdb.portal.model.db.entities.Log[ id=" + id; } } From 773f332c213bcd8c145bab7c2f72c3c60c98af2d Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:26:19 -0600 Subject: [PATCH 010/112] Allow a list of action events to be passed in for notification hints. Add specific actions for Logs. #2 --- .../portal/model/db/entities/CdbEntity.java | 21 ++++++++++++++++++- .../logr/portal/model/db/entities/Log.java | 3 ++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/CdbEntity.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/CdbEntity.java index 628a63aaa..f5af73adf 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/CdbEntity.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/CdbEntity.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import gov.anl.aps.logr.common.mqtt.model.MqttEvent; import gov.anl.aps.logr.portal.controllers.utilities.CdbEntityControllerUtility; import gov.anl.aps.logr.portal.import_export.import_.objects.ValidInfo; import gov.anl.aps.logr.portal.model.db.beans.PropertyTypeFacade; @@ -23,8 +24,9 @@ /** * Base class for all CDB entities. + * @param describes an optional additional specific mqtt event that can be specified for the entity. */ -public class CdbEntity implements Serializable, Cloneable { +public class CdbEntity implements Serializable, Cloneable { private static final Logger LOGGER = LogManager.getLogger(CdbEntity.class.getName()); @@ -44,6 +46,8 @@ public class CdbEntity implements Serializable, Cloneable { // persistence management for associated ItemConnectors, deleted on call to edit this item in facade private transient List deletedConnectorList = null; + private transient List actionEvents; + // import wizard variables private transient boolean isValidImport = true; private transient String validStringImport; @@ -311,4 +315,19 @@ public static boolean isValidCableEndDesignation(String designation) { return list.contains(designation); } + public List getActionEvents() { + return actionEvents; + } + + public void addActionEvent(ActionEvent event) { + if (actionEvents == null) { + actionEvents = new ArrayList<>(); + } + actionEvents.add(event); + } + + public void clearActionEvents() { + actionEvents = null; + } + } diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java index 6e799ea12..07be06a9b 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/entities/Log.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; +import gov.anl.aps.logr.common.mqtt.model.LogEntryEvent; import gov.anl.aps.logr.portal.utilities.MarkdownParser; import gov.anl.aps.logr.portal.view.objects.GroupedReaction; import java.io.Serializable; @@ -50,7 +51,7 @@ @NamedQuery(name = "Log.findByEnteredOnDateTime", query = "SELECT l FROM Log l WHERE l.enteredOnDateTime = :enteredOnDateTime"), @NamedQuery(name = "Log.findByEffectiveFromDateTime", query = "SELECT l FROM Log l WHERE l.effectiveFromDateTime = :effectiveFromDateTime"), @NamedQuery(name = "Log.findByEffectiveToDateTime", query = "SELECT l FROM Log l WHERE l.effectiveToDateTime = :effectiveToDateTime")}) -public class Log extends CdbEntity implements Serializable { +public class Log extends CdbEntity implements Serializable { private static final long serialVersionUID = 1L; @Id From 5bde930d054d9d8cbfc0d77474f6db9e04f44664 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:26:40 -0600 Subject: [PATCH 011/112] Resolve syntax. --- .../anl/aps/logr/portal/model/db/beans/CdbEntityFacade.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/beans/CdbEntityFacade.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/beans/CdbEntityFacade.java index dca9f86a1..449d4950c 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/beans/CdbEntityFacade.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/model/db/beans/CdbEntityFacade.java @@ -42,7 +42,8 @@ public T edit(T entity) { // delete list of connectors, if any if (entity instanceof CdbEntity) { CdbEntity cdbEntity = (CdbEntity) entity; - for (ItemConnector connector : cdbEntity.getDeletedConnectorList()) { + List deletedConnectorList = cdbEntity.getDeletedConnectorList(); + for (ItemConnector connector : deletedConnectorList) { ItemConnectorFacade.getInstance().remove(connector); } cdbEntity.clearDeletedConnectorList(); From 0974626edd4acd87f4baffd78e55423df5a68e33 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:28:51 -0600 Subject: [PATCH 012/112] Implement a new saveLog function that also gathers what has changed. #2 --- .../ItemDomainLogbookController.java | 43 ++++++++----- .../portal/controllers/LogController.java | 19 ------ .../ItemDomainLogbookControllerUtility.java | 62 ++++++++++++++++++- .../utilities/LogControllerUtility.java | 9 ++- 4 files changed, 94 insertions(+), 39 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/ItemDomainLogbookController.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/ItemDomainLogbookController.java index a02591f07..e70ca11cd 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/ItemDomainLogbookController.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/ItemDomainLogbookController.java @@ -16,6 +16,7 @@ import gov.anl.aps.logr.portal.controllers.utilities.EntityInfoControllerUtility; import gov.anl.aps.logr.portal.controllers.utilities.EntityTypeControllerUtility; import gov.anl.aps.logr.portal.controllers.utilities.ItemDomainLogbookControllerUtility; +import gov.anl.aps.logr.portal.controllers.utilities.LogControllerUtility; import gov.anl.aps.logr.portal.controllers.utilities.PropertyTypeControllerUtility; import gov.anl.aps.logr.portal.controllers.utilities.SettingTypeControllerUtility; import gov.anl.aps.logr.portal.model.ItemDomainLogbookLazyDataModel; @@ -121,7 +122,7 @@ public class ItemDomainLogbookController extends ItemController selection = new ArrayList<>(); - + for (String id : ids) { selection.add(entityTypeFacade.find(Integer.valueOf(id))); } setSearchLogbookTypeList(selection); - } + } if (itemTypeIdList != null) { String[] ids = itemTypeIdList.split(","); List selection = new ArrayList<>(); - + for (String id : ids) { selection.add(itemTypeFacade.find(Integer.valueOf(id))); } - setSearchSystemList(selection); + setSearchSystemList(selection); } if (userIdList != null) { String[] ids = userIdList.split(","); List selection = new ArrayList<>(); - + for (String id : ids) { selection.add(userInfoFacade.find(Integer.valueOf(id))); } @@ -1232,12 +1243,12 @@ public void processSearchRequestParams() { } if (modifyStart != null) { long unixTimestamp = Long.parseLong(modifyStart); - setSearchModifiedStartDate(new Date(unixTimestamp)); + setSearchModifiedStartDate(new Date(unixTimestamp)); } if (modifyEnd != null) { long unixTimestamp = Long.parseLong(modifyEnd); setSearchModifiedEndDate(new Date(unixTimestamp)); - } + } } } diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/LogController.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/LogController.java index bebae3c62..57be8efd6 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/LogController.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/LogController.java @@ -100,25 +100,6 @@ protected LogControllerUtility createControllerUtilityInstance() { return new LogControllerUtility(); } - public void saveLogEntry(Log log) { - LogControllerUtility controllerUtility1 = getControllerUtility(); - UserInfo userInfo = SessionUtility.getUser(); - - try { - if (log.getId() != null) { - controllerUtility1.update(log, userInfo); - } else { - controllerUtility1.create(log, userInfo); - } - } catch (CdbException ex) { - String persitanceErrorMessage = log.getPersitanceErrorMessage(); - SessionUtility.addErrorMessage("Error", persitanceErrorMessage); - } catch (RuntimeException ex) { - String persitanceErrorMessage = log.getPersitanceErrorMessage(); - SessionUtility.addErrorMessage("Error", persitanceErrorMessage); - } - } - /** * Converter class for log objects. */ diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java index 7dd0cd904..8f523c23c 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/ItemDomainLogbookControllerUtility.java @@ -6,6 +6,8 @@ import gov.anl.aps.logr.common.exceptions.CdbException; import gov.anl.aps.logr.common.exceptions.InvalidObjectState; +import gov.anl.aps.logr.common.mqtt.model.LogEntryEvent; +import gov.anl.aps.logr.common.mqtt.model.ReplyLogEntryEvent; import gov.anl.aps.logr.common.utilities.CollectionUtility; import gov.anl.aps.logr.portal.constants.ItemDomainName; import gov.anl.aps.logr.portal.constants.LogDocumentSettings; @@ -21,6 +23,7 @@ import gov.anl.aps.logr.portal.model.db.entities.UserInfo; import gov.anl.aps.logr.portal.utilities.AuthorizationUtility; import gov.anl.aps.logr.portal.utilities.SearchResult; +import gov.anl.aps.logr.rest.entities.LogEntry; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; @@ -337,9 +340,9 @@ public LinkedList performEntitySearch(String searchString, Map sea // Add search opts to match description. for (SearchResult result : searchResultList) { - addCommonLogEntryDocumentMatches(result, searchEntityTypeList, searchItemTypeList); - - ItemDomainLogbook resultItem = (ItemDomainLogbook) result.getCdbEntity(); + addCommonLogEntryDocumentMatches(result, searchEntityTypeList, searchItemTypeList); + + ItemDomainLogbook resultItem = (ItemDomainLogbook) result.getCdbEntity(); EntityInfo entityInfo = resultItem.getEntityInfo(); if (searchUserList != null && !searchUserList.isEmpty()) { @@ -422,4 +425,57 @@ public static void copyLogs(ItemDomainLogbook oldLogDoc, ItemDomainLogbook newLo } } + private ItemDomainLogbook getParentLogbook(Log logEntry) { + Log parentLog = logEntry.getParentLog(); + + if (parentLog != null) { + // Parent log has association to the log document. + logEntry = parentLog; + } + List itemElementList = logEntry.getItemElementList(); + + if (itemElementList != null && itemElementList.size() == 1) { + // This should always happen. + // No exception however since this is required for notification framework not core functionality. + ItemElement parentElement = itemElementList.get(0); + Item parentItem = parentElement.getParentItem(); + if (parentItem instanceof ItemDomainLogbook) { + return (ItemDomainLogbook) parentItem; + } + } + return null; + + } + + public Log saveLog(Log logEntity, UserInfo user) throws CdbException { + LogControllerUtility utility = new LogControllerUtility(); + + // Avoid duplicates + logEntity.clearActionEvents(); + + ItemDomainLogbook parentLogbook = getParentLogbook(logEntity); + + Log parentLog = logEntity.getParentLog(); + Integer id = logEntity.getId(); + + String description = ""; + if (id == null) { + description += "log entry was added"; + } else { + description += "log entry id [" + id + "] was modified"; + } + + if (parentLog != null) { + description = "reply " + description; + + // Reply + logEntity.addActionEvent(new ReplyLogEntryEvent(parentLogbook, logEntity, description)); + } else { + logEntity.addActionEvent(new LogEntryEvent(parentLogbook, logEntity, description)); + } + + // Add a generic log entry. + return utility.saveLogEntry(logEntity, user); + } + } diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java index 51ae5e2e2..9484889e4 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/LogControllerUtility.java @@ -147,5 +147,12 @@ protected void publishMqttEvent(MqttEvent event) { super.publishMqttEvent(event); } - + public Log saveLogEntry(Log log, UserInfo userInfo) throws CdbException { + if (log.getId() != null) { + return update(log, userInfo); + } else { + return create(log, userInfo); + } + } + } From 0c54c1a609a2b5dab5ba69aad2dbe70f332c7b45 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:29:43 -0600 Subject: [PATCH 013/112] Utilize the new standardized save log function that logs changes. #2 --- .../gov/anl/aps/logr/rest/routes/LogbookRoute.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java index 982b776e3..275829827 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/rest/routes/LogbookRoute.java @@ -235,7 +235,7 @@ public LogEntry addUpdateLogEntry(@RequestBody(required = true) LogEntry logEntr } logEntry.updateLogPerLogEntryObject(logEntity); - logEntity = saveLog(logEntity, user); + logEntity = utility.saveLog(logEntity, user); // Update modified date. updateModifiedDateForLogDocument(logDocument, user); @@ -401,18 +401,6 @@ private void updateModifiedDateForLogDocument(ItemDomainLogbook logDocument, Use eicu.update(entityInfo, user); } - private Log saveLog(Log log, UserInfo userInfo) throws CdbException { - LogControllerUtility utility = new LogControllerUtility(); - - if (log.getId() != null) { - log = utility.update(log, userInfo); - } else { - log = utility.create(log, userInfo); - } - - return log; - } - @Override protected void verifyUserPermissionForItem(UserInfo user, Item item) throws AuthorizationError { // Permission verification should be done at the top level document only. From 4a303bd43b0ee1a7a97efc68ce1d319b99837aa9 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:30:48 -0600 Subject: [PATCH 014/112] Add functionality for saving action events per entity. #2 --- .../utilities/CdbEntityControllerUtility.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java index 21e8c5ab3..1139da38c 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/portal/controllers/utilities/CdbEntityControllerUtility.java @@ -60,7 +60,30 @@ protected void publishMqttEvent(MqttEvent event) { MQTTConnection connection = mqttFactory.getConnection(); try { connection.publish(event.getTopic().getValue(), jsonMessage.getBytes(), 0, false); + CdbEntity entity = event.getEntity(); + + List actionEvents = entity.getActionEvents(); + _publishActionEvents(connection, actionEvents); + + connection.close(); } catch (Exception ex) { + logger.error(ex); + } + } + + private void _publishActionEvents(MQTTConnection activeConnection, List events) { + if (events == null) { + return; + } + + for (MqttEvent event : events) { + try { + String jsonMessage = event.toJson(); + activeConnection.publish(event.getTopic().getValue(), jsonMessage.getBytes(), 0, false); + } catch (Exception ex) { + logger.error(ex); + } + } } From 888af97d6a687b2f5d7afe41d024b10f16f77ff3 Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 11:31:08 -0600 Subject: [PATCH 015/112] Add a log reply topic. #2 --- .../gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java index 7ca319aae..e00c134c6 100644 --- a/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java +++ b/src/java/LogrPortal/src/java/gov/anl/aps/logr/common/mqtt/constants/MqttTopic.java @@ -12,7 +12,8 @@ public enum MqttTopic { UPDATE("bely/update"), ADD("bely/add"), DELETE("bely/delete"), - LOGENTRY("bely/logEntry"); + LOGENTRY("bely/logEntry"), + LOGENTRYREPLY("bely/logEntryReply"); private final String value; @@ -23,4 +24,9 @@ public enum MqttTopic { public String getValue() { return value; } + + @Override + public String toString() { + return super.toString() + "(" + value + ")"; + } } From ca1376a3022bfa21051b29d0619f8339ba62080f Mon Sep 17 00:00:00 2001 From: Dariusz Jarosz Date: Mon, 17 Nov 2025 12:09:13 -0600 Subject: [PATCH 016/112] :package: Add required dependencies to support the new mqtt functionality. #2 --- .../lib/jakarta.resource-api-2.1.0.jar | Bin 0 -> 65231 bytes src/java/LogrPortal/lib/mqtt-jca-api-1.0.0.jar | Bin 0 -> 22554 bytes .../LogrPortal/nbproject/project.properties | 6 +++++- src/java/LogrPortal/nbproject/project.xml | 8 ++++++++ 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/java/LogrPortal/lib/jakarta.resource-api-2.1.0.jar create mode 100644 src/java/LogrPortal/lib/mqtt-jca-api-1.0.0.jar diff --git a/src/java/LogrPortal/lib/jakarta.resource-api-2.1.0.jar b/src/java/LogrPortal/lib/jakarta.resource-api-2.1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..5a5d9089734b0a7061dc14c4afc35884cc507636 GIT binary patch literal 65231 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 z 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 fPTamP8h<7GHYH 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