From 2f2ff3364fb2f0e9ad36da736c1dfcaf334a54f1 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Tue, 17 Dec 2024 09:41:43 +0530 Subject: [PATCH 1/7] enclosure addition in feature collection links --- .../ogc/rs/apiserver/ApiServerVerticle.java | 19 +++++ .../ogc/rs/database/DatabaseServiceImpl.java | 75 ++++++++++++++++--- .../V17__add_collections_enclosure_table.sql | 18 +++++ 3 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/db/migration/V17__add_collections_enclosure_table.sql diff --git a/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java b/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java index c587e086..80003072 100644 --- a/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java +++ b/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java @@ -897,6 +897,25 @@ private JsonObject buildCollectionFeatureResult(List success) { .put("templated","true"))) .put("itemType", "feature") .put("crs", collection.getJsonArray("crs")); + if (collection.containsKey("enclosure")) { + collection.getJsonArray("enclosure") + .forEach(enclosureJson -> { + JsonObject enclosure = new JsonObject(); + enclosure.mergeIn((JsonObject) enclosureJson); + String href = + hostName + ogcBasePath + "assets/" + enclosure.getString("id"); + enclosure.put("href", href); + enclosure.put("rel", "enclosure"); + enclosure.put("length", enclosure.getInteger("size")); + enclosure.remove("size"); + enclosure.remove("id"); + enclosure.remove("collections_id"); + // enclosure.remove("role"); + collection.getJsonArray("links").add(enclosure); + }); + collection.remove("enclosure"); + + } if (success.get(0).getJsonArray("type").contains("COVERAGE")) { collection .getJsonArray("links") diff --git a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java index 0bcbcc3a..b7d7ea07 100644 --- a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java +++ b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java @@ -41,6 +41,7 @@ public Future> getCollection(String collectionId) { LOGGER.info("getCollection"); Promise> result = Promise.promise(); Collector> collector = Collectors.mapping(Row::toJson, Collectors.toList()); + Collector> enclosureCollector = Collectors.mapping(Row::toJson, Collectors.toList()); client.withConnection(conn -> conn.preparedQuery("select collections_details.id, title, array_agg(distinct crs_to_srid.crs) as crs" + ", collections_details.crs as \"storageCrs\", description, datetime_key, bbox, temporal," + @@ -48,18 +49,35 @@ public Future> getCollection(String collectionId) { " from collections_details join collection_supported_crs" + " on collections_details.id = collection_supported_crs.collection_id join crs_to_srid" + " on crs_to_srid.id = collection_supported_crs.crs_id join collection_type" + - " on collections_details.id=collection_type.collection_id group by collections_details.id" + + " on collections_details.id=collection_type.collection_id where collection_type.type != 'STAC' group by collections_details.id" + " having collections_details.id = $1::uuid") .collecting(collector) - .execute(Tuple.of(UUID.fromString( collectionId))).map(SqlResult::value)) - .onSuccess(success -> { - LOGGER.debug("Built OGC Collection Response - {}", success); - result.complete(success); + .execute(Tuple.of(UUID.fromString( collectionId))).map(SqlResult::value) + .onSuccess(success -> { + String query = + "SELECT * from collections_enclosure where collections_id = $1::uuid"; + conn.preparedQuery(query) + .collecting(enclosureCollector) + .execute(Tuple.of(UUID.fromString(collectionId))) + .map(SqlResult::value) + .onSuccess( + enclosureResult-> { + if (!enclosureResult.isEmpty()) { + success.get(0).put("enclosure", enclosureResult); + } + result.complete(success); + }) + .onFailure( + failed -> { + LOGGER.error("Failed at getFeature- {}", failed.getMessage()); + result.fail("Error!"); + }); + }) .onFailure(fail -> { LOGGER.error("Failed at getCollection- {}",fail.getMessage()); result.fail("Error!"); - }); + })); return result.future(); } @@ -74,18 +92,53 @@ public Future> getCollections() { " from collections_details join collection_supported_crs" + " on collections_details.id = collection_supported_crs.collection_id join crs_to_srid" + " on crs_to_srid.id = collection_supported_crs.crs_id join collection_type" + - " on collections_details.id = collection_type.collection_id group by collections_details.id") + " on collections_details.id = collection_type.collection_id where collection_type.type!= 'STAC' group by collections_details.id") .collecting(collector) .execute() - .map(SqlResult::value)) + .map(SqlResult::value) .onSuccess(success -> { - LOGGER.debug("Collections Result: {}", success.toString()); - result.complete(success); + if (success.isEmpty()) { + LOGGER.error("Collections table is empty!"); + result.fail( + new OgcException(404, "Not found", "Collection table is Empty!")); + } else { + conn.preparedQuery("SELECT * FROM COLLECTIONS_ENCLOSURE") + .collecting(collector) + .execute() + .map(SqlResult::value) + .onSuccess( + enclosureResult -> { + if (enclosureResult.isEmpty()) { + LOGGER.warn("Assets table is empty!"); + result.complete(success); + } else { + for (JsonObject enclosure : enclosureResult) { + for (JsonObject successItem : success) { + if (successItem + .getString("id") + .equals(enclosure.getString("collections_id"))) { + if (successItem.containsKey("enclosure")) { + successItem.getJsonArray("enclosure").add(enclosure); + } else { + successItem.put("enclosure", new JsonArray().add(enclosure)); + } + } + } + } + result.complete(success); + } + }) + .onFailure( + fail -> { + LOGGER.error("Failed to get enclosure links! - {}", fail.getMessage()); + result.fail("Error!"); + }); + } }) .onFailure(fail -> { LOGGER.error("Failed to getCollections! - {}", fail.getMessage()); result.fail("Error!"); - }); + })); return result.future(); } @Override diff --git a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql new file mode 100644 index 00000000..b209cdb5 --- /dev/null +++ b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql @@ -0,0 +1,18 @@ +CREATE TABLE collections_enclosure ( + id UUID PRIMARY KEY DEFAULT public.gen_random_uuid(), + collections_id UUID REFERENCES collections_details(id) NOT NULL, + title VARCHAR(255) NOT NULL, + href VARCHAR(255) NOT NULL, + type VARCHAR(50) NOT NULL, + size BIGINT NOT NULL, + CONSTRAINT unique_enclosure UNIQUE (collections_id, title) +); + +INSERT INTO collections_enclosure (id, collections_id, title, href, type, size) +SELECT id, stac_collections_id, title, href, type, size +FROM stac_collections_assets; + +CREATE INDEX idx_collections_enclosure_collections_id +ON collections_enclosure (collections_id); + +GRANT SELECT, INSERT, DELETE ON collections_enclosure TO ${ogcUser} From ad6efc9cd6ef5fac07eed118caf2f4e1e278d3b8 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Tue, 17 Dec 2024 10:02:05 +0530 Subject: [PATCH 2/7] stac query updation for items of type STAC --- .../java/ogc/rs/database/DatabaseServiceImpl.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java index b7d7ea07..463a7a71 100644 --- a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java +++ b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java @@ -331,7 +331,11 @@ public Future> getStacCollections() { client.withConnection( conn -> conn.preparedQuery( - "Select id, title, description, bbox, temporal,license from collections_details") + "SELECT collections_details.id, title, description," + + " bbox, temporal, license FROM collections_details JOIN collection_type " + + "ON collections_details.id = collection_type.collection_id WHERE " + + "collection_type.type = 'STAC' GROUP BY collections_details.id, title, " + + "description, bbox, temporal, license") .collecting(collector) .execute() .map(SqlResult::value) @@ -421,7 +425,11 @@ public Future getStacCollection(String collectionId) { client.withConnection( conn -> conn.preparedQuery( - "SELECT id, title, description, bbox, temporal, license FROM collections_details where id = $1::uuid") + "SELECT collections_details.id, title, " + + "description, bbox, temporal, license FROM collections_details " + + "JOIN collection_type ON collections_details.id = collection_type.collection_id " + + "WHERE collection_type.type = 'STAC' AND collections_details.id = $1::uuid GROUP " + + "BY collections_details.id, title, description, bbox, temporal, license") .collecting(collector) .execute(Tuple.of(UUID.fromString(collectionId))) .map(SqlResult::value) From bdc1769ea798c57fffbfccf4243b4ba0c207e46b Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Tue, 17 Dec 2024 10:25:02 +0530 Subject: [PATCH 3/7] updating stac collection not found error --- src/main/java/ogc/rs/database/DatabaseServiceImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java index 463a7a71..61bf1bfc 100644 --- a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java +++ b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java @@ -436,7 +436,8 @@ public Future getStacCollection(String collectionId) { .onSuccess( success -> { LOGGER.debug("DB result - {}", success); - if (success.equals(0)) { + if (success.isEmpty()) { + LOGGER.debug("Stac Collection of id {} Not Found!", collectionId); result.fail(new OgcException(404, "Not found", "Collection not found")); } JsonObject collection = success.get(0); From 675bafdd3c4c5090bb75bb6a19d33cc7bdf07ae5 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Tue, 17 Dec 2024 14:49:42 +0530 Subject: [PATCH 4/7] updated the OGC and STAC spec --- .../ogccollections/OgcCollectionMetadata.java | 26 ---- .../ogccollections/OgcCollectionsEntity.java | 19 --- .../StacCollectionMetadata.java | 85 +++++++++++ .../StacCollectionsEntity.java | 139 ++++++++++++++++++ .../staccollections/package-info.java | 1 + .../java/ogc/rs/database/DatabaseService.java | 9 ++ .../ogc/rs/database/DatabaseServiceImpl.java | 42 +++++- 7 files changed, 275 insertions(+), 46 deletions(-) create mode 100644 src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionMetadata.java create mode 100644 src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionsEntity.java create mode 100644 src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/package-info.java diff --git a/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionMetadata.java b/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionMetadata.java index 5630823f..d6564c42 100644 --- a/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionMetadata.java +++ b/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionMetadata.java @@ -72,30 +72,4 @@ public JsonObject generateOgcOasBlock() { return block; } - /** - * - * Generate OpenAPI JSON block for all STAC routes that this collection can have. - * - * @return JSON object containing OpenAPI paths for all STAC routes for this collection. - */ - public JsonObject generateStacOasBlock() { - JsonObject block = new JsonObject(); - - /* GET /stac/collections/ */ - JsonObject collectionSpecific = new JsonObject(); - - collectionSpecific.put("tags", new JsonArray().add(title)); - collectionSpecific.put("summary", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY.get()); - collectionSpecific.put("operationId", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID.get()); - collectionSpecific.put("responses", - new JsonObject() - .put("200", new JsonObject().put("$ref", "#/components/responses/stacCollection")) - .put("404", new JsonObject().put("$ref", "#/components/responses/NotFound")) - .put("500", new JsonObject().put("$ref", "#/components/responses/ServerError"))); - - block.put(STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT.get(), - new JsonObject().put("get", collectionSpecific)); - - return block; - } } diff --git a/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionsEntity.java b/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionsEntity.java index 4b666c42..80126be0 100644 --- a/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionsEntity.java +++ b/src/main/java/ogc/rs/apiserver/router/gisentities/ogccollections/OgcCollectionsEntity.java @@ -60,22 +60,6 @@ public void giveOgcRoutes(OgcRouterBuilder ogcRouterBuilder) { @Override public void giveStacRoutes(StacRouterBuilder stacRouterBuilder) { - RouterBuilder builder = stacRouterBuilder.routerBuilder; - ApiServerVerticle apiServerVerticle = stacRouterBuilder.apiServerVerticle; - FailureHandler failureHandler = stacRouterBuilder.failureHandler; - - List collectionSpecificOpIds = builder.operations().stream() - .filter(op -> op.getOperationId().matches(OgcCollectionMetadata.STAC_OP_ID_PREFIX_REGEX)) - .map(op -> op.getOperationId()).collect(Collectors.toList()); - - collectionSpecificOpIds.forEach(opId -> { - if (opId.matches(OgcCollectionMetadata.STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX)) { - builder.operation(opId).handler(apiServerVerticle::getStacCollection) - .handler(apiServerVerticle::putCommonResponseHeaders) - .handler(apiServerVerticle::buildResponse) - .failureHandler(failureHandler); - } - }); } @Override @@ -108,16 +92,13 @@ public Future generateNewSpecFragments(JsonObject existingOgcSpec, Future result = metadataObjList.compose(list -> { List ogcFrags = new ArrayList(); - List stacFrags = new ArrayList(); list.forEach(obj -> { ogcFrags.add(obj.generateOgcOasBlock()); - stacFrags.add(obj.generateStacOasBlock()); }); OasFragments fragments = new OasFragments(); fragments.setOgc(ogcFrags); - fragments.setStac(stacFrags); return Future.succeededFuture(fragments); }); diff --git a/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionMetadata.java b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionMetadata.java new file mode 100644 index 00000000..680d510d --- /dev/null +++ b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionMetadata.java @@ -0,0 +1,85 @@ +package ogc.rs.apiserver.router.gisentities.staccollections; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +import java.util.UUID; +import java.util.function.Supplier; + +/** Class used to hold STAC Collection metadata */ +public class StacCollectionMetadata { + public static final String STAC_OP_ID_PREFIX_REGEX = "^itemlessStacCollection-.*"; + public static final String STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX = + "^itemlessStacCollection-.*-get-specific-collection$"; + public static final String STAC_GET_ITEMS_COLLECTION_OP_ID_REGEX = + "^itemlessStacCollection-.*-get-items$"; + + private UUID id; + private String title; + private String description; + + private final Supplier STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY = + () -> "Metadata about " + description; + private final Supplier STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID = + () -> "itemlessStacCollection-" + id.toString() + "-get-specific-collection"; + private final Supplier STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT = + () -> "/stac/collections/" + id.toString(); + + private final Supplier STAC_GET_COLLECTION_ITEMS_SUMMARY = + () -> "Get items from " + description; + private final Supplier STAC_GET_COLLECTION_ITEMS_OPERATION_ID = + () -> "itemlessStacCollection-" + id.toString() + "-get-items"; + private final Supplier STAC_GET_COLLECTION_ITEMS_ENDPOINT = + () -> "/stac/collections/" + id.toString() + "/items"; + + public StacCollectionMetadata(JsonObject obj) { + id = UUID.fromString(obj.getString("id")); + title = obj.getString("title", "Undefined title"); + description = obj.getString("description", "Undefined description"); + } + + public UUID getId() { + return id; + } + + /** + * Generate OpenAPI JSON block for all STAC routes that this collection can have. + * + * @return JSON object containing OpenAPI paths for all STAC routes for this collection. + */ + public JsonObject generateStacOasBlock() { + JsonObject block = new JsonObject(); + + /* GET /stac/collections/ */ + JsonObject collectionSpecific = new JsonObject(); + + collectionSpecific.put("tags", new JsonArray().add(title)); + collectionSpecific.put("summary", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_SUMMARY.get()); + collectionSpecific.put("operationId", STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OPERATION_ID.get()); + collectionSpecific.put( + "responses", + new JsonObject() + .put("200", new JsonObject().put("$ref", "#/components/responses/stacCollection")) + .put("404", new JsonObject().put("$ref", "#/components/responses/NotFound")) + .put("500", new JsonObject().put("$ref", "#/components/responses/ServerError"))); + + block.put( + STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_ENDPOINT.get(), + new JsonObject().put("get", collectionSpecific)); + + /* GET stac/collections//items */ + JsonObject stacItems = new JsonObject(); + stacItems.put("tags", new JsonArray().add(title)); + stacItems.put("summary", STAC_GET_COLLECTION_ITEMS_SUMMARY.get()); + stacItems.put("operationId", STAC_GET_COLLECTION_ITEMS_OPERATION_ID.get()); + stacItems.put( + "responses", + new JsonObject() + .put("200", new JsonObject().put("$ref", "#/components/responses/StacFeatures")) + .put("404", new JsonObject().put("$ref", "#/components/responses/NotFound")) + .put("500", new JsonObject().put("$ref", "#/components/responses/ServerError"))); + block.put(STAC_GET_COLLECTION_ITEMS_ENDPOINT.get(), new JsonObject().put("get", stacItems)); + + return block; + } +} diff --git a/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionsEntity.java b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionsEntity.java new file mode 100644 index 00000000..b9de31a9 --- /dev/null +++ b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/StacCollectionsEntity.java @@ -0,0 +1,139 @@ +package ogc.rs.apiserver.router.gisentities.staccollections; + +import com.google.auto.service.AutoService; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.openapi.RouterBuilder; +import ogc.rs.apiserver.ApiServerVerticle; +import ogc.rs.apiserver.handlers.FailureHandler; +import ogc.rs.apiserver.router.gisentities.GisEntityInterface; +import ogc.rs.apiserver.router.gisentities.ogccollections.OgcCollectionMetadata; +import ogc.rs.apiserver.router.gisentities.ogccollections.OgcCollectionsEntity; +import ogc.rs.apiserver.router.routerbuilders.OgcRouterBuilder; +import ogc.rs.apiserver.router.routerbuilders.StacRouterBuilder; +import ogc.rs.apiserver.router.util.OasFragments; +import ogc.rs.database.DatabaseService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Class to handle creation of routes for /stac/collection/ for OGC collections. + */ +@AutoService(GisEntityInterface.class) +public class StacCollectionsEntity implements GisEntityInterface { + + private static final String UUID_REGEX = + "[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}"; + private static final String STAC_COLLECTION_PATH_REGEX = "^stac/collections/" + UUID_REGEX; + + private static final Logger LOGGER = LogManager.getLogger(StacCollectionsEntity.class); + + @Override + public void giveOgcRoutes(OgcRouterBuilder routerBuilder) {} + + @Override + public void giveStacRoutes(StacRouterBuilder stacRouterBuilder) { + RouterBuilder builder = stacRouterBuilder.routerBuilder; + ApiServerVerticle apiServerVerticle = stacRouterBuilder.apiServerVerticle; + FailureHandler failureHandler = stacRouterBuilder.failureHandler; + + List collectionSpecificOpIds = + builder.operations().stream() + .filter( + op -> op.getOperationId().matches(StacCollectionMetadata.STAC_OP_ID_PREFIX_REGEX)) + .map(op -> op.getOperationId()) + .collect(Collectors.toList()); + + collectionSpecificOpIds.forEach( + opId -> { + if (opId.matches( + StacCollectionMetadata.STAC_GET_SPECIFIC_ITEMLESS_COLLECTION_OP_ID_REGEX)) { + builder + .operation(opId) + .handler(apiServerVerticle::getStacCollection) + .handler(apiServerVerticle::putCommonResponseHeaders) + .handler(apiServerVerticle::buildResponse) + .failureHandler(failureHandler); + } else if (opId.matches(StacCollectionMetadata.STAC_GET_ITEMS_COLLECTION_OP_ID_REGEX)) { + builder + .operation(opId) + .handler(apiServerVerticle::getStacItems) + .handler(apiServerVerticle::putCommonResponseHeaders) + .handler(apiServerVerticle::buildResponse) + .failureHandler(failureHandler); + } + }); + } + + @Override + public Future generateNewSpecFragments( + JsonObject existingOgcSpec, + JsonObject existingStacSpec, + DatabaseService dbService, + JsonObject config) { + Promise promise = Promise.promise(); + Set existingCollectionIds = getExistingCollectionsFromOgcSpec(existingStacSpec); + + Future> stacCollectionMetadata = + dbService.getStacCollectionMetadataForOasSpec( + existingCollectionIds.stream().map(i -> i.toString()).collect(Collectors.toList())); + + Future> metadataObjList = + stacCollectionMetadata.compose( + res -> { + List list = + res.stream().map(i -> new StacCollectionMetadata(i)).collect(Collectors.toList()); + + List foundCollectionIds = + list.stream().map(obj -> obj.getId().toString()).collect(Collectors.toList()); + + if (foundCollectionIds.isEmpty()) { + LOGGER.info("No new STAC collections found!"); + } else { + LOGGER.info("New STAC collections found : {}", foundCollectionIds); + } + + return Future.succeededFuture(list); + }); + + Future result = + metadataObjList.compose( + list -> { + List stacFrags = new ArrayList(); + + list.forEach( + obj -> { + stacFrags.add(obj.generateStacOasBlock()); + }); + + OasFragments fragments = new OasFragments(); + fragments.setStac(stacFrags); + + return Future.succeededFuture(fragments); + }); + + result.onSuccess(i -> promise.complete(i)).onFailure(err -> promise.fail(err)); + return promise.future(); + } + + private Set getExistingCollectionsFromOgcSpec(JsonObject ogcSpec) { + Set existingIds = new HashSet(); + + ogcSpec + .getJsonObject("paths") + .forEach( + k -> { + String key = k.getKey(); + if (key.matches(STAC_COLLECTION_PATH_REGEX)) { + String collectionId = key.split("/")[2]; + existingIds.add(UUID.fromString(collectionId)); + } + }); + + return existingIds; + } +} diff --git a/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/package-info.java b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/package-info.java new file mode 100644 index 00000000..cf49bfab --- /dev/null +++ b/src/main/java/ogc/rs/apiserver/router/gisentities/staccollections/package-info.java @@ -0,0 +1 @@ +package ogc.rs.apiserver.router.gisentities.staccollections; \ No newline at end of file diff --git a/src/main/java/ogc/rs/database/DatabaseService.java b/src/main/java/ogc/rs/database/DatabaseService.java index b5fdc75d..d4062935 100644 --- a/src/main/java/ogc/rs/database/DatabaseService.java +++ b/src/main/java/ogc/rs/database/DatabaseService.java @@ -81,4 +81,13 @@ Future getFeature(String collectionId, Integer featureId, Map> getCollectionMetadataForOasSpec(List existingCollectionUuidIds); + + /** + * Get all STAC collections metadata to be used for OpenAPI spec generation. + * + * @param existingCollectionUuidIds UUID IDs of collections that are already part of the spec. + * @return list of {@link JsonObject}, which is cast to the required type by the caller. + */ + Future> getStacCollectionMetadataForOasSpec(List existingCollectionUuidIds); + } diff --git a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java index 61bf1bfc..e49954eb 100644 --- a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java +++ b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java @@ -981,7 +981,9 @@ public Future> getCollectionMetadataForOasSpec( Collectors.mapping(Row::toJson, Collectors.toList()); final String GET_COLLECTION_INFO = - " SELECT id, title, description FROM collections_details WHERE id != ALL($1::UUID[])"; + "SELECT collections_details.id, title, description FROM collections_details " + + "JOIN collection_type ON collections_details.id = collection_type.collection_id " + + "WHERE collections_details.id != ALL($1::UUID[]) AND collection_type.type != 'STAC'"; Future> newCollectionsJson = client.withConnection( @@ -1003,4 +1005,42 @@ public Future> getCollectionMetadataForOasSpec( return result.future(); } + + @Override + public Future> getStacCollectionMetadataForOasSpec( + List existingCollectionUuidIds) { + + Promise> result = Promise.promise(); + + UUID[] existingCollectionIdsArr = + existingCollectionUuidIds.stream().map(i -> UUID.fromString(i)).toArray(UUID[]::new); + + Collector> collector = + Collectors.mapping(Row::toJson, Collectors.toList()); + + final String GET_STAC_COLLECTION_INFO = + "SELECT collections_details.id, title, description FROM collections_details JOIN " + + "collection_type ON collections_details.id = collection_type.collection_id WHERE " + + "collections_details.id != ALL($1::UUID[]) AND collection_type.type = 'STAC'"; + + Future> newCollectionsJson = + client.withConnection( + conn -> + conn.preparedQuery(GET_STAC_COLLECTION_INFO) + .collecting(collector) + .execute(Tuple.of(existingCollectionIdsArr)) + .map(res -> res.value())); + + newCollectionsJson + .onSuccess(succ -> result.complete(succ)) + .onFailure( + fail -> { + LOGGER.error( + "Something went wrong when querying DB for new OGC collections {}", + fail.getMessage()); + result.fail(fail); + }); + + return result.future(); + } } From aa28cc7bafcb850f96a295319a7444343e6d5416 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Wed, 18 Dec 2024 22:39:58 +0530 Subject: [PATCH 5/7] updating the query to getch collections data --- .../ogc/rs/apiserver/ApiServerVerticle.java | 31 +++++---- .../ogc/rs/database/DatabaseServiceImpl.java | 66 +++++-------------- .../V17__add_collections_enclosure_table.sql | 4 ++ 3 files changed, 36 insertions(+), 65 deletions(-) diff --git a/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java b/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java index 80003072..0efe3978 100644 --- a/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java +++ b/src/main/java/ogc/rs/apiserver/ApiServerVerticle.java @@ -897,25 +897,28 @@ private JsonObject buildCollectionFeatureResult(List success) { .put("templated","true"))) .put("itemType", "feature") .put("crs", collection.getJsonArray("crs")); - if (collection.containsKey("enclosure")) { + if (collection.getJsonArray("enclosure") != null && !collection.getJsonArray("enclosure").isEmpty()) { collection.getJsonArray("enclosure") .forEach(enclosureJson -> { - JsonObject enclosure = new JsonObject(); - enclosure.mergeIn((JsonObject) enclosureJson); - String href = - hostName + ogcBasePath + "assets/" + enclosure.getString("id"); - enclosure.put("href", href); - enclosure.put("rel", "enclosure"); - enclosure.put("length", enclosure.getInteger("size")); - enclosure.remove("size"); - enclosure.remove("id"); - enclosure.remove("collections_id"); - // enclosure.remove("role"); - collection.getJsonArray("links").add(enclosure); + // Ensure the enclosure is not null + if (enclosureJson != null) { + JsonObject enclosure = new JsonObject(); + enclosure.mergeIn((JsonObject) enclosureJson); + String href = + hostName + ogcBasePath + "assets/" + enclosure.getString("id"); + enclosure.put("href", href); + enclosure.put("rel", "enclosure"); + enclosure.put("length", enclosure.getInteger("size")); + enclosure.remove("size"); + enclosure.remove("id"); + enclosure.remove("collections_id"); + // enclosure.remove("role"); + collection.getJsonArray("links").add(enclosure); + } }); collection.remove("enclosure"); - } + if (success.get(0).getJsonArray("type").contains("COVERAGE")) { collection .getJsonArray("links") diff --git a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java index e49954eb..4bb30b4f 100644 --- a/src/main/java/ogc/rs/database/DatabaseServiceImpl.java +++ b/src/main/java/ogc/rs/database/DatabaseServiceImpl.java @@ -86,59 +86,25 @@ public Future> getCollections() { Promise> result = Promise.promise(); Collector> collector = Collectors.mapping(Row::toJson, Collectors.toList()); client.withConnection(conn -> - conn.preparedQuery("select collections_details.id, title, array_agg(distinct crs_to_srid.crs) as crs" + - ", collections_details.crs as \"storageCrs\", description, datetime_key, bbox, temporal" + - ", array_agg(distinct collection_type.type) as type" + - " from collections_details join collection_supported_crs" + - " on collections_details.id = collection_supported_crs.collection_id join crs_to_srid" + - " on crs_to_srid.id = collection_supported_crs.crs_id join collection_type" + - " on collections_details.id = collection_type.collection_id where collection_type.type!= 'STAC' group by collections_details.id") + conn.preparedQuery("select collections_details.id, collections_details.title, array_agg(DISTINCT crs_to_srid.crs) as crs" + + " , collections_details.crs as \"storageCrs\", collections_details.description, collections_details.datetime_key, " + + " collections_details.bbox, collections_details.temporal, jsonb_agg(distinct(row_to_json(collections_enclosure.*)::jsonb " + + " - 'collections_id')) AS enclosure, ARRAY_AGG(DISTINCT collection_type.type) AS type FROM collections_details LEFT " + + " JOIN collections_enclosure ON collections_details.id = collections_enclosure.collections_id JOIN collection_supported_crs ON " + + " collections_details.id = collection_supported_crs.collection_id JOIN crs_to_srid ON crs_to_srid.id = " + + " collection_supported_crs.crs_id JOIN collection_type ON collections_details.id = collection_type.collection_id " + + " WHERE collection_type.type != 'STAC' GROUP BY collections_details.id;") .collecting(collector) .execute() - .map(SqlResult::value) + .map(SqlResult::value)) .onSuccess(success -> { - if (success.isEmpty()) { - LOGGER.error("Collections table is empty!"); - result.fail( - new OgcException(404, "Not found", "Collection table is Empty!")); - } else { - conn.preparedQuery("SELECT * FROM COLLECTIONS_ENCLOSURE") - .collecting(collector) - .execute() - .map(SqlResult::value) - .onSuccess( - enclosureResult -> { - if (enclosureResult.isEmpty()) { - LOGGER.warn("Assets table is empty!"); - result.complete(success); - } else { - for (JsonObject enclosure : enclosureResult) { - for (JsonObject successItem : success) { - if (successItem - .getString("id") - .equals(enclosure.getString("collections_id"))) { - if (successItem.containsKey("enclosure")) { - successItem.getJsonArray("enclosure").add(enclosure); - } else { - successItem.put("enclosure", new JsonArray().add(enclosure)); - } - } - } - } - result.complete(success); - } - }) - .onFailure( - fail -> { - LOGGER.error("Failed to get enclosure links! - {}", fail.getMessage()); - result.fail("Error!"); - }); - } + LOGGER.debug("Collections Result: {}", success.toString()); + result.complete(success); }) .onFailure(fail -> { LOGGER.error("Failed to getCollections! - {}", fail.getMessage()); result.fail("Error!"); - })); + }); return result.future(); } @Override @@ -331,11 +297,10 @@ public Future> getStacCollections() { client.withConnection( conn -> conn.preparedQuery( - "SELECT collections_details.id, title, description," + "Select collections_details.id, title, description," + " bbox, temporal, license FROM collections_details JOIN collection_type " + "ON collections_details.id = collection_type.collection_id WHERE " - + "collection_type.type = 'STAC' GROUP BY collections_details.id, title, " - + "description, bbox, temporal, license") + + "collection_type.type = 'STAC' ") .collecting(collector) .execute() .map(SqlResult::value) @@ -428,8 +393,7 @@ public Future getStacCollection(String collectionId) { "SELECT collections_details.id, title, " + "description, bbox, temporal, license FROM collections_details " + "JOIN collection_type ON collections_details.id = collection_type.collection_id " - + "WHERE collection_type.type = 'STAC' AND collections_details.id = $1::uuid GROUP " - + "BY collections_details.id, title, description, bbox, temporal, license") + + "WHERE collection_type.type = 'STAC' AND collections_details.id = $1::uuid ") .collecting(collector) .execute(Tuple.of(UUID.fromString(collectionId))) .map(SqlResult::value) diff --git a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql index b209cdb5..e342cbaf 100644 --- a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql +++ b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql @@ -16,3 +16,7 @@ CREATE INDEX idx_collections_enclosure_collections_id ON collections_enclosure (collections_id); GRANT SELECT, INSERT, DELETE ON collections_enclosure TO ${ogcUser} + +-- TRUNCATE table stac_collections_assets as the data has been shifted to collections_enclosure +TRUNCATE TABLE stac_collections_assets; + From 8c01b084e7a5a83145273a9979f208b9eb070897 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Thu, 19 Dec 2024 10:27:55 +0530 Subject: [PATCH 6/7] removed truncate statement from script --- .../db/migration/V17__add_collections_enclosure_table.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql index e342cbaf..f69a622f 100644 --- a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql +++ b/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql @@ -17,6 +17,4 @@ ON collections_enclosure (collections_id); GRANT SELECT, INSERT, DELETE ON collections_enclosure TO ${ogcUser} --- TRUNCATE table stac_collections_assets as the data has been shifted to collections_enclosure -TRUNCATE TABLE stac_collections_assets; From 13daaee00aa559bab9353b332b4781ba16c46471 Mon Sep 17 00:00:00 2001 From: Tanvi Prasad Date: Thu, 9 Jan 2025 15:26:26 +0530 Subject: [PATCH 7/7] renamed the migration file --- ...closure_table.sql => V18__add_collections_enclosure_table.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/main/resources/db/migration/{V17__add_collections_enclosure_table.sql => V18__add_collections_enclosure_table.sql} (100%) diff --git a/src/main/resources/db/migration/V17__add_collections_enclosure_table.sql b/src/main/resources/db/migration/V18__add_collections_enclosure_table.sql similarity index 100% rename from src/main/resources/db/migration/V17__add_collections_enclosure_table.sql rename to src/main/resources/db/migration/V18__add_collections_enclosure_table.sql