From bc6378d695446860a9387f9aa62080d70f5f3161 Mon Sep 17 00:00:00 2001
From: Lars Johansson <lars.johansson@ess.eu>
Date: Wed, 29 Mar 2023 11:37:38 +0200
Subject: [PATCH] Add endpoint to get legacy names

---
 .../names/repository/NameRepository.java      | 130 ++++++++++++++++++
 .../openepics/names/rest/api/v1/INames.java   |  43 ++++++
 .../rest/controller/NamesController.java      |  18 +++
 .../openepics/names/service/NamesService.java |  29 ++++
 4 files changed, 220 insertions(+)

diff --git a/src/main/java/org/openepics/names/repository/NameRepository.java b/src/main/java/org/openepics/names/repository/NameRepository.java
index 14f1eb81..b949d580 100644
--- a/src/main/java/org/openepics/names/repository/NameRepository.java
+++ b/src/main/java/org/openepics/names/repository/NameRepository.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import javax.persistence.EntityManager;
 import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
 import javax.persistence.TypedQuery;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
@@ -240,6 +241,135 @@ public class NameRepository {
         return query.getResultList();
     }
 
+    /**
+     * Count legacy names
+     *
+     * @param name name
+     * @return count of legacy names
+     */
+    public Long countNamesLegacy(String name) {
+        // a name is considered legacy if it is active but refers to a parent that is deleted
+        // query
+        //     logical but non-trivial
+        //     relies on lifecycle and parent-child attributes
+        //     look for one or more deleted parents anywhere in system structure or device structure hierarchies
+        StringBuilder sql = new StringBuilder();
+        sql.append("select count(n) from Name n where n.latest = true and n.deleted = false ");
+        sql.append("and (");
+        sql.append("       (n.systemGroupUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true))");
+        sql.append("    or (n.systemUuid      in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true and sy.deleted = true))");
+        sql.append("    or (n.systemUuid      in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true                         and sy.parentUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true)))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true and su.deleted = true))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true                         and su.parentUuid in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true and sy.deleted = true)))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true                         and su.parentUuid in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true                          and sy.parentUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true))))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true and dt.deleted = true))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true                         and dt.parentUuid in (select dg.uuid from DeviceGroup dg where dg.status = 'APPROVED' and dg.latest = true and dg.deleted = true)))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true                         and dt.parentUuid in (select dg.uuid from DeviceGroup dg where dg.status = 'APPROVED' and dg.latest = true                          and dg.parentUuid in (select di.uuid from Discipline  di where di.status = 'APPROVED' and di.latest = true and di.deleted = true))))");
+        sql.append(")");
+
+        boolean hasWhere = StringUtils.isNotEmpty(name);
+        Query query = null;
+        if (hasWhere) {
+            sql.append(" and n.conventionName like :pName");
+            query = em.createQuery(sql.toString(), Long.class).setParameter("pName", name);
+        } else {
+            query = em.createQuery(sql.toString(), Long.class);
+        }
+
+        return (Long) query.getSingleResult();
+    }
+
+    /**
+     * Find legacy names.
+     *
+     * @param name name
+    * @param orderBy order by
+    * @param isAsc is ascending
+    * @param offset offset
+    * @param limit limit
+    * @return list of names
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public List<Name> readNamesLegacy(String name,
+            FieldName orderBy, Boolean isAsc, Integer offset, Integer limit) {
+
+        // a name is considered legacy if it is active but refers to a parent that is deleted
+        // query
+        //     logical but non-trivial
+        //     relies on lifecycle and parent-child attributes
+        //     look for one or more deleted parents anywhere in system structure or device structure hierarchies
+        StringBuilder sql = new StringBuilder();
+        sql.append("select n from Name n where n.latest = true and n.deleted = false ");
+        sql.append("and (");
+        sql.append("       (n.systemGroupUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true))");
+        sql.append("    or (n.systemUuid      in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true and sy.deleted = true))");
+        sql.append("    or (n.systemUuid      in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true                         and sy.parentUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true)))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true and su.deleted = true))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true                         and su.parentUuid in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true and sy.deleted = true)))");
+        sql.append("    or (n.subsystemUuid   in (select su.uuid from Subsystem   su where su.status = 'APPROVED' and su.latest = true                         and su.parentUuid in (select sy.uuid from System      sy where sy.status = 'APPROVED' and sy.latest = true                          and sy.parentUuid in (select sg.uuid from SystemGroup sg where sg.status = 'APPROVED' and sg.latest = true and sg.deleted = true))))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true and dt.deleted = true))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true                         and dt.parentUuid in (select dg.uuid from DeviceGroup dg where dg.status = 'APPROVED' and dg.latest = true and dg.deleted = true)))");
+        sql.append("    or (n.deviceTypeUuid  in (select dt.uuid from DeviceType  dt where dt.status = 'APPROVED' and dt.latest = true                         and dt.parentUuid in (select dg.uuid from DeviceGroup dg where dg.status = 'APPROVED' and dg.latest = true                          and dg.parentUuid in (select di.uuid from Discipline  di where di.status = 'APPROVED' and di.latest = true and di.deleted = true))))");
+        sql.append(")");
+
+        StringBuilder sqlOrderBy = new StringBuilder();
+        if (orderBy != null) {
+            sqlOrderBy.append(" order by ");
+
+            if (FieldName.NAMEEQUIVALENCE.equals(orderBy)) {
+                sqlOrderBy.append("n.");
+                sqlOrderBy.append(Name.FIELD_CONVENTION_NAME_EQUIVALENCE);
+            } else if (FieldName.SYSTEMSTRUCTURE.equals(orderBy)) {
+                sqlOrderBy.append(NameStructure.FUNCTION_GET_MNEMONIC_PATH_SYSTEM_STRUCTURE);
+                sqlOrderBy.append("(n.conventionName)");
+            } else if (FieldName.DEVICESTRUCTURE.equals(orderBy)) {
+                sqlOrderBy.append(NameStructure.FUNCTION_GET_MNEMONIC_PATH_DEVICE_STRUCTURE);
+                sqlOrderBy.append("(n.conventionName)");
+            } else if (FieldName.INDEX.equals(orderBy)) {
+                sqlOrderBy.append(NameStructure.FUNCTION_GET_INSTANCE_INDEX);
+                sqlOrderBy.append("(n.conventionName)");
+            } else if (FieldName.DESCRIPTION.equals(orderBy)) {
+                sqlOrderBy.append("n.");
+                sqlOrderBy.append(Name.FIELD_DESCRIPTION);
+            } else if (FieldName.WHEN.equals(orderBy)) {
+                sqlOrderBy.append("n.");
+                sqlOrderBy.append(Name.FIELD_REQUESTED);
+            } else {
+                sqlOrderBy.append("n.");
+                sqlOrderBy.append(Name.FIELD_CONVENTION_NAME);
+            }
+
+            if (BooleanUtils.toBoolean(isAsc)) {
+                sqlOrderBy.append(" asc");
+            } else {
+                sqlOrderBy.append(" desc");
+            }
+        }
+
+        boolean hasWhere = StringUtils.isNotEmpty(name);
+        boolean hasOrderBy = orderBy != null;
+        Query query = null;
+        if (hasWhere) {
+            sql.append(" and n.conventionName like :pName");
+            if (hasOrderBy) {
+                sql.append(sqlOrderBy.toString());
+            }
+            query = em.createQuery(sql.toString(), Name.class).setParameter("pName", name);
+        } else {
+            if (hasOrderBy) {
+                sql.append(sqlOrderBy.toString());
+            }
+            query = em.createQuery(sql.toString(), Name.class);
+        }
+
+        if (offset != null && limit != null) {
+            query.setFirstResult(offset * limit);
+            query.setMaxResults(limit);
+        }
+        return query.getResultList();
+    }
+
     /**
      * Prepare predicates for names.
      *
diff --git a/src/main/java/org/openepics/names/rest/api/v1/INames.java b/src/main/java/org/openepics/names/rest/api/v1/INames.java
index 8742dc74..a4f0afad 100644
--- a/src/main/java/org/openepics/names/rest/api/v1/INames.java
+++ b/src/main/java/org/openepics/names/rest/api/v1/INames.java
@@ -636,6 +636,49 @@ public interface INames {
             @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page,
             @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize);
 
+    @Operation(
+            summary     = "Find valid legacy names (search)",
+            description = """
+                          Find valid legacy names (search).
+                          Return paged array of name elements.
+                          """
+    )
+    @ApiResponses(value = {
+            @ApiResponse(
+                    responseCode = "200",
+                    description  = "OK. Return paged array of name elements.",
+                    content = @Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = ResponsePageNameElements.class))),
+            @ApiResponse(
+                    responseCode = "400",
+                    description  = "Bad request. Reason and information such as message, details, field are available.",
+                    content = @Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = Response.class))),
+            @ApiResponse(
+                    responseCode = "422",
+                    description  = "Unprocessable entity. Reason and information such as message, details, field are available.",
+                    content = @Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = Response.class))),
+            @ApiResponse(
+                    responseCode = "500",
+                    description  = "Internal server error. Reason and information such as message, details, field are available.",
+                    content = @Content(
+                            mediaType = "application/json",
+                            schema = @Schema(implementation = Response.class)))
+    })
+    @GetMapping(
+            value = "/legacy",
+            produces = {"application/json"})
+    public ResponsePageNameElements readNamesLegacy(
+            @Parameter(in = ParameterIn.QUERY, description = "search by name") @RequestParam(required = false) String name,
+            @Parameter(in = ParameterIn.QUERY, description = "order by field") @RequestParam(required = false, defaultValue = DEFAULT_SORT_FIELD_WHEN) FieldName orderBy,
+            @Parameter(in = ParameterIn.QUERY, description = "sort order, ascending or descending") @RequestParam(required = false, defaultValue = DEFAULT_SORT_ORDER_ASC) Boolean isAsc,
+            @Parameter(in = ParameterIn.QUERY, description = "page starting from 0, offset") @RequestParam(required = false, defaultValue = DEFAULT_PAGE) Integer page,
+            @Parameter(in = ParameterIn.QUERY, description = "page size, limit") @RequestParam(required = false, defaultValue = DEFAULT_PAGE_SIZE) Integer pageSize);
+
     // ----------------------------------------------------------------------------------------------------
 
     /**
diff --git a/src/main/java/org/openepics/names/rest/controller/NamesController.java b/src/main/java/org/openepics/names/rest/controller/NamesController.java
index 9b1cc613..784f2c98 100644
--- a/src/main/java/org/openepics/names/rest/controller/NamesController.java
+++ b/src/main/java/org/openepics/names/rest/controller/NamesController.java
@@ -274,6 +274,24 @@ public class NamesController implements INames {
         }
     }
 
+    @Override
+    public ResponsePageNameElements readNamesLegacy(String name,
+            FieldName orderBy, Boolean isAsc, Integer page, Integer pageSize) {
+        // not validate
+        // read names
+        try {
+            return namesService.readNamesLegacy(name,
+                    orderBy, isAsc, page, pageSize);
+        } catch (ServiceException e) {
+            logService.logServiceException(LOGGER, Level.WARNING, e);
+            logService.logStackTraceElements(LOGGER, Level.WARNING, e);
+            throw e;
+        } catch (Exception e) {
+            logService.logStackTraceElements(LOGGER, Level.WARNING, e);
+            throw e;
+        }
+    }
+
     // ----------------------------------------------------------------------------------------------------
 
     @Override
diff --git a/src/main/java/org/openepics/names/service/NamesService.java b/src/main/java/org/openepics/names/service/NamesService.java
index 6a6751f3..e69af587 100644
--- a/src/main/java/org/openepics/names/service/NamesService.java
+++ b/src/main/java/org/openepics/names/service/NamesService.java
@@ -336,6 +336,35 @@ public class NamesService {
                 Boolean.TRUE, orderBy, isAsc, offset, limit);
     }
 
+    public ResponsePageNameElements readNamesLegacy(String name,
+            FieldName orderBy, Boolean isAsc, Integer offset, Integer limit) {
+        // validation outside method
+        // read legacy names
+        // return name elements for names
+
+        if (LOGGER.isLoggable(Level.FINE)) {
+            LOGGER.log(Level.FINE, MessageFormat.format(TextUtil.DESCRIPTION_NAME_VALUE, TextUtil.READ_NAMES, "name", name));
+            LOGGER.log(Level.FINE, MessageFormat.format(TextUtil.DESCRIPTION_NAME_VALUE, TextUtil.READ_NAMES, "orderBy", orderBy));
+            LOGGER.log(Level.FINE, MessageFormat.format(TextUtil.DESCRIPTION_NAME_VALUE, TextUtil.READ_NAMES, "isAsc", isAsc));
+            LOGGER.log(Level.FINE, MessageFormat.format(TextUtil.DESCRIPTION_NAME_VALUE, TextUtil.READ_NAMES, "offset", offset));
+            LOGGER.log(Level.FINE, MessageFormat.format(TextUtil.DESCRIPTION_NAME_VALUE, TextUtil.READ_NAMES, "limit", limit));
+        }
+
+        List<Name> names = nameRepository.readNamesLegacy(name, orderBy, isAsc, offset, limit);
+        Long totalCount = nameRepository.countNamesLegacy(name);
+
+        final List<NameElement> nameElements = NameElementUtil.getNameElements(names);
+
+        ResponsePageNameElements response = new ResponsePageNameElements(nameElements, totalCount, nameElements.size(), offset, limit);
+        LOGGER.log(Level.INFO,
+                () -> MessageFormat.format(
+                        TextUtil.DESCRIPTION_NUMBER_ELEMENTS,
+                        TextUtil.READ_NAMES,
+                        nameElements.size()));
+        return response;
+
+    }
+
     // ----------------------------------------------------------------------------------------------------
 
     public String equivalenceName(String name) {
-- 
GitLab