diff --git a/database/local_data.sql b/database/local_data.sql index cb8c614..2d72b31 100644 --- a/database/local_data.sql +++ b/database/local_data.sql @@ -1,16 +1,8 @@ -- Data for testing locally -- *** DO NOT DEPLOY TO UAT OR OPS *** -INSERT INTO "Users" ("id", "username") VALUES ('fee1dc78-0604-4fa6-adae-0b4b55440e7d', 'test@local.test'); - -INSERT INTO "RasterDefinitions" ( - "outputGranuleExtentFlag", "outputSamplingGridType", "rasterResolution", "utmZoneAdjust", "mgrsBandAdjust" -) VALUES ( - TRUE, 'UTM', 1000, 1, -1 -); - -INSERT INTO "RasterDefinitions" ( - "outputGranuleExtentFlag", "outputSamplingGridType", "rasterResolution" +INSERT INTO "Users" ( + "id", "username", "email", "firstName", "lastName" ) VALUES ( - TRUE, 'GEO', 3 + 'fee1dc78-0604-4fa6-adae-0b4b55440e7d', 'test-user', 'test@localhost', 'First', 'Last' ); diff --git a/database/schema.sql b/database/schema.sql index 7568ba4..ff373ec 100644 --- a/database/schema.sql +++ b/database/schema.sql @@ -1,41 +1,59 @@ -- Create tables CREATE TABLE "Users" ( "id" uuid DEFAULT gen_random_uuid() PRIMARY KEY, - "username" varchar UNIQUE NOT NULL + "username" varchar UNIQUE NOT NULL, + "email" varchar NOT NULL, + "firstName" varchar NOT NULL, + "lastName" varchar NOT NULL ); CREATE TABLE "RasterDefinitions" ( "id" uuid DEFAULT gen_random_uuid() PRIMARY KEY, + "userId" uuid NOT NULL, + "name" varchar NOT NULL, "outputGranuleExtentFlag" boolean NOT NULL, "outputSamplingGridType" varchar NOT NULL, "rasterResolution" int NOT NULL, "utmZoneAdjust" int, - "mgrsBandAdjust" int + "mgrsBandAdjust" int, + FOREIGN KEY ("userId") REFERENCES "Users" ("id") ); CREATE TABLE "L2RasterProducts" ( "id" uuid DEFAULT gen_random_uuid() PRIMARY KEY, - "definitionID" uuid NOT NULL, + "timestamp" timestamp with time zone NOT NULL DEFAULT current_timestamp, "cycle" int NOT NULL, "pass" int NOT NULL, "scene" int NOT NULL, - FOREIGN KEY ("definitionID") REFERENCES "RasterDefinitions" ("id") + "outputGranuleExtentFlag" boolean NOT NULL, + "outputSamplingGridType" varchar NOT NULL, + "rasterResolution" int NOT NULL, + "utmZoneAdjust" int, + "mgrsBandAdjust" int +); + +CREATE TABLE "Granules" ( + "id" uuid DEFAULT gen_random_uuid() PRIMARY KEY, + "productId" uuid NOT NULL, + "timestamp" timestamp with time zone NOT NULL DEFAULT current_timestamp, + "uri" varchar NOT NULL, + FOREIGN KEY ("productId") REFERENCES "L2RasterProducts" ("id") ); CREATE TABLE "ProductHistory" ( - "requestedByID" uuid, - "rasterProductID" uuid, + "requestedById" uuid, + "rasterProductId" uuid, "timestamp" timestamp with time zone NOT NULL DEFAULT current_timestamp, - PRIMARY KEY ("requestedByID", "rasterProductID"), - FOREIGN KEY ("requestedByID") REFERENCES "Users" ("id"), - FOREIGN KEY ("rasterProductID") REFERENCES "L2RasterProducts" ("id") ON DELETE CASCADE + PRIMARY KEY ("requestedById", "rasterProductId"), + FOREIGN KEY ("requestedById") REFERENCES "Users" ("id"), + FOREIGN KEY ("rasterProductId") REFERENCES "L2RasterProducts" ("id") ON DELETE CASCADE ); CREATE TABLE "Status" ( "id" uuid DEFAULT gen_random_uuid() PRIMARY KEY, - "productID" uuid NOT NULL, + "productId" uuid NOT NULL, "timestamp" timestamp with time zone NOT NULL DEFAULT current_timestamp, "state" varchar NOT NULL, "reason" text, - FOREIGN KEY ("productID") REFERENCES "L2RasterProducts" ("id") ON DELETE CASCADE + FOREIGN KEY ("productId") REFERENCES "L2RasterProducts" ("id") ON DELETE CASCADE ); diff --git a/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java b/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java new file mode 100644 index 0000000..7576f4d --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/SwodlrProperties.java @@ -0,0 +1,12 @@ +package gov.nasa.podaac.swodlr; + +import java.util.Map; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConfigurationProperties("swodlr") +@ConstructorBinding +public record SwodlrProperties( + Map teaMapping, + String productCreateQueueUrl +) { } diff --git a/src/main/java/gov/nasa/podaac/swodlr/Utils.java b/src/main/java/gov/nasa/podaac/swodlr/Utils.java index c96cffa..9afb1fa 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/Utils.java +++ b/src/main/java/gov/nasa/podaac/swodlr/Utils.java @@ -1,6 +1,5 @@ package gov.nasa.podaac.swodlr; -import java.util.UUID; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -8,8 +7,6 @@ @Component public class Utils implements ApplicationContextAware { - public static final UUID NULL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); - private static ApplicationContext applicationContext; @Override diff --git a/src/main/java/gov/nasa/podaac/swodlr/granule/Granule.java b/src/main/java/gov/nasa/podaac/swodlr/granule/Granule.java new file mode 100644 index 0000000..091b70c --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/granule/Granule.java @@ -0,0 +1,71 @@ +package gov.nasa.podaac.swodlr.granule; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import gov.nasa.podaac.swodlr.Utils; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import java.net.URI; +import java.net.URISyntaxException; +import java.time.LocalDateTime; +import java.util.UUID; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import org.hibernate.validator.constraints.URL; + +@Entity +@Table(name = "Granules") +public class Granule { + @Id + private UUID id; + + @ManyToOne(optional = false) + @JoinColumn(name = "productId", nullable = false) + private L2RasterProduct product; + + @Column(nullable = false) + private LocalDateTime timestamp; + + @Column(nullable = false) + private String uri; + + Granule() { } + + public Granule(L2RasterProduct product, String uri) { + this.id = UUID.randomUUID(); + this.timestamp = LocalDateTime.now(); + this.product = product; + this.uri = uri; + } + + public UUID getId() { + return id; + } + + public L2RasterProduct getProduct() { + return product; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + @JsonIgnore + public String getS3Uri() { + return uri; + } + + public String getUri() { + try { + return Utils + .applicationContext() + .getBean(TeaMapper.class) + .convertS3Uri(URI.create(uri)) + .toString(); + } catch (URISyntaxException ex) { } + + return null; + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleController.java b/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleController.java new file mode 100644 index 0000000..b14094d --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleController.java @@ -0,0 +1,19 @@ +package gov.nasa.podaac.swodlr.granule; + +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.graphql.data.method.annotation.SchemaMapping; +import org.springframework.stereotype.Controller; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; + +@Controller +public class GranuleController { + @Autowired + private GranuleRepository granuleRepository; + + @SchemaMapping(typeName = "L2RasterProduct", field = "granules") + public Set getGranulesForProduct(L2RasterProduct product) { + return granuleRepository.findByProduct(product); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleRepository.java b/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleRepository.java new file mode 100644 index 0000000..6565443 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/granule/GranuleRepository.java @@ -0,0 +1,10 @@ +package gov.nasa.podaac.swodlr.granule; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import java.util.Set; +import java.util.UUID; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GranuleRepository extends JpaRepository { + Set findByProduct(L2RasterProduct product); +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/granule/TeaMapper.java b/src/main/java/gov/nasa/podaac/swodlr/granule/TeaMapper.java new file mode 100644 index 0000000..211c0c0 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/granule/TeaMapper.java @@ -0,0 +1,22 @@ +package gov.nasa.podaac.swodlr.granule; + +import gov.nasa.podaac.swodlr.SwodlrProperties; +import java.net.URI; +import java.net.URISyntaxException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class TeaMapper { + @Autowired + private SwodlrProperties swodlrProperties; + + public URI convertS3Uri(URI s3Uri) throws URISyntaxException{ + String bucketName = s3Uri.getAuthority(); + String teaHost = swodlrProperties.teaMapping().get(bucketName); + String path = "/" + bucketName + s3Uri.getPath(); + + URI teaUri = new URI("https", teaHost, path, null); + return teaUri; + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProduct.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProduct.java index 742b6ae..eee1582 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProduct.java +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProduct.java @@ -1,52 +1,78 @@ package gov.nasa.podaac.swodlr.l2rasterproduct; +import gov.nasa.podaac.swodlr.granule.Granule; import gov.nasa.podaac.swodlr.producthistory.ProductHistory; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.Status; import gov.nasa.podaac.swodlr.user.User; +import gov.nasa.podaac.swodlr.validation.ValidRasterOptions; + +import java.time.LocalDateTime; +import java.util.List; import java.util.Set; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.OrderBy; import javax.persistence.Table; import javax.validation.constraints.PositiveOrZero; +import org.hibernate.validator.constraints.Range; + @Entity @Table(name = "L2RasterProducts") +@ValidRasterOptions public class L2RasterProduct { @Id private UUID id; - @ManyToOne(optional = false) - @JoinColumn(name = "definitionID", nullable = false) - private RasterDefinition definition; + @Column(nullable = false) + private LocalDateTime timestamp; @Column(nullable = false) - @PositiveOrZero private int cycle; @Column(nullable = false) - @PositiveOrZero private int pass; @Column(nullable = false) - @PositiveOrZero private int scene; + @Column(nullable = false) + private Boolean outputGranuleExtentFlag; + + @Column(nullable = false) + @Enumerated(EnumType.STRING) + private GridType outputSamplingGridType; + + @Column(nullable = false) + private Integer rasterResolution; + + @Column + private Integer utmZoneAdjust; + + @Column + private Integer mgrsBandAdjust; + @OneToMany(mappedBy = "product") - private Set statuses; + @OrderBy("timestamp DESC") + private List statuses; + + @OneToMany(mappedBy = "product") + private Set granules; @ManyToMany @JoinTable( name = "ProductHistory", - joinColumns = @JoinColumn(name = "rasterProductID"), - inverseJoinColumns = @JoinColumn(name = "requestedByID") + joinColumns = @JoinColumn(name = "rasterProductId"), + inverseJoinColumns = @JoinColumn(name = "requestedById") ) private Set users; @@ -55,22 +81,32 @@ public class L2RasterProduct { public L2RasterProduct() { } - public L2RasterProduct(RasterDefinition definition, int cycle, int pass, int scene) { + public L2RasterProduct( + int cycle, + int pass, + int scene, + boolean outputGranuleExtentFlag, + GridType outputSamplingGridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust + ) { this.id = UUID.randomUUID(); - this.definition = definition; + this.timestamp = LocalDateTime.now(); this.cycle = cycle; this.pass = pass; this.scene = scene; + this.outputGranuleExtentFlag = outputGranuleExtentFlag; + this.outputSamplingGridType = outputSamplingGridType; + this.rasterResolution = rasterResolution; + this.utmZoneAdjust = utmZoneAdjust; + this.mgrsBandAdjust = mgrsBandAdjust; } public UUID getId() { return id; } - public RasterDefinition getDefinition() { - return definition; - } - public int getCycle() { return cycle; } @@ -82,4 +118,32 @@ public int getPass() { public int getScene() { return scene; } + + public boolean getOutputGranuleExtentFlag() { + return outputGranuleExtentFlag; + } + + public GridType getOutputSamplingGridType() { + return outputSamplingGridType; + } + + public int getRasterResolution() { + return rasterResolution; + } + + public Integer getUtmZoneAdjust() { + return utmZoneAdjust; + } + + public Integer getMgrsBandAdjust() { + return mgrsBandAdjust; + } + + public Set getGranules() { + return granules; + } + + public List getStatuses() { + return statuses; + } } 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 7ff29b6..ee9554e 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductController.java @@ -1,15 +1,19 @@ package gov.nasa.podaac.swodlr.l2rasterproduct; -import gov.nasa.podaac.swodlr.Utils; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.Status; import gov.nasa.podaac.swodlr.user.User; import gov.nasa.podaac.swodlr.user.UserReference; import java.util.List; import java.util.UUID; + +import javax.validation.constraints.NotNull; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.graphql.data.method.annotation.Argument; import org.springframework.graphql.data.method.annotation.ContextValue; import org.springframework.graphql.data.method.annotation.MutationMapping; +import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.graphql.data.method.annotation.SchemaMapping; import org.springframework.stereotype.Controller; import reactor.core.publisher.Mono; @@ -23,21 +27,57 @@ public class L2RasterProductController { L2RasterProductRepository l2RasterProductRepository; @MutationMapping - public Mono createL2RasterProduct( + public Mono generateL2RasterProduct( @ContextValue UserReference userRef, - @Argument UUID definition, @Argument int cycle, @Argument int scene, - @Argument int pass + @Argument int pass, + @Argument boolean outputGranuleExtentFlag, + @Argument @NotNull GridType outputSamplingGridType, + @Argument int rasterResolution, + @Argument Integer utmZoneAdjust, + @Argument Integer mgrsBandAdjust ) { return Mono.defer(() -> { User user = userRef.fetch(); - return l2RasterProductService.createL2RasterProduct(user, definition, cycle, scene, pass); + + return l2RasterProductService.getL2RasterProduct( + user, + cycle, + scene, + pass, + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ) + .switchIfEmpty(Mono.defer(() -> l2RasterProductService.createL2RasterProduct( + user, + cycle, + pass, + scene, + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ))); }); } + @QueryMapping + public L2RasterProduct l2RasterProduct(@Argument UUID id) { + var result = l2RasterProductRepository.findById(id); + if (result.isPresent()) { + return result.get(); + } + + return null; + } + @SchemaMapping(typeName = "Status", field = "product") - public L2RasterProduct getStatusForProduct(Status status) { + public L2RasterProduct getProductForStatus(Status status) { return status.getProduct(); } @@ -47,10 +87,6 @@ public List getProductsForUser( @Argument UUID after, @Argument int limit ) { - if (after == null) { - after = Utils.NULL_UUID; - } - return l2RasterProductRepository.findByUser(userRef.fetch(), after, limit); } } diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQuery.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQuery.java new file mode 100644 index 0000000..b163989 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQuery.java @@ -0,0 +1,9 @@ +package gov.nasa.podaac.swodlr.l2rasterproduct; + +import gov.nasa.podaac.swodlr.user.User; +import java.util.List; +import java.util.UUID; + +public interface L2RasterProductQuery { + List findByUser(User user, UUID after, int limit); +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQueryImpl.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQueryImpl.java new file mode 100644 index 0000000..3745d45 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductQueryImpl.java @@ -0,0 +1,66 @@ +package gov.nasa.podaac.swodlr.l2rasterproduct; + +import gov.nasa.podaac.swodlr.user.User; +import java.util.List; +import java.util.UUID; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.hibernate.Session; +import org.hibernate.query.Query; +import org.hibernate.type.IntegerType; +import org.hibernate.type.UUIDCharType; + +public class L2RasterProductQueryImpl implements L2RasterProductQuery { + @PersistenceContext + private EntityManager entityManager; + + /* + * This implementation is utilized to workaround an JPA issue with the + * PostgreSQL dialect in Hibernate where Hibernate does not parameterize + * null values as their original datatype and instead parameterizes as + * bytea's. This is a known issue of the PostgreSQL driver/dialect + * + * This can lead to the exceptions: + * - "ERROR: could not determine data type of parameter $1" + * - "ERROR: operator does not exist: uuid = bytea" + * + * Solutions researched include casting values, however, by casting + * in the query, we loose the benefits of parameterization in our + * prepared statements and create a statement which is harder to read. + * Workarounds such as COALESCE and testing other PostgreSQL dialects + * available in Hibernate were tried, but were found to either not + * solve the issue or still require the use of CASTs in queries + * + * The hope is to one day remove this code in favor of Spring Data JPA + * queries, pending the PostgreSQL/Hibernate teams' cooperation with + * one another + * + * Relevant discussions: + * - https://stackoverflow.com/a/64223435 + * - https://stackoverflow.com/a/62680643 + * - https://github.com/pgjdbc/pgjdbc/issues/247#issuecomment-78213991 + */ + @Override + public List findByUser(User user, UUID after, int limit) { + String statement = """ + SELECT \"L2RasterProducts\".* FROM \"L2RasterProducts\" + JOIN \"ProductHistory\" ON \"ProductHistory\".\"rasterProductId\" = \"L2RasterProducts\".id + WHERE + (\"ProductHistory\".\"requestedById\" = CAST(:userId as UUID)) AND + ( + (:after is NULL) + OR + (\"ProductHistory\".timestamp, \"ProductHistory\".\"rasterProductId\") < (SELECT timestamp, \"rasterProductId\" FROM \"ProductHistory\" WHERE \"requestedById\" = CAST(:userId as UUID) AND \"rasterProductId\" = CAST(:after as UUID)) + ) + ORDER BY \"ProductHistory\".timestamp DESC, \"ProductHistory\".\"rasterProductId\" DESC LIMIT :limit + """; + + Session session = entityManager.unwrap(Session.class); + Query query = session.createNativeQuery(statement, L2RasterProduct.class); + query.setParameter("userId", user.getId(), UUIDCharType.INSTANCE); + query.setParameter("after", after, UUIDCharType.INSTANCE); + query.setParameter("limit", limit, IntegerType.INSTANCE); + + return query.getResultList(); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductRepository.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductRepository.java index a9a4256..fe16cd6 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductRepository.java +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductRepository.java @@ -1,29 +1,34 @@ package gov.nasa.podaac.swodlr.l2rasterproduct; -import gov.nasa.podaac.swodlr.user.User; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -public interface L2RasterProductRepository extends JpaRepository { - @Query( - value = """ - SELECT \"L2RasterProducts\".* FROM \"L2RasterProducts\" - JOIN \"ProductHistory\" ON \"ProductHistory\".\"rasterProductID\" = \"L2RasterProducts\".id - WHERE - \"ProductHistory\".\"requestedByID\" = :#{#user.getId()} - AND - ( - :#{#after} = CAST('00000000-0000-0000-0000-000000000000' as uuid) - OR - (\"ProductHistory\".timestamp, \"ProductHistory\".\"rasterProductID\") < (SELECT timestamp, \"rasterProductID\" FROM \"ProductHistory\" WHERE \"requestedByID\" = :#{#user.getId()} AND \"rasterProductID\" = :#{#after}) - ) - ORDER BY \"ProductHistory\".timestamp DESC, \"ProductHistory\".\"rasterProductID\" DESC LIMIT :#{#limit} - """, - nativeQuery = true - ) - List findByUser(User user, UUID after, int limit); - +public interface L2RasterProductRepository extends JpaRepository, L2RasterProductQuery { List findById(L2RasterProduct product); + + @Query(""" + SELECT p FROM L2RasterProduct p WHERE + p.cycle = :cycle and + p.pass = :pass and + p.scene = :scene and + p.outputGranuleExtentFlag = :outputGranuleExtentFlag and + p.outputSamplingGridType = :outputSamplingGridType and + p.rasterResolution = :rasterResolution and + p.utmZoneAdjust = :utmZoneAdjust and + p.mgrsBandAdjust = :mgrsBandAdjust + """) + Optional findOneByParameters( + int cycle, + int pass, + int scene, + boolean outputGranuleExtentFlag, + GridType outputSamplingGridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust + ); } diff --git a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductService.java b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductService.java index 6c38d09..90a3e3b 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductService.java +++ b/src/main/java/gov/nasa/podaac/swodlr/l2rasterproduct/L2RasterProductService.java @@ -1,79 +1,101 @@ package gov.nasa.podaac.swodlr.l2rasterproduct; -import gov.nasa.podaac.swodlr.cmr.SwotCmrLookupService; -import gov.nasa.podaac.swodlr.exception.SwodlrException; import gov.nasa.podaac.swodlr.producthistory.ProductHistory; import gov.nasa.podaac.swodlr.producthistory.ProductHistoryRepository; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinitionRepository; +import gov.nasa.podaac.swodlr.queue.ProductCreateQueue; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.State; import gov.nasa.podaac.swodlr.status.Status; import gov.nasa.podaac.swodlr.status.StatusRepository; import gov.nasa.podaac.swodlr.user.User; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import reactor.core.publisher.Mono; @Service public class L2RasterProductService { @Autowired - L2RasterProductRepository l2RasterProductRepository; + private L2RasterProductRepository l2RasterProductRepository; @Autowired - RasterDefinitionRepository rasterDefinitionRepository; + private StatusRepository statusRepository; @Autowired - StatusRepository statusRepository; + private ProductHistoryRepository productHistoryRepository; @Autowired - ProductHistoryRepository productHistoryRepository; - - @Autowired - SwotCmrLookupService swotCmrLookupService; + private ProductCreateQueue productCreateQueue; @Transactional public Mono createL2RasterProduct( User user, - UUID definitionId, int cycle, int pass, - int scene + int scene, + boolean outputGranuleExtentFlag, + GridType outputSamplingGridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust ) { - return Mono - .defer(() -> { - var result = rasterDefinitionRepository.findById(definitionId); - if (!result.isPresent()) { - return Mono.error(new SwodlrException("Definition not found")); - } + L2RasterProduct product = new L2RasterProduct( + cycle, + pass, + scene, + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ); + Status status = new Status(product, State.NEW); + ProductHistory history = new ProductHistory(user, product); + + product = l2RasterProductRepository.save(product); + statusRepository.save(status); + productHistoryRepository.save(history); + + return productCreateQueue.queueProduct(product).thenReturn(product); + } - Map data = new HashMap<>(); - data.put("definition", result.get()); + @Transactional + public Mono getL2RasterProduct( + User requestor, + int cycle, + int pass, + int scene, + boolean outputGranuleExtentFlag, + GridType outputSamplingGridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust + ) { + var productResult = l2RasterProductRepository.findOneByParameters( + cycle, + pass, + scene, + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ); - return Mono.just(data); - }) - .flatMap((Map data) -> { - return swotCmrLookupService.findGranules(cycle, pass, scene) - .map((granules) -> { - data.put("granules", granules); - return data; - }); - }) - .map((Map data) -> { - RasterDefinition definition = (RasterDefinition) data.get("definition"); + if (!productResult.isPresent()) { + return Mono.empty(); + } - L2RasterProduct product = new L2RasterProduct(definition, cycle, pass, scene); - Status status = new Status(product, State.NEW); - ProductHistory history = new ProductHistory(user, product); + L2RasterProduct product = productResult.get(); + ProductHistory history = new ProductHistory(requestor, product); + productHistoryRepository.save(history); - product = l2RasterProductRepository.save(product); - statusRepository.save(status); - productHistoryRepository.save(history); + if (product.getStatuses().get(0).getState() == State.UNAVAILABLE) { + Status status = new Status(product, State.NEW); + statusRepository.save(status); + return productCreateQueue.queueProduct(product).thenReturn(product); + } - return product; - }); + return Mono.just(product); } } diff --git a/src/main/java/gov/nasa/podaac/swodlr/producthistory/ProductHistoryId.java b/src/main/java/gov/nasa/podaac/swodlr/producthistory/ProductHistoryId.java index 83daf46..6300ecf 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/producthistory/ProductHistoryId.java +++ b/src/main/java/gov/nasa/podaac/swodlr/producthistory/ProductHistoryId.java @@ -11,11 +11,11 @@ @Embeddable public class ProductHistoryId implements Serializable { @ManyToOne(optional = false) - @JoinColumn(name = "requestedByID", nullable = false) + @JoinColumn(name = "requestedById", nullable = false) private User requestedBy; @ManyToOne(optional = false) - @JoinColumn(name = "rasterProductID", nullable = false) + @JoinColumn(name = "rasterProductId", nullable = false) private L2RasterProduct rasterProduct; public ProductHistoryId() { } diff --git a/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateMessage.java b/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateMessage.java new file mode 100644 index 0000000..6ccc079 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateMessage.java @@ -0,0 +1,32 @@ +package gov.nasa.podaac.swodlr.queue; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; +import java.util.UUID; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ProductCreateMessage( + @JsonProperty("product_id") UUID productId, + int cycle, + int pass, + int scene, + @JsonProperty("output_granule_extent_flag") Boolean outputGranuleExtentFlag, + @JsonProperty("output_sampling_grid_type") GridType outputSamplingGridType, + @JsonProperty("raster_resolution") Integer rasterResolution, + @JsonProperty("utm_zone_adjust") Integer utmZoneAdjust, + @JsonProperty("mgrs_band_adjust") Integer mgrsBandAdjust +) { + public ProductCreateMessage(L2RasterProduct product) { + this( + product.getId(), + product.getCycle(), + product.getPass(), + product.getScene(), + product.getOutputGranuleExtentFlag(), + product.getOutputSamplingGridType(), + product.getRasterResolution(), + product.getUtmZoneAdjust(), + product.getMgrsBandAdjust() + ); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateQueue.java b/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateQueue.java new file mode 100644 index 0000000..f48312d --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/queue/ProductCreateQueue.java @@ -0,0 +1,8 @@ +package gov.nasa.podaac.swodlr.queue; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import reactor.core.publisher.Mono; + +public interface ProductCreateQueue { + public Mono queueProduct(L2RasterProduct product); +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/queue/SqsProductCreateQueue.java b/src/main/java/gov/nasa/podaac/swodlr/queue/SqsProductCreateQueue.java new file mode 100644 index 0000000..f294925 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/queue/SqsProductCreateQueue.java @@ -0,0 +1,34 @@ +package gov.nasa.podaac.swodlr.queue; + +import gov.nasa.podaac.swodlr.SwodlrProperties; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import io.awspring.cloud.sqs.operations.SqsTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; +import software.amazon.awssdk.services.sqs.SqsAsyncClient; + +@Component +public class SqsProductCreateQueue implements ProductCreateQueue { + private final SqsTemplate productCreateQueue; + + public SqsProductCreateQueue( + @Autowired SqsAsyncClient sqsAsyncClient, + @Autowired SwodlrProperties swodlrProperties + ) { + productCreateQueue = SqsTemplate.builder() + .sqsAsyncClient(sqsAsyncClient) + .configure((options) -> { + options.defaultQueue(swodlrProperties.productCreateQueueUrl()); + }) + .build(); + } + + @Override + public Mono queueProduct(L2RasterProduct product) { + return Mono.defer(() -> { + ProductCreateMessage message = new ProductCreateMessage(product); + return Mono.fromFuture(productCreateQueue.sendAsync(message)).then(); + }); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinition.java b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinition.java index f4ec3b8..abe5276 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinition.java +++ b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinition.java @@ -1,20 +1,35 @@ package gov.nasa.podaac.swodlr.rasterdefinition; +import gov.nasa.podaac.swodlr.user.User; +import gov.nasa.podaac.swodlr.validation.ValidRasterOptions; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; + +import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Range; @Entity @Table(name = "RasterDefinitions") +@ValidRasterOptions public class RasterDefinition { @Id private UUID id; + @ManyToOne(optional = false) + @JoinColumn(name = "userId", nullable = false) + private User user; + + @Column(nullable = false) + @Length(min = 1, max = 100) + private String name; + /* * Flag indicating whether the SAS should produce a non- * overlapping or overlapping granule @@ -23,14 +38,14 @@ public class RasterDefinition { * true - an overlapping, 256 km x 128 km granule extent */ @Column(nullable = false) - public Boolean outputGranuleExtentFlag; + private Boolean outputGranuleExtentFlag; /* * Type of the raster sampling grid */ @Column(nullable = false) @Enumerated(EnumType.STRING) - public GridType outputSamplingGridType; + private GridType outputSamplingGridType; /* * Resolution of the raster sampling grid in units of integer @@ -45,8 +60,7 @@ public class RasterDefinition { * TODO: Validate based on value of outputSamplingGridType + valid values? */ @Column(nullable = false) - @Range(min = 3, max = 10000) - public Integer rasterResolution; + private Integer rasterResolution; /* * This parameter allows the UTM grid to use a zone within @@ -58,8 +72,7 @@ public class RasterDefinition { * TODO: Check if not null only on UTM grids? */ @Column - @Range(min = -1, max = 1) - public Integer utmZoneAdjust; + private Integer utmZoneAdjust; /* * This parameter allows the UTM grid to use an MGRS @@ -72,17 +85,61 @@ public class RasterDefinition { * TODO: Check if not null only on UTM grids? */ @Column - @Range(min = -1, max = 1) - public Integer mgrsBandAdjust; + private Integer mgrsBandAdjust; + + RasterDefinition() { } - public RasterDefinition() { - id = UUID.randomUUID(); + public RasterDefinition( + User user, + String name, + boolean outputGranuleExtentFlag, + GridType outputSamplingGridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust + ) { + this.id = UUID.randomUUID(); + this.user = user; + this.name = name; + this.outputGranuleExtentFlag = outputGranuleExtentFlag; + this.outputSamplingGridType = outputSamplingGridType; + this.rasterResolution = rasterResolution; + this.utmZoneAdjust = utmZoneAdjust; + this.mgrsBandAdjust = mgrsBandAdjust; } public UUID getId() { return id; } + public User getUser() { + return user; + } + + public String getName() { + return name; + } + + public Boolean getOutputGranuleExtentFlag() { + return outputGranuleExtentFlag; + } + + public GridType getOutputSamplingGridType() { + return outputSamplingGridType; + } + + public Integer getRasterResolution() { + return rasterResolution; + } + + public Integer getUtmZoneAdjust() { + return utmZoneAdjust; + } + + public Integer getMgrsBandAdjust() { + return mgrsBandAdjust; + } + public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionController.java b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionController.java index 22c5926..8147e64 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionController.java +++ b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionController.java @@ -1,21 +1,26 @@ package gov.nasa.podaac.swodlr.rasterdefinition; -import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import gov.nasa.podaac.swodlr.exception.SwodlrException; +import gov.nasa.podaac.swodlr.user.User; +import gov.nasa.podaac.swodlr.user.UserReference; import java.util.List; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.graphql.data.method.annotation.Argument; -import org.springframework.graphql.data.method.annotation.QueryMapping; +import org.springframework.graphql.data.method.annotation.ContextValue; +import org.springframework.graphql.data.method.annotation.MutationMapping; import org.springframework.graphql.data.method.annotation.SchemaMapping; import org.springframework.stereotype.Controller; +import reactor.core.publisher.Mono; @Controller public class RasterDefinitionController { @Autowired RasterDefinitionRepository rasterDefinitionRepository; - @QueryMapping - List rasterDefinitions( + @SchemaMapping(typeName = "User", field = "rasterDefinitions") + Mono> getRasterDefinitionsForUser( + @ContextValue UserReference userRef, @Argument UUID id, @Argument Boolean outputGranuleExtentFlag, @Argument GridType outputSamplingGridType, @@ -23,13 +28,53 @@ List rasterDefinitions( @Argument Integer utmZoneAdjust, @Argument Integer mgrsBandAdjust ) { - return rasterDefinitionRepository.findByParameter( - id, outputGranuleExtentFlag, outputSamplingGridType, rasterResolution, - utmZoneAdjust, mgrsBandAdjust); + return Mono.fromCallable(() -> { + User user = userRef.fetch(); + return rasterDefinitionRepository.findByParameter( + user, id, outputGranuleExtentFlag, outputSamplingGridType, + rasterResolution, utmZoneAdjust, mgrsBandAdjust + ); + }); } - @SchemaMapping(typeName = "L2RasterProduct", field = "definition") - RasterDefinition getDefinitionForL2RasterProduct(L2RasterProduct rasterProduct) { - return rasterProduct.getDefinition(); + @MutationMapping + Mono deleteRasterDefinition(@ContextValue UserReference userRef, @Argument UUID id) { + return Mono.fromCallable(() -> { + User user = userRef.fetch(); + var result = rasterDefinitionRepository.findOneByUserAndId(user, id); + if (result.isEmpty()) { + throw new SwodlrException("Raster definition not found"); + } + + rasterDefinitionRepository.delete(result.get()); + return true; + }); + } + + @MutationMapping + Mono createRasterDefinition( + @ContextValue UserReference userRef, + @Argument String name, + @Argument boolean outputGranuleExtentFlag, + @Argument GridType outputSamplingGridType, + @Argument int rasterResolution, + @Argument Integer utmZoneAdjust, + @Argument Integer mgrsBandAdjust + ) { + return Mono.fromCallable(() -> { + User user = userRef.fetch(); + RasterDefinition definition = new RasterDefinition( + user, + name, + outputGranuleExtentFlag, + outputSamplingGridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ); + + definition = rasterDefinitionRepository.save(definition); + return definition; + }); } } diff --git a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQuery.java b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQuery.java index 7e5680a..94d2c55 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQuery.java +++ b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQuery.java @@ -1,10 +1,12 @@ package gov.nasa.podaac.swodlr.rasterdefinition; +import gov.nasa.podaac.swodlr.user.User; import java.util.List; import java.util.UUID; public interface RasterDefinitionQuery { List findByParameter( + User user, UUID id, Boolean outputGranuleExtentFlag, GridType outputSamplingGridType, diff --git a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQueryImpl.java b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQueryImpl.java index 6883c71..b04da1b 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQueryImpl.java +++ b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionQueryImpl.java @@ -1,5 +1,6 @@ package gov.nasa.podaac.swodlr.rasterdefinition; +import gov.nasa.podaac.swodlr.user.User; import java.util.List; import java.util.UUID; import javax.persistence.EntityManager; @@ -44,6 +45,7 @@ public class RasterDefinitionQueryImpl implements RasterDefinitionQuery { */ @Override public List findByParameter( + User user, UUID id, Boolean outputGranuleExtentFlag, GridType outputSamplingGridType, @@ -53,6 +55,7 @@ public List findByParameter( ) { String statement = """ SELECT * FROM \"RasterDefinitions\" WHERE + (\"userId\" = CAST(:userId as UUID)) AND (:id is NULL OR \"id\" = CAST(:id as UUID)) AND (:outputGranuleExtentFlag is NULL OR \"outputGranuleExtentFlag\" = :outputGranuleExtentFlag) AND (:outputSamplingGridType is NULL OR \"outputSamplingGridType\" = :outputSamplingGridType) AND @@ -64,6 +67,7 @@ public List findByParameter( Session session = entityManager.unwrap(Session.class); Query query = session.createNativeQuery(statement, RasterDefinition.class); + query.setParameter("userId", user.getId(), UUIDCharType.INSTANCE); query.setParameter("id", id, UUIDCharType.INSTANCE); query.setParameter("outputGranuleExtentFlag", outputGranuleExtentFlag, BooleanType.INSTANCE); query.setParameter("outputSamplingGridType", outputSamplingGridType != null diff --git a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionRepository.java b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionRepository.java index f705dd7..5d45727 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionRepository.java +++ b/src/main/java/gov/nasa/podaac/swodlr/rasterdefinition/RasterDefinitionRepository.java @@ -1,10 +1,11 @@ package gov.nasa.podaac.swodlr.rasterdefinition; -import java.util.List; +import gov.nasa.podaac.swodlr.user.User; +import java.util.Optional; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; public interface RasterDefinitionRepository extends JpaRepository, RasterDefinitionQuery { - List findById(RasterDefinition definition); + Optional findOneByUserAndId(User user, UUID id); } diff --git a/src/main/java/gov/nasa/podaac/swodlr/security/authentication/handlers/UserBootstrapAuthenticationSuccessHandler.java b/src/main/java/gov/nasa/podaac/swodlr/security/authentication/handlers/UserBootstrapAuthenticationSuccessHandler.java index bf74087..1b2b071 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/security/authentication/handlers/UserBootstrapAuthenticationSuccessHandler.java +++ b/src/main/java/gov/nasa/podaac/swodlr/security/authentication/handlers/UserBootstrapAuthenticationSuccessHandler.java @@ -32,12 +32,24 @@ public Mono onAuthenticationSuccess(WebFilterExchange webFilterExchange, String username = oauth2User.getName(); Optional result = userRepository.findByUsername(username); + String firstName = oauth2User.getAttribute("first_name"); + String lastName = oauth2User.getAttribute("last_name"); + String email = oauth2User.getAttribute("email_address"); + if (result.isPresent()) { + User user = result.get(); + + // Update db with latest info from EDL + user.firstName = firstName; + user.lastName = lastName; + user.email = email; + userRepository.save(user); + UserReference userReference = new UserReference(result.get()); session.getAttributes().put("user", userReference); } else { - User user = new User(username); - userRepository.save(user); + User user = new User(username, email, firstName, lastName); + user = userRepository.save(user); UserReference userReference = new UserReference(user); session.getAttributes().put("user", userReference); diff --git a/src/main/java/gov/nasa/podaac/swodlr/status/Status.java b/src/main/java/gov/nasa/podaac/swodlr/status/Status.java index 8de1580..900ddac 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/status/Status.java +++ b/src/main/java/gov/nasa/podaac/swodlr/status/Status.java @@ -19,7 +19,7 @@ public class Status { private UUID id; @ManyToOne(optional = false) - @JoinColumn(name = "productID", nullable = false) + @JoinColumn(name = "productId", nullable = false) private L2RasterProduct product; @Column(nullable = false) @@ -32,9 +32,7 @@ public class Status { @Column private String reason; - public Status() { - this(null, null); - } + public Status() { } public Status(L2RasterProduct product, State state) { this(product, state, null); diff --git a/src/main/java/gov/nasa/podaac/swodlr/status/StatusController.java b/src/main/java/gov/nasa/podaac/swodlr/status/StatusController.java index 4513b07..76bad6b 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/status/StatusController.java +++ b/src/main/java/gov/nasa/podaac/swodlr/status/StatusController.java @@ -1,6 +1,5 @@ package gov.nasa.podaac.swodlr.status; -import gov.nasa.podaac.swodlr.Utils; import gov.nasa.podaac.swodlr.exception.SwodlrException; import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProductRepository; @@ -28,7 +27,7 @@ List statusByProduct(@Argument UUID product, @Argument int limit) { throw new SwodlrException("Invalid `product` parameter"); } - return statusRepository.findByProductId(result.get(), Utils.NULL_UUID, limit); + return statusRepository.findByProductId(result.get(), null, limit); } @QueryMapping @@ -42,7 +41,7 @@ List statusByPrevious(@Argument UUID after, @Argument int limit) { } @SchemaMapping(typeName = "L2RasterProduct", field = "status") - List getStatusForL2RasterProduct(L2RasterProduct product, @Argument int limit) { - return statusRepository.findByProductId(product, Utils.NULL_UUID, limit); + List getStatusForL2RasterProduct(L2RasterProduct product, @Argument UUID after, @Argument int limit) { + return statusRepository.findByProductId(product, after, limit); } } diff --git a/src/main/java/gov/nasa/podaac/swodlr/status/StatusQuery.java b/src/main/java/gov/nasa/podaac/swodlr/status/StatusQuery.java new file mode 100644 index 0000000..c05cc38 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/status/StatusQuery.java @@ -0,0 +1,9 @@ +package gov.nasa.podaac.swodlr.status; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import java.util.List; +import java.util.UUID; + +public interface StatusQuery { + List findByProductId(L2RasterProduct product, UUID after, int limit); +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/status/StatusQueryImpl.java b/src/main/java/gov/nasa/podaac/swodlr/status/StatusQueryImpl.java new file mode 100644 index 0000000..7aeb849 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/status/StatusQueryImpl.java @@ -0,0 +1,66 @@ +package gov.nasa.podaac.swodlr.status; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import java.util.List; +import java.util.UUID; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import org.hibernate.Session; +import org.hibernate.query.Query; +import org.hibernate.type.IntegerType; +import org.hibernate.type.UUIDCharType; + +public class StatusQueryImpl implements StatusQuery { + @PersistenceContext + private EntityManager entityManager; + + /* + * This implementation is utilized to workaround an JPA issue with the + * PostgreSQL dialect in Hibernate where Hibernate does not parameterize + * null values as their original datatype and instead parameterizes as + * bytea's. This is a known issue of the PostgreSQL driver/dialect + * + * This can lead to the exceptions: + * - "ERROR: could not determine data type of parameter $1" + * - "ERROR: operator does not exist: uuid = bytea" + * + * Solutions researched include casting values, however, by casting + * in the query, we loose the benefits of parameterization in our + * prepared statements and create a statement which is harder to read. + * Workarounds such as COALESCE and testing other PostgreSQL dialects + * available in Hibernate were tried, but were found to either not + * solve the issue or still require the use of CASTs in queries + * + * The hope is to one day remove this code in favor of Spring Data JPA + * queries, pending the PostgreSQL/Hibernate teams' cooperation with + * one another + * + * Relevant discussions: + * - https://stackoverflow.com/a/64223435 + * - https://stackoverflow.com/a/62680643 + * - https://github.com/pgjdbc/pgjdbc/issues/247#issuecomment-78213991 + */ + @Override + public List findByProductId(L2RasterProduct product, UUID after, int limit) { + String statement = """ + SELECT * FROM \"Status\" + WHERE + (\"productId\" = CAST(:productId AS UUID)) + AND + ( + (:after is NULL) + OR + ((timestamp, id) < (SELECT timestamp, id FROM \"Status\" WHERE id = CAST(:after as UUID))) + ) + ORDER BY timestamp DESC, id DESC LIMIT :limit + """; + + Session session = entityManager.unwrap(Session.class); + Query query = session.createNativeQuery(statement, Status.class); + query.setParameter("productId", product.getId(), UUIDCharType.INSTANCE); + query.setParameter("after", after, UUIDCharType.INSTANCE); + query.setParameter("limit", limit, IntegerType.INSTANCE); + + return query.getResultList(); + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/status/StatusRepository.java b/src/main/java/gov/nasa/podaac/swodlr/status/StatusRepository.java index beefd40..7f2d130 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/status/StatusRepository.java +++ b/src/main/java/gov/nasa/podaac/swodlr/status/StatusRepository.java @@ -1,26 +1,7 @@ package gov.nasa.podaac.swodlr.status; -import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; -import java.util.List; import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -public interface StatusRepository extends JpaRepository { - @Query( - value = """ - SELECT * FROM \"Status\" - WHERE - \"productID\" = :#{#product.getId()} - AND - ( - :#{#after} = CAST('00000000-0000-0000-0000-000000000000' as uuid) - OR - (timestamp, id) < (SELECT timestamp, id FROM \"Status\" WHERE id = :#{#after}) - ) - ORDER BY timestamp DESC, id DESC LIMIT :#{#limit} - """, - nativeQuery = true - ) - List findByProductId(L2RasterProduct product, UUID after, int limit); -} + +public interface StatusRepository extends JpaRepository, StatusQuery { } diff --git a/src/main/java/gov/nasa/podaac/swodlr/user/User.java b/src/main/java/gov/nasa/podaac/swodlr/user/User.java index 6cb1fa2..6c55241 100644 --- a/src/main/java/gov/nasa/podaac/swodlr/user/User.java +++ b/src/main/java/gov/nasa/podaac/swodlr/user/User.java @@ -1,14 +1,9 @@ package gov.nasa.podaac.swodlr.user; -import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; -import java.util.Set; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinTable; -import javax.persistence.ManyToMany; import javax.persistence.Table; @Entity @@ -20,22 +15,30 @@ public class User { @Column(unique = true) private String username; - @ManyToMany - @JoinTable( - name = "ProductHistory", - joinColumns = @JoinColumn(name = "requestedBy"), - inverseJoinColumns = @JoinColumn(name = "rasterProduct") - ) - Set productHistory; + @Column(nullable = false) + public String email; + + @Column(nullable = false) + public String firstName; + + @Column(nullable = false) + public String lastName; public User() { } - public User(String username) { + public User(String username, String email, String firstName, String lastName) { id = UUID.randomUUID(); this.username = username; + this.email = email; + this.firstName = firstName; + this.lastName = lastName; } public UUID getId() { return id; } + + public String getUsername() { + return username; + } } diff --git a/src/main/java/gov/nasa/podaac/swodlr/validation/AbstractRasterOptionValidator.java b/src/main/java/gov/nasa/podaac/swodlr/validation/AbstractRasterOptionValidator.java new file mode 100644 index 0000000..39a44aa --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/validation/AbstractRasterOptionValidator.java @@ -0,0 +1,119 @@ +package gov.nasa.podaac.swodlr.validation; + +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; +import java.util.Map; +import java.util.Set; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; + +public abstract class AbstractRasterOptionValidator implements ConstraintValidator { + static final Set VALID_GEO_RESOLUTIONS = Set.of( + 3, 4, 5, 6, 8, 15, 30, 60, 180, 300 + ); + + static final Set VALID_UTM_RESOLUTIONS = Set.of( + 100, 125, 200, 250, 500, 1000, 2500, 5000, 10000 + ); + + static final Set VALID_ADJUSTS = Set.of(-1, 0, 1); + + public abstract boolean isValid(T value, ConstraintValidatorContext context); + + boolean validateCps(int cycle, int pass, int scene, ConstraintValidatorContext context) { + Map parameters = Map.ofEntries( + Map.entry("cycle", cycle), + Map.entry("pass", pass), + Map.entry("scene", scene) + ); + + boolean valid = true; + var it = parameters.entrySet().iterator(); + + while (it.hasNext()) { + var entry = it.next(); + String paramName = entry.getKey(); + int value = entry.getValue(); + + if (value < 0 || value > 999) { + valid = false; + + context + .buildConstraintViolationWithTemplate("must be >= 0 and < 1000") + .addPropertyNode(paramName) + .addConstraintViolation(); + } + } + + return valid; + } + + boolean validateParameters( + GridType gridType, + int rasterResolution, + Integer utmZoneAdjust, + Integer mgrsBandAdjust, + ConstraintValidatorContext context + ) { + boolean valid = true; + + if (gridType == GridType.UTM) { + if (!VALID_UTM_RESOLUTIONS.contains(rasterResolution)) { + valid = false; + context + .buildConstraintViolationWithTemplate( + "must be a valid resolution according to the grid type") + .addPropertyNode("rasterResolution") + .addConstraintViolation(); + } + + if (utmZoneAdjust == null || !VALID_ADJUSTS.contains(utmZoneAdjust)) { + valid = false; + context + .buildConstraintViolationWithTemplate("must be one of: -1, 0, -1") + .addPropertyNode("utmZoneAdjust") + .addConstraintViolation(); + } + + if (mgrsBandAdjust == null || !VALID_ADJUSTS.contains(mgrsBandAdjust)) { + valid = false; + context + .buildConstraintViolationWithTemplate("must be one of: -1, 0, -1") + .addPropertyNode("mgrsBandAdjust") + .addConstraintViolation(); + } + } else if (gridType == GridType.GEO) { + if (!VALID_GEO_RESOLUTIONS.contains(rasterResolution)) { + valid = false; + context + .buildConstraintViolationWithTemplate("must be a valid resolution according to the grid type") + .addPropertyNode("rasterResolution") + .addConstraintViolation(); + } + + if (utmZoneAdjust != null) { + valid = false; + context + .buildConstraintViolationWithTemplate("not applicable to GEO rasters") + .addPropertyNode("utmZoneAdjust") + .addConstraintViolation(); + } + + if (mgrsBandAdjust != null) { + valid = false; + context + .buildConstraintViolationWithTemplate("not applicable to GEO rasters") + .addPropertyNode("mgrsBandAdjust") + .addConstraintViolation(); + } + } else { + context + .buildConstraintViolationWithTemplate("must be one of: UTM or GEO") + .addPropertyNode("gridType") + .addConstraintViolation(); + + valid = false; + } + + return valid; + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/validation/ValidRasterOptions.java b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidRasterOptions.java new file mode 100644 index 0000000..8fd4b17 --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidRasterOptions.java @@ -0,0 +1,24 @@ +package gov.nasa.podaac.swodlr.validation; + +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import javax.validation.Constraint; +import javax.validation.Payload; + +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = { + ValidateL2RasterProductOptions.class, + ValidateRasterDefinitionOptions.class +}) +@Documented +public @interface ValidRasterOptions { + String message() default "Raster options invalid"; + + Class[] groups() default { }; + + Class[] payload() default { }; +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateL2RasterProductOptions.java b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateL2RasterProductOptions.java new file mode 100644 index 0000000..c08de9a --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateL2RasterProductOptions.java @@ -0,0 +1,30 @@ +package gov.nasa.podaac.swodlr.validation; + +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import javax.validation.ConstraintValidatorContext; + +public class ValidateL2RasterProductOptions extends AbstractRasterOptionValidator { + @Override + public boolean isValid(L2RasterProduct product, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + + boolean valid = validateCps( + product.getCycle(), + product.getPass(), + product.getScene(), + context + ); + + if (!validateParameters( + product.getOutputSamplingGridType(), + product.getRasterResolution(), + product.getUtmZoneAdjust(), + product.getMgrsBandAdjust(), + context + )) { + valid = false; + } + + return valid; + } +} diff --git a/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateRasterDefinitionOptions.java b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateRasterDefinitionOptions.java new file mode 100644 index 0000000..867fe8e --- /dev/null +++ b/src/main/java/gov/nasa/podaac/swodlr/validation/ValidateRasterDefinitionOptions.java @@ -0,0 +1,19 @@ +package gov.nasa.podaac.swodlr.validation; + +import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; +import javax.validation.ConstraintValidatorContext; + +public class ValidateRasterDefinitionOptions extends AbstractRasterOptionValidator { + @Override + public boolean isValid(RasterDefinition definition, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + + return validateParameters( + definition.getOutputSamplingGridType(), + definition.getRasterResolution(), + definition.getUtmZoneAdjust(), + definition.getMgrsBandAdjust(), + context + ); + } +} diff --git a/src/main/resources/graphql/granule.graphqls b/src/main/resources/graphql/granule.graphqls new file mode 100644 index 0000000..6ee7d06 --- /dev/null +++ b/src/main/resources/graphql/granule.graphqls @@ -0,0 +1,5 @@ +type Granule { + id: ID! + timestamp: String! + uri: String! +} diff --git a/src/main/resources/graphql/l2_raster_product.graphqls b/src/main/resources/graphql/l2_raster_product.graphqls index ec2e3b2..8492421 100644 --- a/src/main/resources/graphql/l2_raster_product.graphqls +++ b/src/main/resources/graphql/l2_raster_product.graphqls @@ -1,8 +1,15 @@ type L2RasterProduct { id: ID! - definition: RasterDefinition! + timestamp: String! cycle: Int! pass: Int! scene: Int! - status(limit: Int = 10): [Status!]! + outputGranuleExtentFlag: Boolean! + outputSamplingGridType: GridType! + rasterResolution: Int! + utmZoneAdjust: Int + mgrsBandAdjust: Int + + granules: [Granule!]! + status(after: ID, limit: Int = 10): [Status!]! } diff --git a/src/main/resources/graphql/mutation.graphqls b/src/main/resources/graphql/mutation.graphqls index 6e39b5e..649b03d 100644 --- a/src/main/resources/graphql/mutation.graphqls +++ b/src/main/resources/graphql/mutation.graphqls @@ -1,3 +1,24 @@ type Mutation { - createL2RasterProduct(definition: ID!, cycle: Int!, pass: Int!, scene: Int!): L2RasterProduct! + # -- Product -- + generateL2RasterProduct( + cycle: Int!, + pass: Int!, + scene: Int!, + outputGranuleExtentFlag: Boolean!, + outputSamplingGridType: GridType!, + rasterResolution: Int!, + utmZoneAdjust: Int, + mgrsBandAdjust: Int + ): L2RasterProduct! + + # -- Raster Definitions -- + deleteRasterDefinition(id: ID!): Boolean! + createRasterDefinition( + name: String!, + outputGranuleExtentFlag: Boolean!, + outputSamplingGridType: GridType!, + rasterResolution: Int!, + utmZoneAdjust: Int, + mgrsBandAdjust: Int + ): RasterDefinition! } diff --git a/src/main/resources/graphql/query.graphqls b/src/main/resources/graphql/query.graphqls index d0ee40a..f20e64a 100644 --- a/src/main/resources/graphql/query.graphqls +++ b/src/main/resources/graphql/query.graphqls @@ -1,6 +1,6 @@ type Query { currentUser: User + l2RasterProduct(id: ID!): L2RasterProduct statusByProduct(product: ID!, limit: Int = 10): [Status!] statusByPrevious(after: ID!, limit: Int = 10): [Status!] - rasterDefinitions(id: ID, outputGranuleExtentFlag: Boolean, outputSamplingGridType: GridType, rasterResolution: Int, utmZoneAdjust: Int, mgrsBandAdjust: Int): [RasterDefinition!] } diff --git a/src/main/resources/graphql/raster_definition.graphqls b/src/main/resources/graphql/raster_definition.graphqls index 85e4701..654ddc9 100644 --- a/src/main/resources/graphql/raster_definition.graphqls +++ b/src/main/resources/graphql/raster_definition.graphqls @@ -1,5 +1,6 @@ type RasterDefinition { id: ID! + name: String! outputGranuleExtentFlag: Boolean! outputSamplingGridType: GridType! rasterResolution: Int! diff --git a/src/main/resources/graphql/user.graphqls b/src/main/resources/graphql/user.graphqls index 32d9ae7..a694315 100644 --- a/src/main/resources/graphql/user.graphqls +++ b/src/main/resources/graphql/user.graphqls @@ -1,4 +1,16 @@ type User { id: ID! - products(after: ID, limit: Int = 10): [L2RasterProduct!] -} \ No newline at end of file + email: String! + firstName: String! + lastName: String! + + products(after: ID, limit: Int = 10): [L2RasterProduct!]! + rasterDefinitions( + id: ID, + outputGranuleExtentFlag: Boolean, + outputSamplingGridType: GridType, + rasterResolution: Int, + utmZoneAdjust: Int, + mgrsBandAdjust: Int + ): [RasterDefinition!]! +} diff --git a/src/test/java/gov/nasa/podaac/swodlr/GranuleTests.java b/src/test/java/gov/nasa/podaac/swodlr/GranuleTests.java new file mode 100644 index 0000000..12a6fd4 --- /dev/null +++ b/src/test/java/gov/nasa/podaac/swodlr/GranuleTests.java @@ -0,0 +1,82 @@ +package gov.nasa.podaac.swodlr; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import gov.nasa.podaac.swodlr.granule.Granule; +import gov.nasa.podaac.swodlr.granule.GranuleRepository; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProductRepository; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.graphql.test.tester.HttpGraphQlTester; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles({"test"}) +@TestInstance(Lifecycle.PER_CLASS) +@TestPropertySource({"file:./src/main/resources/application.properties", "classpath:application.properties"}) +@AutoConfigureHttpGraphQlTester +public class GranuleTests { + private L2RasterProduct mockProduct; + + @Autowired + private GranuleRepository granuleRepository; + + @Autowired + private L2RasterProductRepository productRepository; + + @Autowired + private HttpGraphQlTester graphQlTester; + + @BeforeAll + void initMocks() { + mockProduct = new L2RasterProduct( + 0, + 0, + 0, + false, + GridType.UTM, + 1000, + 0, + 0 + ); + + mockProduct = productRepository.save(mockProduct); + } + + @AfterAll + void tearDownMocks() { + productRepository.deleteAll(); + } + + @AfterEach + void deleteAllGranules() { + granuleRepository.deleteAll(); + } + + @Test + public void convertS3UriToTeaUri() { + Granule granule = new Granule(mockProduct, "s3://test-bucket/path"); + granule = granuleRepository.save(granule); + + graphQlTester + .documentName("query/l2RasterProduct_granules") + .variable("id", mockProduct.getId()) + .execute() + .path("l2RasterProduct.granules[0].uri") + .entity(String.class) + .satisfies(uri -> + assertTrue(uri.equals("https://earl-grey/test-bucket/path")) + ); + } +} diff --git a/src/test/java/gov/nasa/podaac/swodlr/L2RasterProductTests.java b/src/test/java/gov/nasa/podaac/swodlr/L2RasterProductTests.java index ca2e40b..f08fdfc 100644 --- a/src/test/java/gov/nasa/podaac/swodlr/L2RasterProductTests.java +++ b/src/test/java/gov/nasa/podaac/swodlr/L2RasterProductTests.java @@ -3,21 +3,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProductRepository; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinitionRepository; +import gov.nasa.podaac.swodlr.queue.ProductCreateQueue; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.State; +import reactor.core.publisher.Mono; import java.time.LocalDateTime; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Random; +import java.util.Map; import java.util.Set; import java.util.UUID; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -25,6 +29,8 @@ import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.graphql.test.tester.GraphQlTester.Response; import org.springframework.graphql.test.tester.HttpGraphQlTester; import org.springframework.test.context.ActiveProfiles; @@ -36,26 +42,19 @@ @TestPropertySource({"file:./src/main/resources/application.properties", "classpath:application.properties"}) @AutoConfigureHttpGraphQlTester public class L2RasterProductTests { - private RasterDefinition definition; - @Autowired private HttpGraphQlTester graphQlTester; @Autowired private L2RasterProductRepository l2RasterProductRepository; - @Autowired - private RasterDefinitionRepository rasterDefinitionRepository; - - @BeforeAll - public void setupDefinition() { - definition = TestUtils.dummyDefinition(); - rasterDefinitionRepository.save(definition); - } + @MockBean + private ProductCreateQueue productCreateQueue; - @AfterAll - public void deleteDefinition() { - rasterDefinitionRepository.delete(definition); + @BeforeEach + public void initMock() { + when(productCreateQueue.queueProduct(any(L2RasterProduct.class))) + .thenReturn(Mono.empty()); } @AfterEach @@ -67,29 +66,50 @@ public void deleteProducts() { public void createL2RasterProductWithValidDefinition() { LocalDateTime start = LocalDateTime.now(); - Random random = new Random(); - int cycle = random.nextInt(Integer.MAX_VALUE); - int pass = random.nextInt(Integer.MAX_VALUE); - int scene = random.nextInt(Integer.MAX_VALUE); + int cycle = 1; + int pass = 2; + int scene = 3; + boolean outputGranuleExtentFlag = true; + GridType gridType = GridType.UTM; + int rasterResolution = 1000; + int utmZoneAdjust = 1; + int mgrsBandAdjust = -1; Response response = graphQlTester - .documentName("mutation/createL2RasterProduct") - .variable("definition", definition.getId()) + .documentName("mutation/generateL2RasterProduct") .variable("cycle", cycle) .variable("pass", pass) .variable("scene", scene) + .variable("outputGranuleExtentFlag", outputGranuleExtentFlag) + .variable("outputSamplingGridType", gridType) + .variable("rasterResolution", rasterResolution) + .variable("utmZoneAdjust", utmZoneAdjust) + .variable("mgrsBandAdjust", mgrsBandAdjust) .execute(); - /* -- Definition -- */ + /* -- Product -- */ + // Check product options response - .path("createL2RasterProduct.definition.id") - .entity(UUID.class) - .isEqualTo(definition.getId()); + .path("generateL2RasterProduct") + .entity(new ParameterizedTypeReference>() {}) + .satisfies(product -> { + assertEquals(product.get("cycle"), cycle); + assertEquals(product.get("pass"), pass); + assertEquals(product.get("scene"), scene); + assertEquals(product.get("outputGranuleExtentFlag"), outputGranuleExtentFlag); + assertEquals(product.get("outputSamplingGridType"), gridType.toString()); + assertEquals(product.get("rasterResolution"), rasterResolution); + assertEquals(product.get("utmZoneAdjust"), utmZoneAdjust); + assertEquals(product.get("mgrsBandAdjust"), mgrsBandAdjust); + }); + // Verify queue invoked on new product + verify(productCreateQueue).queueProduct(any()); + /* -- Status -- */ // Timestamp response - .path("createL2RasterProduct.status[*].timestamp") + .path("generateL2RasterProduct.status[*].timestamp") .entityList(LocalDateTime.class) .hasSize(1) .satisfies(timestamps -> { @@ -99,14 +119,14 @@ public void createL2RasterProductWithValidDefinition() { // State response - .path("createL2RasterProduct.status[*].state") + .path("generateL2RasterProduct.status[*].state") .entityList(String.class) .hasSize(1) .containsExactly(State.NEW.toString()); // Reason response - .path("createL2RasterProduct.status[*].reason") + .path("generateL2RasterProduct.status[*].reason") .entityList(Object.class) .containsExactly(new Object[] {null}); } @@ -118,14 +138,17 @@ public void createL2RasterProductWithInvalidCps() { )); graphQlTester - .documentName("mutation/createL2RasterProduct") - .variable("definition", definition.getId()) + .documentName("mutation/generateL2RasterProduct") .variable("cycle", -1) .variable("pass", -1) .variable("scene", -1) + .variable("outputGranuleExtentFlag", false) + .variable("outputSamplingGridType", GridType.GEO) + .variable("rasterResolution", 8) .execute() .errors() .satisfy(errors -> { + System.out.println(errors); assertEquals( invalidParams.size(), errors.size(), @@ -133,9 +156,9 @@ public void createL2RasterProductWithInvalidCps() { ); for (var error : errors) { - assertEquals("createL2RasterProduct", error.getPath()); + assertEquals("generateL2RasterProduct", error.getPath()); assertEquals("ValidationError", error.getExtensions().get("classification")); - assertEquals("must be greater than or equal to 0", error.getMessage()); + assertEquals("must be >= 0 and < 1000", error.getMessage()); var prop = error.getExtensions().get("property"); assertTrue(invalidParams.contains(prop)); @@ -147,22 +170,34 @@ public void createL2RasterProductWithInvalidCps() { } @Test - public void createL2RasterProductWithInvalidDefinition() { + public void generateL2RasterProductWithInvalidParameters() { graphQlTester - .documentName("mutation/createL2RasterProduct") - .variable("definition", Utils.NULL_UUID) + .documentName("mutation/generateL2RasterProduct") .variable("cycle", 0) .variable("pass", 0) .variable("scene", 0) + .variable("outputGranuleExtentFlag", false) + .variable("outputSamplingGridType", "UTM") + .variable("rasterResolution", -1) + .variable("mgrsBandAdjust", -2) + .variable("utmZoneAdjust", 2) .execute() .errors() .satisfy(errors -> { - assertEquals(1, errors.size()); + final Set expectedProperties = new HashSet<>(); + Collections.addAll( + expectedProperties, + "rasterResolution", + "mgrsBandAdjust", + "utmZoneAdjust" + ); - var error = errors.get(0); - assertEquals("createL2RasterProduct", error.getPath()); - assertEquals("DataFetchingException", error.getExtensions().get("classification")); - assertEquals("Definition not found", error.getMessage()); + assertEquals(expectedProperties.size(), errors.size()); + for (var error : errors) { + assertEquals("generateL2RasterProduct", error.getPath()); + assertEquals("ValidationError", error.getExtensions().get("classification")); + assertTrue(expectedProperties.remove(error.getExtensions().get("property"))); + } }); } @@ -173,17 +208,18 @@ public void queryCurrentUsersProducts() { LocalDateTime start = LocalDateTime.now(); - // Create new mock products to fill pages for pagination - RasterDefinition definition = TestUtils.dummyDefinition(); - rasterDefinitionRepository.save(definition); + final GridType gridType = GridType.GEO; + final int rasterResolution = 8; for (int i = 0; i < pageLimit * pages; i++) { graphQlTester - .documentName("mutation/createL2RasterProduct") - .variable("definition", definition.getId()) + .documentName("mutation/generateL2RasterProduct") .variable("cycle", i) .variable("pass", i) .variable("scene", i) + .variable("outputGranuleExtentFlag", false) + .variable("outputSamplingGridType", gridType) + .variable("rasterResolution", rasterResolution) .executeAndVerify(); } @@ -193,6 +229,8 @@ public void queryCurrentUsersProducts() { // Iterate through pages for (int i = 0; i < pages; i++) { + final int j = (pageLimit * (pages - i)); + Response response = graphQlTester .documentName("query/currentUser_products") .variable("after", afterId) @@ -212,43 +250,67 @@ public void queryCurrentUsersProducts() { }) .get() .get(pageLimit - 1); - - /* -- Definitions -- */ - response - .path("currentUser.products[*].definition.id") - .entityList(UUID.class) - .containsExactly(Collections.nCopies(pageLimit, definition.getId()).toArray(UUID[]::new)); - - /* -- Cycle -- */ + + /* -- cycle -- */ response .path("currentUser.products[*].cycle") .entityList(Integer.class) .satisfies(ids -> { - int j = pages; + int k = j; for (int id : ids) { - assertEquals(--j, id); + assertEquals(--k, id); } }); - /* -- Pass -- */ + /* -- pass -- */ response .path("currentUser.products[*].pass") .entityList(Integer.class) .satisfies(ids -> { - int j = pages; + int k = j; for (int id : ids) { - assertEquals(--j, id); + assertEquals(--k, id); } }); - /* -- Scene -- */ + /* -- scene -- */ response .path("currentUser.products[*].scene") .entityList(Integer.class) .satisfies(ids -> { - int j = pages; + int k = j; for (int id : ids) { - assertEquals(--j, id); + assertEquals(--k, id); + } + }); + + /* -- outputGranuleExtentFlag -- */ + response + .path("currentUser.products[*].outputGranuleExtentFlag") + .entityList(Boolean.class) + .satisfies(flags -> { + for (var flag : flags) { + assertEquals(flag, false); + } + }); + + /* -- outputSamplingGridType -- */ + response + .path("currentUser.products[*].outputSamplingGridType") + .entityList(String.class) + .satisfies(gridTypes -> { + for (var testGridType : gridTypes) { + assertEquals(testGridType, gridType.toString()); + } + }); + + /* -- rasterResolution -- */ + response + .path("currentUser.products[*].rasterResolution") + .entityList(Integer.class) + .satisfies(rasterResolutions -> { + for (var testRasterResolution : rasterResolutions) { + assertEquals(testRasterResolution, rasterResolution); } }); diff --git a/src/test/java/gov/nasa/podaac/swodlr/RasterDefinitionTests.java b/src/test/java/gov/nasa/podaac/swodlr/RasterDefinitionTests.java index 429ed36..63dd4bb 100644 --- a/src/test/java/gov/nasa/podaac/swodlr/RasterDefinitionTests.java +++ b/src/test/java/gov/nasa/podaac/swodlr/RasterDefinitionTests.java @@ -6,7 +6,7 @@ import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinitionRepository; -import graphql.com.google.common.collect.Lists; +import gov.nasa.podaac.swodlr.user.User; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -14,7 +14,8 @@ import java.util.Random; import java.util.Set; import java.util.UUID; -import org.junit.jupiter.api.BeforeEach; + +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -35,27 +36,93 @@ public class RasterDefinitionTests { @Autowired private HttpGraphQlTester graphQlTester; + @Autowired + private User mockUser; + @Autowired private RasterDefinitionRepository rasterDefinitionRepository; - @BeforeEach + @AfterEach public void clearDefinitions() { rasterDefinitionRepository.deleteAll(); } + @Test + public void deleteDefinition() { + // Generate definition + RasterDefinition definition = new RasterDefinition( + mockUser, + "Test Definition", + false, + GridType.GEO, + 8, + null, + null + ); + rasterDefinitionRepository.save(definition); + + // Delete definition + graphQlTester + .documentName("mutation/deleteRasterDefinition") + .variable("id", definition.getId()) + .executeAndVerify(); + + // Verify removal in database + var result = rasterDefinitionRepository.findById(definition.getId()); + assertTrue(result.isEmpty()); + } + + @Test + public void createDefinition() { + final String name = "Test Definition"; + final boolean outputGranuleExtentFlag = false; + final GridType outputSamplingGridType = GridType.UTM; + final int rasterResolution = 100; + final int utmZoneAdjust = -1; + final int mgrsBandAdjust = 1; + + var response = graphQlTester + .documentName("mutation/createRasterDefinition") + .variable("name", name) + .variable("outputGranuleExtentFlag", outputGranuleExtentFlag) + .variable("outputSamplingGridType", outputSamplingGridType) + .variable("rasterResolution", rasterResolution) + .variable("utmZoneAdjust", utmZoneAdjust) + .variable("mgrsBandAdjust", mgrsBandAdjust) + .execute(); + + List definitions = rasterDefinitionRepository.findAll(); + assertEquals(1, definitions.size()); + + RasterDefinition definition = definitions.get(0); + assertEquals(definition.getId(), response.path("createRasterDefinition.id").entity(UUID.class).get()); + assertEquals(definition.getOutputGranuleExtentFlag(), outputGranuleExtentFlag); + assertEquals(definition.getOutputSamplingGridType().toString(), outputSamplingGridType.toString()); + assertEquals(definition.getRasterResolution(), rasterResolution); + assertEquals(definition.getUtmZoneAdjust(), utmZoneAdjust); + assertEquals(definition.getMgrsBandAdjust(), mgrsBandAdjust); + } + @Test public void queryRasterDefinitionsWithoutArgs() { - var utmDefinition = new RasterDefinition(); - utmDefinition.outputGranuleExtentFlag = true; - utmDefinition.outputSamplingGridType = GridType.UTM; - utmDefinition.rasterResolution = 10000; - utmDefinition.utmZoneAdjust = -1; - utmDefinition.mgrsBandAdjust = 1; - - var geoDefinition = new RasterDefinition(); - geoDefinition.outputGranuleExtentFlag = false; - geoDefinition.outputSamplingGridType = GridType.GEO; - geoDefinition.rasterResolution = 3; + var utmDefinition = new RasterDefinition( + mockUser, + "utm-definition", + true, + GridType.UTM, + 10000, + -1, + 1 + ); + var geoDefinition = new RasterDefinition( + mockUser, + "geo-definition", + false, + GridType.GEO, + 3, + null, + null + ); rasterDefinitionRepository.save(utmDefinition); rasterDefinitionRepository.save(geoDefinition); @@ -65,9 +132,9 @@ public void queryRasterDefinitionsWithoutArgs() { validUuids.add(geoDefinition.getId()); graphQlTester - .documentName("query/rasterDefinitions") + .documentName("query/currentUser_rasterDefinitions") .execute() - .path("rasterDefinitions[*].id") + .path("currentUser.rasterDefinitions[*].id") .entityList(UUID.class) .satisfies(uuidList -> { for (UUID uuid : uuidList) { @@ -80,22 +147,37 @@ public void queryRasterDefinitionsWithoutArgs() { public void queryRasterDefinitionsWithArgs() { final int numDefinitions = 20; final Random random = new Random(); - final List gridTypes = Lists.newArrayList(GridType.values()); + final GridType[] gridTypes = GridType.values(); + final int[] validGeoResolutions = {3, 4, 5, 6, 8, 15, 30, 60, 180, 300}; + final int[] validUtmResolutions = {100, 125, 200, 250, 500, 1000, 2500, 5000, 10000}; + final Map definitions = new HashMap<>(); final String[] parameters = {"id", "outputGranuleExtentFlag", "outputSamplingGridType", "rasterResolution", "utmZoneAdjust", "mgrsBandAdjust"}; for (int i = 0; i < numDefinitions; i++) { - RasterDefinition definition = new RasterDefinition(); - definition.outputGranuleExtentFlag = random.nextBoolean(); - definition.outputSamplingGridType = gridTypes.get(random.nextInt(2)); - definition.rasterResolution = random.nextInt(3, 10000 + 1); - - if (definition.outputSamplingGridType == GridType.UTM) { - definition.utmZoneAdjust = random.nextInt(-1, 1 + 1); - definition.mgrsBandAdjust = random.nextInt(-1, 1 + 1); + GridType gridType = gridTypes[random.nextInt(gridTypes.length)]; + Integer utmZoneAdjust = null; + Integer mgrsBandAdjust = null; + Integer rasterResolution; + if (gridType == GridType.UTM) { + rasterResolution = validUtmResolutions[random.nextInt(validUtmResolutions.length)]; + utmZoneAdjust = random.nextInt(-1, 1 + 1); + mgrsBandAdjust = random.nextInt(-1, 1 + 1); + } else { + rasterResolution = validGeoResolutions[random.nextInt(validGeoResolutions.length)]; } + RasterDefinition definition = new RasterDefinition( + mockUser, + UUID.randomUUID().toString(), + random.nextBoolean(), + gridType, + rasterResolution, + utmZoneAdjust, + mgrsBandAdjust + ); + definitions.put(definition.getId(), definition); rasterDefinitionRepository.save(definition); } @@ -110,10 +192,10 @@ public void queryRasterDefinitionsWithArgs() { } graphQlTester - .documentName("query/rasterDefinitions") + .documentName("query/currentUser_rasterDefinitions") .variable(paramName, getDefinitionField(paramName, definition)) .execute() - .path("rasterDefinitions[*].id") + .path("currentUser.rasterDefinitions[*].id") .entityList(UUID.class) .satisfies(uuidList -> { assertTrue(uuidList.contains(definition.getId())); @@ -134,16 +216,16 @@ public void queryRasterDefinitionsWithArgs() { for (boolean extentVal : testExtentVals) { for (GridType gridType : testGridSamplingTypes) { graphQlTester - .documentName("query/rasterDefinitions") + .documentName("query/currentUser_rasterDefinitions") .variable("outputGranuleExtentFlag", extentVal) .variable("outputSamplingGridType", gridType) .execute() - .path("rasterDefinitions[*].id") + .path("currentUser.rasterDefinitions[*].id") .entityList(UUID.class) .satisfies(uuidList -> { for (UUID uuid : uuidList) { - var testExtentFlag = definitions.get(uuid).outputGranuleExtentFlag; - var testGridType = definitions.get(uuid).outputSamplingGridType; + var testExtentFlag = definitions.get(uuid).getOutputGranuleExtentFlag(); + var testGridType = definitions.get(uuid).getOutputSamplingGridType(); assertEquals(extentVal, testExtentFlag, "outputGranuleExtentFlag: %s != %s".formatted(extentVal, testExtentFlag)); @@ -164,15 +246,15 @@ private Object getDefinitionField(String name, RasterDefinition definition) { case "id": return definition.getId(); case "outputGranuleExtentFlag": - return definition.outputGranuleExtentFlag; + return definition.getOutputGranuleExtentFlag(); case "outputSamplingGridType": - return definition.outputSamplingGridType; + return definition.getOutputSamplingGridType(); case "rasterResolution": - return definition.rasterResolution; + return definition.getRasterResolution(); case "utmZoneAdjust": - return definition.utmZoneAdjust; + return definition.getUtmZoneAdjust(); case "mgrsBandAdjust": - return definition.mgrsBandAdjust; + return definition.getMgrsBandAdjust(); default: // We shouldn't end up here assert false; diff --git a/src/test/java/gov/nasa/podaac/swodlr/StatusTests.java b/src/test/java/gov/nasa/podaac/swodlr/StatusTests.java index 3d50445..af86ff5 100644 --- a/src/test/java/gov/nasa/podaac/swodlr/StatusTests.java +++ b/src/test/java/gov/nasa/podaac/swodlr/StatusTests.java @@ -6,8 +6,7 @@ import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProduct; import gov.nasa.podaac.swodlr.l2rasterproduct.L2RasterProductRepository; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinitionRepository; +import gov.nasa.podaac.swodlr.rasterdefinition.GridType; import gov.nasa.podaac.swodlr.status.State; import gov.nasa.podaac.swodlr.status.Status; import gov.nasa.podaac.swodlr.status.StatusRepository; @@ -16,14 +15,14 @@ import java.io.IOException; import java.io.InputStreamReader; import java.time.LocalDateTime; -import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; + import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; @@ -32,6 +31,7 @@ import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.Resource; import org.springframework.graphql.test.tester.GraphQlTester.Response; import org.springframework.graphql.test.tester.HttpGraphQlTester; @@ -44,7 +44,7 @@ @TestPropertySource({"file:./src/main/resources/application.properties", "classpath:application.properties"}) @AutoConfigureHttpGraphQlTester public class StatusTests { - private RasterDefinition definition; + private static final UUID NULL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000"); @Autowired private HttpGraphQlTester graphQlTester; @@ -55,26 +55,13 @@ public class StatusTests { @Autowired private L2RasterProductRepository l2RasterProductRepository; - @Autowired - private RasterDefinitionRepository rasterDefinitionRepository; - @Value("classpath:frost.txt") private Resource frost; - @BeforeAll - public void setupDefinition() { - definition = TestUtils.dummyDefinition(); - rasterDefinitionRepository.save(definition); - } - @AfterAll - public void deleteDefinition() { - rasterDefinitionRepository.delete(definition); - } - - @AfterEach - public void deleteProducts() { + public void tearDownMocks() { l2RasterProductRepository.deleteAll(); + statusRepository.deleteAll(); } @Test @@ -90,23 +77,23 @@ public void queryStatus() throws IOException { int reasonIndex = 0; /* Setup mock data */ - UUID productId = graphQlTester - .documentName("mutation/createL2RasterProduct") - .variable("definition", definition.getId()) - .variable("cycle", 0) - .variable("pass", 0) - .variable("scene", 0) - .execute() - .path("createL2RasterProduct.id") - .entity(UUID.class) - .get(); - - L2RasterProduct product = l2RasterProductRepository.findById(productId).get(); + final L2RasterProduct mockProduct = new L2RasterProduct( + 0, + 1, + 2, + false, + GridType.UTM, + 1000, + 0, + 0 + ); + l2RasterProductRepository.save(mockProduct); + LocalDateTime start = LocalDateTime.now(); for (int i = 0; i < pages * pageLimit; i++) { Status status = new Status( - product, + mockProduct, stateEnums.get(stateIndex), reasons.get(++reasonIndex) ); @@ -123,7 +110,7 @@ public void queryStatus() throws IOException { for (int i = 0; i < pages; i++) { Response response = graphQlTester .documentName(i == 0 ? "query/statusByProduct" : "query/statusByPrevious") - .variable("product", productId) + .variable("product", mockProduct.getId()) .variable("after", afterId) .variable("limit", pageLimit) .execute(); @@ -184,17 +171,25 @@ public void queryStatus() throws IOException { } /* Product */ - // ID - response - .path("status[*].product.id") - .entityList(UUID.class) - .containsExactly(Collections.nCopies(pageLimit, productId).toArray(UUID[]::new)); - - // Definition response - .path("status[*].product.definition.id") - .entityList(UUID.class) - .containsExactly(Collections.nCopies(pageLimit, definition.getId()).toArray(UUID[]::new)); + .path("status[*].product") + .entityList(new ParameterizedTypeReference>() {}) + .satisfies(products -> { + Iterator> it = products.iterator(); + while (it.hasNext()) { + Map product = it.next(); + + assertEquals(mockProduct.getId().toString(), product.get("id")); + assertEquals(mockProduct.getCycle(), product.get("cycle")); + assertEquals(mockProduct.getPass(), product.get("pass")); + assertEquals(mockProduct.getScene(), product.get("scene")); + assertEquals(mockProduct.getOutputGranuleExtentFlag(), product.get("outputGranuleExtentFlag")); + assertEquals(mockProduct.getOutputSamplingGridType().toString(), product.get("outputSamplingGridType")); + assertEquals(mockProduct.getRasterResolution(), product.get("rasterResolution")); + assertEquals(mockProduct.getUtmZoneAdjust(), product.get("utmZoneAdjust")); + assertEquals(mockProduct.getMgrsBandAdjust(), product.get("mgrsBandAdjust")); + } + }); } } @@ -202,7 +197,7 @@ public void queryStatus() throws IOException { public void queryStatusWithInvalidProduct() { graphQlTester .documentName("query/statusByProduct") - .variable("product", Utils.NULL_UUID) + .variable("product", NULL_UUID) .execute() .errors() .satisfy(errors -> { @@ -219,7 +214,7 @@ public void queryStatusWithInvalidProduct() { public void queryStatusWithInvalidAfter() { graphQlTester .documentName("query/statusByPrevious") - .variable("after", Utils.NULL_UUID) + .variable("after", NULL_UUID) .execute() .errors() .satisfy(errors -> { diff --git a/src/test/java/gov/nasa/podaac/swodlr/TestUtils.java b/src/test/java/gov/nasa/podaac/swodlr/TestUtils.java deleted file mode 100644 index 3f70c5b..0000000 --- a/src/test/java/gov/nasa/podaac/swodlr/TestUtils.java +++ /dev/null @@ -1,17 +0,0 @@ -package gov.nasa.podaac.swodlr; - -import gov.nasa.podaac.swodlr.rasterdefinition.GridType; -import gov.nasa.podaac.swodlr.rasterdefinition.RasterDefinition; - -public class TestUtils { - static final RasterDefinition dummyDefinition() { - RasterDefinition definition = new RasterDefinition(); - definition.outputGranuleExtentFlag = true; - definition.outputSamplingGridType = GridType.UTM; - definition.rasterResolution = 1000; - definition.utmZoneAdjust = 1; - definition.mgrsBandAdjust = -1; - - return definition; - } -} diff --git a/src/test/java/gov/nasa/podaac/swodlr/security/MockGraphQlUserInjector.java b/src/test/java/gov/nasa/podaac/swodlr/security/MockGraphQlUserInjector.java index 0554ca7..65f5bf3 100644 --- a/src/test/java/gov/nasa/podaac/swodlr/security/MockGraphQlUserInjector.java +++ b/src/test/java/gov/nasa/podaac/swodlr/security/MockGraphQlUserInjector.java @@ -1,10 +1,12 @@ package gov.nasa.podaac.swodlr.security; +import gov.nasa.podaac.swodlr.user.User; import gov.nasa.podaac.swodlr.user.UserReference; import gov.nasa.podaac.swodlr.user.UserRepository; import java.util.Collections; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Profile; import org.springframework.graphql.server.WebGraphQlInterceptor; import org.springframework.graphql.server.WebGraphQlRequest; @@ -17,20 +19,27 @@ public class MockGraphQlUserInjector implements WebGraphQlInterceptor { private static final UUID MOCK_USER_ID = UUID.fromString("fee1dc78-0604-4fa6-adae-0b4b55440e7d"); - private UserReference mockUser; + private User mockUser; + private UserReference mockUserRef; public MockGraphQlUserInjector(@Autowired UserRepository userRepository) { - mockUser = new UserReference(userRepository.findById(MOCK_USER_ID).get()); + mockUser = userRepository.findById(MOCK_USER_ID).get(); + mockUserRef = new UserReference(mockUser); } @Override public Mono intercept(WebGraphQlRequest request, Chain chain) { return Mono.defer(() -> { request.configureExecutionInput((executionInput, builder) -> { - builder.graphQLContext(Collections.singletonMap("userRef", mockUser)); + builder.graphQLContext(Collections.singletonMap("userRef", mockUserRef)); return builder.build(); }); return chain.next(request); }); } + + @Bean + User mockUser() { + return mockUser; + } } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 263efaf..aa00b99 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,14 +1,18 @@ -spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect -spring.jpa.properties.hibernate.hbm2ddl.auto = none -spring.datasource.url = jdbc:h2:mem:swodlr;\ +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect +spring.jpa.properties.hibernate.hbm2ddl.auto=none +spring.datasource.url=jdbc:h2:mem:;\ MODE=PostgreSQL;\ DATABASE_TO_LOWER=TRUE;\ DEFAULT_NULL_ORDERING=HIGH;\ - INIT=CREATE ALIAS gen_random_uuid FOR 'java.util.UUID.randomUUID'\\;RUNSCRIPT FROM 'database/schema.sql'\\;RUNSCRIPT FROM 'database/local_data.sql' + DB_CLOSE_DELAY=-1;\ + DB_CLOSE_ON_EXIT=FALSE;\ + INIT=CREATE ALIAS IF NOT EXISTS gen_random_uuid FOR 'java.util.UUID.randomUUID'\\;RUNSCRIPT FROM 'database/schema.sql'\\;RUNSCRIPT FROM 'database/local_data.sql' spring.security.oauth2.client.registration.edl.client-id=silence-dogood -swodlr.security.session-encryption-key=0123456789abcdefe62100cadeadbeef -swodlr.security.frontend-uri-pattern=http://thisisa.test/ spring.cloud.aws.region.static=us-west-2 spring.config.import= + +swodlr.tea-mapping.test-bucket=earl-grey +swodlr.security.session-encryption-key=0123456789abcdefe62100cadeadbeef +swodlr.security.frontend-uri-pattern=http://thisisa.test/ diff --git a/src/test/resources/graphql-test/mutation/createL2RasterProduct.graphql b/src/test/resources/graphql-test/mutation/createL2RasterProduct.graphql deleted file mode 100644 index 18331ff..0000000 --- a/src/test/resources/graphql-test/mutation/createL2RasterProduct.graphql +++ /dev/null @@ -1,17 +0,0 @@ -mutation ($definition: ID!, $cycle: Int!, $pass: Int! $scene: Int!) { - createL2RasterProduct(definition: $definition, cycle: $cycle, pass: $pass, scene: $scene) { - id - definition { - id - } - cycle - pass - scene - status { - id - timestamp - state - reason - } - } -} \ No newline at end of file diff --git a/src/test/resources/graphql-test/mutation/createRasterDefinition.graphql b/src/test/resources/graphql-test/mutation/createRasterDefinition.graphql new file mode 100644 index 0000000..7076e71 --- /dev/null +++ b/src/test/resources/graphql-test/mutation/createRasterDefinition.graphql @@ -0,0 +1,19 @@ +mutation ( + $name: String!, + $outputGranuleExtentFlag: Boolean! + $outputSamplingGridType: GridType!, + $rasterResolution: Int! + $utmZoneAdjust: Int, + $mgrsBandAdjust: Int +){ + createRasterDefinition( + name: $name, + outputGranuleExtentFlag: $outputGranuleExtentFlag, + outputSamplingGridType: $outputSamplingGridType, + rasterResolution: $rasterResolution, + utmZoneAdjust: $utmZoneAdjust, + mgrsBandAdjust: $mgrsBandAdjust + ) { + id + } +} diff --git a/src/test/resources/graphql-test/mutation/deleteRasterDefinition.graphql b/src/test/resources/graphql-test/mutation/deleteRasterDefinition.graphql new file mode 100644 index 0000000..fbe926e --- /dev/null +++ b/src/test/resources/graphql-test/mutation/deleteRasterDefinition.graphql @@ -0,0 +1,3 @@ +mutation ($id: ID!) { + deleteRasterDefinition(id: $id) +} diff --git a/src/test/resources/graphql-test/mutation/generateL2RasterProduct.graphql b/src/test/resources/graphql-test/mutation/generateL2RasterProduct.graphql new file mode 100644 index 0000000..246b05a --- /dev/null +++ b/src/test/resources/graphql-test/mutation/generateL2RasterProduct.graphql @@ -0,0 +1,37 @@ +mutation ( + $cycle: Int!, + $pass: Int!, + $scene: Int!, + $outputGranuleExtentFlag: Boolean!, + $outputSamplingGridType: GridType!, + $rasterResolution: Int!, + $utmZoneAdjust: Int, + $mgrsBandAdjust: Int +) { + generateL2RasterProduct( + cycle: $cycle, + pass: $pass, + scene: $scene, + outputGranuleExtentFlag: $outputGranuleExtentFlag, + outputSamplingGridType: $outputSamplingGridType, + rasterResolution: $rasterResolution, + utmZoneAdjust: $utmZoneAdjust, + mgrsBandAdjust: $mgrsBandAdjust + ) { + id + cycle + pass + scene + outputGranuleExtentFlag + outputSamplingGridType + rasterResolution + utmZoneAdjust + mgrsBandAdjust + status { + id + timestamp + state + reason + } + } +} diff --git a/src/test/resources/graphql-test/query/currentUser_products.graphql b/src/test/resources/graphql-test/query/currentUser_products.graphql index 3f204fc..f2132f8 100644 --- a/src/test/resources/graphql-test/query/currentUser_products.graphql +++ b/src/test/resources/graphql-test/query/currentUser_products.graphql @@ -2,9 +2,13 @@ query ($after: ID, $limit: Int) { currentUser { products(after: $after, limit: $limit) { id - definition { - id - } + cycle + pass + scene + outputGranuleExtentFlag + outputSamplingGridType + rasterResolution + status { id timestamp diff --git a/src/test/resources/graphql-test/query/currentUser_rasterDefinitions.graphql b/src/test/resources/graphql-test/query/currentUser_rasterDefinitions.graphql new file mode 100644 index 0000000..1aef648 --- /dev/null +++ b/src/test/resources/graphql-test/query/currentUser_rasterDefinitions.graphql @@ -0,0 +1,27 @@ +query ( + $id: ID, + $outputGranuleExtentFlag: Boolean, + $outputSamplingGridType: GridType, + $rasterResolution: Int, + $utmZoneAdjust: Int, + $mgrsBandAdjust: Int +) { + currentUser { + rasterDefinitions( + id: $id, + outputGranuleExtentFlag: $outputGranuleExtentFlag, + outputSamplingGridType: $outputSamplingGridType, + rasterResolution: $rasterResolution, + utmZoneAdjust: $utmZoneAdjust, + mgrsBandAdjust: $mgrsBandAdjust + ) { + id + name + outputGranuleExtentFlag + outputSamplingGridType + rasterResolution + utmZoneAdjust + mgrsBandAdjust + } + } +} diff --git a/src/test/resources/graphql-test/query/l2RasterProduct_granules.graphql b/src/test/resources/graphql-test/query/l2RasterProduct_granules.graphql new file mode 100644 index 0000000..b1ca318 --- /dev/null +++ b/src/test/resources/graphql-test/query/l2RasterProduct_granules.graphql @@ -0,0 +1,8 @@ +query ($id: ID!) { + l2RasterProduct(id: $id) { + granules { + id + uri + } + } +} diff --git a/src/test/resources/graphql-test/query/statusByPrevious.graphql b/src/test/resources/graphql-test/query/statusByPrevious.graphql index 963b88b..37edf51 100644 --- a/src/test/resources/graphql-test/query/statusByPrevious.graphql +++ b/src/test/resources/graphql-test/query/statusByPrevious.graphql @@ -6,9 +6,14 @@ query ($after: ID!, $limit: Int) { reason product { id - definition { - id - } + cycle + pass + scene + outputGranuleExtentFlag + outputSamplingGridType + rasterResolution + utmZoneAdjust + mgrsBandAdjust } } } diff --git a/src/test/resources/graphql-test/query/statusByProduct.graphql b/src/test/resources/graphql-test/query/statusByProduct.graphql index 796a3a3..63e6abe 100644 --- a/src/test/resources/graphql-test/query/statusByProduct.graphql +++ b/src/test/resources/graphql-test/query/statusByProduct.graphql @@ -6,9 +6,14 @@ query ($product: ID!, $limit: Int) { reason product { id - definition { - id - } + cycle + pass + scene + outputGranuleExtentFlag + outputSamplingGridType + rasterResolution + utmZoneAdjust + mgrsBandAdjust } } } diff --git a/terraform/app.tf b/terraform/app.tf index 1a3cd56..4779f2c 100644 --- a/terraform/app.tf +++ b/terraform/app.tf @@ -138,7 +138,7 @@ resource "aws_ssm_parameter" "app_session_encryption_key" { resource "aws_ssm_parameter" "app_frontend_uri_pattern" { name = "${local.app_path}/swodlr.security.frontend-uri-pattern" - count = var.frontend_uri_pattern == null ? 1 : 0 + count = length(var.frontend_uri_pattern) > 0 ? 1 : 0 type = "String" value = var.frontend_uri_pattern overwrite = true diff --git a/terraform/variables.tf b/terraform/variables.tf index 7e303c9..999d527 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -62,5 +62,5 @@ variable "active_profiles" { variable "frontend_uri_pattern" { type = string - nullable = true + default = "" }