From e4ad05d22b5414ad8aa2b3b1ab1152996fcc2770 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Mon, 8 Jun 2026 16:13:41 +0300 Subject: [PATCH 1/5] simple approach --- .../ri639/api/ResourceApiRouteBuilder.java | 9 ++ .../ri639/reposervices/ResourceFilter.java | 62 +++++++++++++ .../reposervices/ResourceRepoService.java | 92 +++++++++++++++++++ src/main/resources/application-testing.yml | 1 + src/main/resources/application.yml | 1 + 5 files changed, 165 insertions(+) create mode 100644 src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java diff --git a/src/main/java/org/etsi/osl/tmf/ri639/api/ResourceApiRouteBuilder.java b/src/main/java/org/etsi/osl/tmf/ri639/api/ResourceApiRouteBuilder.java index bc445f1..abbb6ed 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/api/ResourceApiRouteBuilder.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/api/ResourceApiRouteBuilder.java @@ -62,6 +62,9 @@ public class ResourceApiRouteBuilder extends RouteBuilder { @Value("${CATALOG_RESOURCES_OF_PARTNERS}") private String CATALOG_RESOURCES_OF_PARTNERS = ""; + + @Value("${CATALOG_GET_RESOURCES_BY_FILTER}") + private String CATALOG_GET_RESOURCES_BY_FILTER = ""; @Autowired @@ -110,6 +113,12 @@ public class ResourceApiRouteBuilder extends RouteBuilder { .bean( resourceRepoService, "addOrUpdateResourceByNameCategoryVersion(${header.aname},${header.acategory}, ${header.aversion}, ${body})") .marshal().json( JsonLibrary.Jackson) .convertBodyTo( String.class ); + + from( CATALOG_GET_RESOURCES_BY_FILTER ) + .log(LoggingLevel.DEBUG, log, CATALOG_GET_RESOURCES_BY_FILTER + " message received!") + .to("log:DEBUG?showBody=true&showHeaders=true") + .convertBodyTo( String.class ) + .bean( resourceRepoService, "findResourcesByFilter(${body})"); } diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java new file mode 100644 index 0000000..6e94fbb --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java @@ -0,0 +1,62 @@ +/*- + * ========================LICENSE_START================================= + * org.etsi.osl.tmf.api + * %% + * Copyright (C) 2019 - 2020 openslice.io + * %% + * 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 + * + * http://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. + * =========================LICENSE_END================================== + */ +package org.etsi.osl.tmf.ri639.reposervices; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * Filter DTO for querying resources via the CATALOG_GET_RESOURCES_BY_FILTER JMS route. + * Example: {"specId":null,"namePattern":null,"vendor":null,"location":null,"status":"AVAILABLE","page":0,"pageSize":20} + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResourceFilter { + + private String specId; + private String namePattern; + /** Matched against a resource characteristic named "vendor" */ + private String vendor; + /** Matched against place.name */ + private String location; + /** Matched against resourceStatus enum value (e.g. "AVAILABLE") */ + private String status; + private int page = 0; + private int pageSize = 20; + + public String getSpecId() { return specId; } + public void setSpecId(String specId) { this.specId = specId; } + + public String getNamePattern() { return namePattern; } + public void setNamePattern(String namePattern) { this.namePattern = namePattern; } + + public String getVendor() { return vendor; } + public void setVendor(String vendor) { this.vendor = vendor; } + + public String getLocation() { return location; } + public void setLocation(String location) { this.location = location; } + + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + + public int getPage() { return page; } + public void setPage(int page) { this.page = page; } + + public int getPageSize() { return pageSize; } + public void setPageSize(int pageSize) { this.pageSize = pageSize; } +} diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java index 9b760ac..b0b1ab0 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -575,6 +576,97 @@ public class ResourceRepoService { } + /** + * Find resources matching the given filter, returned as a JSON string. + * Filter fields: specId, namePattern, vendor (characteristic), location (place.name), status, page, pageSize. + */ + @Transactional + public String findResourcesByFilter(String filterJson) throws JsonProcessingException { + ResourceFilter filter; + try { + filter = objectMapper.readValue(filterJson, ResourceFilter.class); + } catch (Exception e) { + logger.error("Cannot parse ResourceFilter JSON: " + filterJson, e); + return "[]"; + } + + Session session = sessionFactory.openSession(); + Transaction tx = session.beginTransaction(); + try { + StringBuilder hql = new StringBuilder("SELECT DISTINCT srv FROM RIResource srv "); + + boolean joinVendorChar = filter.getVendor() != null; + if (joinVendorChar) { + hql.append("JOIN srv.resourceCharacteristic vendorChar "); + } + + List conditions = new ArrayList<>(); + Map params = new HashMap<>(); + + if (filter.getSpecId() != null) { + conditions.add("srv.resourceSpecification.uuid = :specId"); + params.put("specId", filter.getSpecId()); + } + if (filter.getNamePattern() != null) { + conditions.add("srv.name LIKE :namePattern"); + params.put("namePattern", "%" + filter.getNamePattern() + "%"); + } + if (joinVendorChar) { + conditions.add("vendorChar.name = 'vendor' AND vendorChar.value.value = :vendor"); + params.put("vendor", filter.getVendor()); + } + if (filter.getLocation() != null) { + conditions.add("srv.place.name LIKE :location"); + params.put("location", "%" + filter.getLocation() + "%"); + } + if (filter.getStatus() != null) { + conditions.add("UPPER(srv.resourceStatus) = UPPER(:status)"); + params.put("status", filter.getStatus()); + } + + if (!conditions.isEmpty()) { + hql.append("WHERE ").append(String.join(" AND ", conditions)).append(" "); + } + hql.append("ORDER BY srv.name ASC"); + + org.hibernate.query.Query query = session.createQuery(hql.toString(), Resource.class); + params.forEach(query::setParameter); + + int pageSize = filter.getPageSize() > 0 ? filter.getPageSize() : 20; + query.setFirstResult(filter.getPage() * pageSize); + query.setMaxResults(pageSize); + + List resources = query.list(); + + List> result = new ArrayList<>(); + for (Resource r : resources) { + Hibernate.initialize(r.getResourceCharacteristic()); + Hibernate.initialize(r.getResourceSpecification()); + Map entry = new LinkedHashMap<>(); + entry.put("id", r.getUuid()); + entry.put("@type", r.getType()); + entry.put("name", r.getName()); + entry.put("category", r.getCategory()); + entry.put("description", r.getDescription()); + entry.put("resourceStatus", r.getResourceStatus() != null ? r.getResourceStatus().toString() : null); + if (r.getResourceSpecification() != null) { + entry.put("resourceSpecificationId", r.getResourceSpecification().getUuid()); + entry.put("resourceSpecificationName", r.getResourceSpecification().getName()); + } + result.add(entry); + } + + tx.commit(); + return objectMapper.writeValueAsString(result); + } catch (Exception e) { + logger.error("Error querying resources by filter", e); + tx.rollback(); + return "[]"; + } finally { + session.close(); + } + } + @Transactional public Resource addOrUpdateResourceByNameCategoryVersion(String aName, String aCategory, String aVersion, ResourceCreate aesourceCreate) { diff --git a/src/main/resources/application-testing.yml b/src/main/resources/application-testing.yml index cbb0b1f..fb35b5c 100644 --- a/src/main/resources/application-testing.yml +++ b/src/main/resources/application-testing.yml @@ -244,6 +244,7 @@ EVENT_RESOURCE_STATE_CHANGED: "jms:topic:EVENT.RESOURCE.STATECHANGED" EVENT_RESOURCE_DELETE: "jms:topic:EVENT.SERVICE.RESOURCE" EVENT_RESOURCE_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.RESOURCE.ATTRCHANGED" CATALOG_RESOURCES_OF_PARTNERS: "jms:queue:CATALOG.GET.SERVICESOFPARTNERS" +CATALOG_GET_RESOURCES_BY_FILTER: "jms:queue:CATALOG.GET.RESOURCES_BY_FILTER" #RESOURCE_ACTIVATION CATALOG_ADD_RESOURCEACTIVATION: "jms:queue:CATALOG.ADD.RESOURCEACTIVATION" diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 30bccc8..3d1cf21 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -254,6 +254,7 @@ EVENT_RESOURCE_STATE_CHANGED: "jms:topic:EVENT.RESOURCE.STATECHANGED" EVENT_RESOURCE_DELETE: "jms:topic:EVENT.SERVICE.RESOURCE" EVENT_RESOURCE_ATTRIBUTE_VALUE_CHANGED: "jms:topic:EVENT.RESOURCE.ATTRCHANGED" CATALOG_RESOURCES_OF_PARTNERS: "jms:queue:CATALOG.GET.SERVICESOFPARTNERS" +CATALOG_GET_RESOURCES_BY_FILTER: "jms:queue:CATALOG.GET.RESOURCES_BY_FILTER" #RESOURCE_ACTIVATION CATALOG_ADD_RESOURCEACTIVATION: "jms:queue:CATALOG.ADD.RESOURCEACTIVATION" -- GitLab From a00ebec1b5a36c6e6b4767ec34e2a73dca2f2cfb Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Mon, 8 Jun 2026 16:41:56 +0300 Subject: [PATCH 2/5] filter methid return map object for paging --- .../reposervices/ResourceRepoService.java | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java index b0b1ab0..4a8cdad 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java @@ -577,7 +577,8 @@ public class ResourceRepoService { /** - * Find resources matching the given filter, returned as a JSON string. + * Find resources matching the given filter. + * Returns JSON: {"resources":[...],"totalCount":N,"continuationToken":"" or null} * Filter fields: specId, namePattern, vendor (characteristic), location (place.name), status, page, pageSize. */ @Transactional @@ -587,18 +588,13 @@ public class ResourceRepoService { filter = objectMapper.readValue(filterJson, ResourceFilter.class); } catch (Exception e) { logger.error("Cannot parse ResourceFilter JSON: " + filterJson, e); - return "[]"; + return emptyBrowseResult(); } Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); try { - StringBuilder hql = new StringBuilder("SELECT DISTINCT srv FROM RIResource srv "); - boolean joinVendorChar = filter.getVendor() != null; - if (joinVendorChar) { - hql.append("JOIN srv.resourceCharacteristic vendorChar "); - } List conditions = new ArrayList<>(); Map params = new HashMap<>(); @@ -624,21 +620,28 @@ public class ResourceRepoService { params.put("status", filter.getStatus()); } - if (!conditions.isEmpty()) { - hql.append("WHERE ").append(String.join(" AND ", conditions)).append(" "); - } - hql.append("ORDER BY srv.name ASC"); + String whereClause = conditions.isEmpty() ? "" : "WHERE " + String.join(" AND ", conditions) + " "; + String joinClause = joinVendorChar ? "JOIN srv.resourceCharacteristic vendorChar " : ""; + + // COUNT query for totalCount + String countHql = "SELECT COUNT(DISTINCT srv) FROM RIResource srv " + joinClause + whereClause; + org.hibernate.query.Query countQuery = session.createQuery(countHql, Long.class); + params.forEach(countQuery::setParameter); + long totalCount = countQuery.uniqueResult(); - org.hibernate.query.Query query = session.createQuery(hql.toString(), Resource.class); + // Data query with pagination + String dataHql = "SELECT DISTINCT srv FROM RIResource srv " + joinClause + whereClause + "ORDER BY srv.name ASC"; + org.hibernate.query.Query query = session.createQuery(dataHql, Resource.class); params.forEach(query::setParameter); int pageSize = filter.getPageSize() > 0 ? filter.getPageSize() : 20; - query.setFirstResult(filter.getPage() * pageSize); + int page = filter.getPage() >= 0 ? filter.getPage() : 0; + query.setFirstResult(page * pageSize); query.setMaxResults(pageSize); List resources = query.list(); - List> result = new ArrayList<>(); + List> resourceList = new ArrayList<>(); for (Resource r : resources) { Hibernate.initialize(r.getResourceCharacteristic()); Hibernate.initialize(r.getResourceSpecification()); @@ -653,15 +656,23 @@ public class ResourceRepoService { entry.put("resourceSpecificationId", r.getResourceSpecification().getUuid()); entry.put("resourceSpecificationName", r.getResourceSpecification().getName()); } - result.add(entry); + resourceList.add(entry); } + boolean hasMore = (long) (page + 1) * pageSize < totalCount; + String continuationToken = hasMore ? String.valueOf(page + 1) : null; + + Map browseResult = new LinkedHashMap<>(); + browseResult.put("resources", resourceList); + browseResult.put("totalCount", totalCount); + browseResult.put("continuationToken", continuationToken); + tx.commit(); - return objectMapper.writeValueAsString(result); + return objectMapper.writeValueAsString(browseResult); } catch (Exception e) { logger.error("Error querying resources by filter", e); tx.rollback(); - return "[]"; + return emptyBrowseResult(); } finally { session.close(); } @@ -698,6 +709,8 @@ public class ResourceRepoService { return result; } - + private String emptyBrowseResult() { + return "{\"resources\":[],\"totalCount\":0,\"continuationToken\":null}"; + } } -- GitLab From 0d05009a108a4fdd214d962126d2a85b2b1ad038 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Mon, 8 Jun 2026 23:11:13 +0300 Subject: [PATCH 3/5] fix filter status --- .../tmf/ri639/reposervices/ResourceRepoService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java index 4a8cdad..bb5062c 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java @@ -58,6 +58,7 @@ import org.etsi.osl.tmf.ri639.model.ResourceCreateNotification; import org.etsi.osl.tmf.ri639.model.ResourceRelationship; import org.etsi.osl.tmf.ri639.model.ResourceStateChangeEvent; import org.etsi.osl.tmf.ri639.model.ResourceStateChangeNotification; +import org.etsi.osl.tmf.ri639.model.ResourceStatusType; import org.etsi.osl.tmf.ri639.model.ResourceUpdate; import org.etsi.osl.tmf.ri639.repo.ResourceRepository; import org.etsi.osl.tmf.sim638.model.Service; @@ -616,8 +617,13 @@ public class ResourceRepoService { params.put("location", "%" + filter.getLocation() + "%"); } if (filter.getStatus() != null) { - conditions.add("UPPER(srv.resourceStatus) = UPPER(:status)"); - params.put("status", filter.getStatus()); + ResourceStatusType statusEnum = ResourceStatusType.fromValue(filter.getStatus().toLowerCase()); + if (statusEnum != null) { + conditions.add("srv.resourceStatus = :status"); + params.put("status", statusEnum); + } else { + logger.warn("Unknown resourceStatus filter value: " + filter.getStatus()); + } } String whereClause = conditions.isEmpty() ? "" : "WHERE " + String.join(" AND ", conditions) + " "; -- GitLab From a44aaecff3f5ce9de4d4f7948594faf2f34082a6 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Mon, 15 Jun 2026 23:46:58 +0300 Subject: [PATCH 4/5] remove license --- .../ri639/reposervices/ResourceFilter.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java index 6e94fbb..4be0184 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java @@ -1,22 +1,3 @@ -/*- - * ========================LICENSE_START================================= - * org.etsi.osl.tmf.api - * %% - * Copyright (C) 2019 - 2020 openslice.io - * %% - * 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 - * - * http://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. - * =========================LICENSE_END================================== - */ package org.etsi.osl.tmf.ri639.reposervices; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -- GitLab From 762f06e148240f80d812147c17c856ef7abd7394 Mon Sep 17 00:00:00 2001 From: Christos Tranoris Date: Mon, 15 Jun 2026 23:47:45 +0300 Subject: [PATCH 5/5] remove license --- .../reposervices/ResourceRepoService.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java index bb5062c..e7cceca 100644 --- a/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceRepoService.java @@ -1,22 +1,3 @@ -/*- - * ========================LICENSE_START================================= - * org.etsi.osl.tmf.api - * %% - * Copyright (C) 2019 openslice.io - * %% - * 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 - * - * http://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. - * =========================LICENSE_END================================== - */ package org.etsi.osl.tmf.ri639.reposervices; import java.io.UnsupportedEncodingException; -- GitLab