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 bc445f18d73265463f8a7e11f3eacc6f4e68a1d0..abbb6ed5eac102b3215324263258e435bf41db92 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 0000000000000000000000000000000000000000..4be018482e04efc8b992724fd21a763604463ab3 --- /dev/null +++ b/src/main/java/org/etsi/osl/tmf/ri639/reposervices/ResourceFilter.java @@ -0,0 +1,43 @@ +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 9b760ac4bec8d5b2aa618273a765a9369739d803..e7ccecaf24c4b1172ee24358e05524caeb1d85b0 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; @@ -25,6 +6,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; @@ -57,6 +39,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; @@ -575,6 +558,113 @@ public class ResourceRepoService { } + /** + * 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 + 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 emptyBrowseResult(); + } + + Session session = sessionFactory.openSession(); + Transaction tx = session.beginTransaction(); + try { + boolean joinVendorChar = filter.getVendor() != null; + + 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) { + 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) + " "; + 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(); + + // 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; + int page = filter.getPage() >= 0 ? filter.getPage() : 0; + query.setFirstResult(page * pageSize); + query.setMaxResults(pageSize); + + List resources = query.list(); + + List> resourceList = 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()); + } + 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(browseResult); + } catch (Exception e) { + logger.error("Error querying resources by filter", e); + tx.rollback(); + return emptyBrowseResult(); + } finally { + session.close(); + } + } + @Transactional public Resource addOrUpdateResourceByNameCategoryVersion(String aName, String aCategory, String aVersion, ResourceCreate aesourceCreate) { @@ -606,6 +696,8 @@ public class ResourceRepoService { return result; } - + private String emptyBrowseResult() { + return "{\"resources\":[],\"totalCount\":0,\"continuationToken\":null}"; + } } diff --git a/src/main/resources/application-testing.yml b/src/main/resources/application-testing.yml index cbb0b1fd16e9240a9b37e31e02a3bb2efc7d0da1..fb35b5ce96efec3b729d23db889c27c8a8de77df 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 30bccc8b745a51fe7485383f68babe11eeaa27ae..3d1cf21d5b9da36e4624b2e44dfa0cdd1725c5ac 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"