diff --git a/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java b/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java index a7b7bba..a6348a4 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java +++ b/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java @@ -9,5 +9,7 @@ public record SwodlrProperties( Map teaMapping, String productCreateQueueUrl, - String availableTilesTableName + String availableTilesTableName, + String cmrGraphqlEndpoint, + String rasterCollectionConceptId ) { } diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java index dbd0d55..6af57f9 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java @@ -1,6 +1,7 @@ package gov.nasa.podaac.swodlr.l2rasterproduct; import gov.nasa.podaac.swodlr.exception.SwodlrException; +import gov.nasa.podaac.swodlr.l2rasterproduct.cmr.CmrSearchService; import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.State; import gov.nasa.podaac.swodlr.status.Status; @@ -31,6 +32,9 @@ public class L2RasterProductController { private Logger logger = LoggerFactory.getLogger(getClass()); + @Autowired + private CmrSearchService cmrSearchService; + @Autowired private L2RasterProductService l2RasterProductService; @@ -80,6 +84,51 @@ public Mono generateL2RasterProduct( }); } + @MutationMapping + public Mono generateL2RasterProductByConceptId( + @ContextValue UserReference userRef, + @Argument String conceptId, + @Argument boolean outputGranuleExtentFlag, + @Argument @NotNull GridType outputSamplingGridType, + @Argument int rasterResolution, + @Argument Integer utmZoneAdjust, + @Argument Integer mgrsBandAdjust + ) { + return Mono.defer(() -> { + User user = userRef.fetch(); + + return cmrSearchService + .findL2RasterProductById(conceptId) + .flatMap((result) -> { + return l2RasterProductService + .getL2RasterProduct( + user, + result.cycle(), + result.pass(), + result.scene(), + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ) + .switchIfEmpty( + Mono.defer(() -> l2RasterProductService.createL2RasterProduct( + user, + result.cycle(), + result.pass(), + result.scene(), + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + )) + ); + }); + }); + } + @PreAuthorize("hasRole(\"ROLE_Administrator\")") @MutationMapping @Transactional diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrGraphqlRequest.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrGraphqlRequest.java new file mode 100644 index 0000000..711292d --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrGraphqlRequest.java @@ -0,0 +1,33 @@ +package gov.nasa.podaac.swodlr.l2rasterproduct.cmr; + +import java.util.Map; + +public class CmrGraphqlRequest { + private static final String QUERY = + """ + query($params: GranulesInput) { + granules(params: $params) { + items { + granuleUr + } + } + } + """; + + private final Map variables; + + public CmrGraphqlRequest(String collectionConceptId, String conceptId) { + variables = Map.of("params", Map.of( + "conceptId", conceptId, + "collectionConceptId", collectionConceptId + )); + } + + public final String getQuery() { + return QUERY; + } + + public final Map getVariables() { + return variables; + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrSearchService.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrSearchService.java new file mode 100644 index 0000000..abfb19b --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/CmrSearchService.java @@ -0,0 +1,88 @@ +package gov.nasa.podaac.swodlr.l2rasterproduct.cmr; + +import gov.nasa.podaac.swodlr.SwodlrProperties; +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +@Service +public class CmrSearchService { + private Logger logger = LoggerFactory.getLogger(getClass()); + private Pattern granuleUrRe = Pattern.compile("SWOT_L2_HR_Raster_\\d{3}m_\\w+?_(?\\d{3})_(?\\d{3})_(?\\d{3})F_\\d+T\\d+_\\d+T\\d+_(?\\w+?)_\\d{2}"); + + @Autowired + private SwodlrProperties properties; + + public Mono findL2RasterProductById(String id) { + return getUserToken() + .flatMap((token) -> { + return Mono.defer(() -> { + return WebClient.create() + .post() + .uri(URI.create(properties.cmrGraphqlEndpoint())) + .header("Authorization", "Bearer " + token) + .bodyValue(new CmrGraphqlRequest( + properties.rasterCollectionConceptId(), + id + )) + .retrieve() + .bodyToMono(new ParameterizedTypeReference>() {}); + }); + }) + .map((response) -> { + if (response.get("errors") != null) { + for (var error : (List) response.get("errors")) { + logger.error("GraphQL query error: %s", error.toString()); + } + + throw new RuntimeException("GraphQL search returned an error"); + } + + if (response.get("data") == null) { + throw new RuntimeException("No data found in CMR response"); + } + + var data = (Map) response.get("data"); + var granules = (Map) data.get("granules"); + var items = (List>) granules.get("items"); + + if (items.size() != 1) { + return null; + } + + var result = items.get(0); + var granuleUr = (String) result.get("granuleUr"); + var matcher = granuleUrRe.matcher(granuleUr); + + if (!matcher.matches()) { + throw new RuntimeException("Regex failed to match granuleUr"); + } + + return new GranuleResult( + Integer.parseInt(matcher.group("cycle")), + Integer.parseInt(matcher.group("pass")), + Integer.parseInt(matcher.group("scene")), + matcher.group("crid") + ); + }); + } + + private Mono getUserToken() { + return ReactiveSecurityContextHolder + .getContext() + .map((securityContext) -> securityContext.getAuthentication()) + .filter((authentication) -> authentication != null) + .cast(JwtAuthenticationToken.class) + .map((jwt) -> jwt.getToken().getTokenValue()); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/GranuleResult.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/GranuleResult.java new file mode 100644 index 0000000..17b1e86 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/cmr/GranuleResult.java @@ -0,0 +1,8 @@ +package gov.nasa.podaac.swodlr.l2rasterproduct.cmr; + +public record GranuleResult( + int cycle, + int pass, + int scene, + String crid +) { } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 399acf4..7cde19d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,7 +20,8 @@ spring.security.oauth2.client.provider.edl.token-uri=https://urs.earthdata.nasa. spring.security.oauth2.client.provider.edl.user-info-uri=https://urs.earthdata.nasa.gov/api/users spring.security.oauth2.client.provider.edl.user-name-attribute=uid -swodlr.cmr.endpoint=https://graphql.earthdata.nasa.gov/api +swodlr.cmr-graphql-endpoint=https://graphql.earthdata.nasa.gov/api +swodlr.raster-collection-concept-id=C2799438271-POCLOUD swodlr.security.sessionLength=24h #--- diff --git a/src/main/resources/graphql/mutation.graphqls b/src/main/resources/graphql/mutation.graphqls index 80666c6..d1a4ef8 100644 --- a/src/main/resources/graphql/mutation.graphqls +++ b/src/main/resources/graphql/mutation.graphqls @@ -11,6 +11,15 @@ type Mutation { mgrsBandAdjust: Int ): L2RasterProduct! + generateL2RasterProductByConceptId( + conceptId: ID!, + outputGranuleExtentFlag: Boolean!, + outputSamplingGridType: GridType!, + rasterResolution: Int!, + utmZoneAdjust: Int, + mgrsBandAdjust: Int + ): L2RasterProduct! + # -- Raster Definitions -- deleteRasterDefinition(id: ID!): Boolean! createRasterDefinition(