diff --git a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java index 1e359958ee..cfbbed401c 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java @@ -158,6 +158,7 @@ public final class Controllers { public static final String INCLUDE_ALIASES = "include-aliases"; public static final String MIN_NUMBER = "min-number"; public static final String MAX_NUMBER = "max-number"; + public static final String MEASUREMENT_ID = "measurement-id"; public static final String MIN_HEIGHT = "min-height"; public static final String MAX_HEIGHT = "max-height"; public static final String MIN_FLOW = "min-flow"; diff --git a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java index 5e8254a247..b5d8116dcf 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/MeasurementController.java @@ -37,6 +37,7 @@ import static cwms.cda.api.Controllers.MAX_FLOW; import static cwms.cda.api.Controllers.MAX_HEIGHT; import static cwms.cda.api.Controllers.MAX_NUMBER; +import static cwms.cda.api.Controllers.MEASUREMENT_ID; import static cwms.cda.api.Controllers.MIN_FLOW; import static cwms.cda.api.Controllers.MIN_HEIGHT; import static cwms.cda.api.Controllers.MIN_NUMBER; @@ -48,6 +49,7 @@ import static cwms.cda.api.Controllers.TIMEZONE; import static cwms.cda.api.Controllers.TIME_FORMAT_DESC; import static cwms.cda.api.Controllers.UNIT_SYSTEM; +import static cwms.cda.api.Controllers.queryParamAsClass; import static cwms.cda.api.Controllers.queryParamAsDouble; import static cwms.cda.api.Controllers.queryParamAsInstant; import static cwms.cda.api.Controllers.requiredParam; @@ -63,6 +65,7 @@ import cwms.cda.data.dao.MeasurementDao; import cwms.cda.data.dto.StatusResponse; import cwms.cda.data.dto.measurement.Measurement; +import cwms.cda.data.dto.measurement.MeasurementLegacy; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; import io.javalin.apibuilder.CrudHandler; @@ -103,8 +106,9 @@ private Timer.Context markAndTime(String subject) { queryParams = { @OpenApiParam(name = OFFICE_MASK, description = "Office id mask for filtering measurements. Use null to retrieve measurements for all offices."), @OpenApiParam(name = ID_MASK, description = "Location id mask for filtering measurements. Use null to retrieve measurements for all locations."), - @OpenApiParam(name = MIN_NUMBER, description = "Minimum measurement number-id for filtering measurements."), - @OpenApiParam(name = MAX_NUMBER, description = "Maximum measurement number-id for filtering measurements."), + @OpenApiParam(name = MEASUREMENT_ID, description = "Measurement ID filter. Supports UUIDs (and optionally integer IDs). This filter has precedence over min-number and max-number filters."), + @OpenApiParam(name = MIN_NUMBER, type = Integer.class, description = "Minimum measurement number-id for filtering measurements. Only applies to integer measurement IDs."), + @OpenApiParam(name = MAX_NUMBER, type = Integer.class, description = "Maximum measurement number-id for filtering measurements. Only applies to integer measurement IDs."), @OpenApiParam(name = BEGIN, description = "The start of the time window to delete. " + TIME_FORMAT_DESC + " A null value is treated as an unbounded start."), @OpenApiParam(name = END, description = "The end of the time window to delete." + @@ -128,8 +132,9 @@ private Timer.Context markAndTime(String subject) { }, responses = { @OpenApiResponse(status = "200", content = { - @OpenApiContent(isArray = true, type = Formats.JSONV1, from = Measurement.class), - @OpenApiContent(isArray = true, type = Formats.JSON, from = Measurement.class) + @OpenApiContent(isArray = true, type = Formats.JSONV2, from = Measurement.class), + @OpenApiContent(isArray = true, type = Formats.JSONV1, from = MeasurementLegacy.class), + @OpenApiContent(isArray = true, type = Formats.JSON, from = MeasurementLegacy.class) }) }, description = "Returns matching measurement data.", @@ -144,6 +149,7 @@ public void getAll(@NotNull Context ctx) { Instant maxDate = queryParamAsInstant(ctx, END); String minNum = ctx.queryParam(MIN_NUMBER); String maxNum = ctx.queryParam(MAX_NUMBER); + String measurementId = ctx.queryParam(MEASUREMENT_ID); Number minHeight = queryParamAsDouble(ctx, MIN_HEIGHT); Number maxHeight = queryParamAsDouble(ctx, MAX_HEIGHT); Number minFlow = queryParamAsDouble(ctx, MIN_FLOW); @@ -154,7 +160,7 @@ public void getAll(@NotNull Context ctx) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); List measurements = dao.retrieveMeasurements(officeId, locationId, minDate, maxDate, unitSystem, - minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agency, quality); + minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agency, quality, measurementId); String formatHeader = ctx.header(Header.ACCEPT); ContentType contentType = Formats.parseHeader(formatHeader, Measurement.class); ctx.contentType(contentType.toString()); @@ -182,8 +188,9 @@ public void getOne(@NotNull Context ctx, @NotNull String locationId) { @OpenApi( requestBody = @OpenApiRequestBody( content = { - @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV1), - @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSON) + @OpenApiContent(isArray = true, from = Measurement.class, type = Formats.JSONV2), + @OpenApiContent(isArray = true, from = MeasurementLegacy.class, type = Formats.JSONV1), + @OpenApiContent(isArray = true, from = MeasurementLegacy.class, type = Formats.JSON) }, required = true), queryParams = { @@ -234,8 +241,8 @@ public void update(@NotNull Context ctx, @NotNull String locationId) { + "to be used if the format of the " + BEGIN + "and " + END + " parameters do not include offset or time zone information. " + "Defaults to UTC."), - @OpenApiParam(name = MIN_NUMBER, description = "Specifies the min number-id of the measurement to delete."), - @OpenApiParam(name = MAX_NUMBER, description = "Specifies the max number-id of the measurement to delete."), + @OpenApiParam(name = MIN_NUMBER, type = Integer.class, description = "Specifies the min number-id of the measurement to delete. Only applies to integer measurement IDs."), + @OpenApiParam(name = MAX_NUMBER, type = Integer.class, description = "Specifies the max number-id of the measurement to delete. Only applies to integer measurement IDs."), }, description = "Delete an existing measurement.", method = HttpMethod.DELETE, @@ -255,7 +262,7 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) { try (Timer.Context ignored = markAndTime(DELETE)) { DSLContext dsl = getDslContext(ctx); MeasurementDao dao = new MeasurementDao(dsl); - dao.deleteMeasurements(officeId, locationId, minDate, maxDate,minNum, maxNum); + dao.deleteMeasurements(officeId, locationId, minDate, maxDate, minNum, maxNum, null); StatusResponse re = new StatusResponse(officeId, "Measurement successfully deleted for specified location-id.", locationId); ctx.status(HttpServletResponse.SC_OK).json(re); } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java index 679f3e6bb3..aade6569c1 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/MeasurementDao.java @@ -72,6 +72,8 @@ import org.jooq.impl.DSL; import static org.jooq.impl.DSL.max; import static org.jooq.impl.DSL.min; +import static usace.cwms.db.jooq.codegen_latest.packages.CWMS_STREAM_PACKAGE.call_RETRIEVE_MEAS_XML_BY_ID; + import usace.cwms.db.jooq.codegen.packages.CWMS_STREAM_PACKAGE; import usace.cwms.db.jooq.codegen.tables.AV_STREAMFLOW_MEAS; import usace.cwms.db.jooq.codegen.udt.records.STREAMFLOW_MEAS2_T; @@ -86,33 +88,31 @@ public MeasurementDao(DSLContext dsl) { super(dsl); } - /** - * Retrieve a list of measurements - * - * @param officeId - the office id - * @param locationId - the location id for filtering - * @param unitSystem - the unit system to use for the returned data - * @return a list of measurements - */ public List retrieveMeasurements(String officeId, String locationId, Instant minDateMask, Instant maxDateMask, String unitSystem, Number minHeight, Number maxHeight, Number minFlow, Number maxFlow, String minNum, String maxNum, - String agencies, String qualities) { + String agencies, String qualities, String measurementId) { return connectionResult(dsl, conn -> { setOffice(conn, officeId); Timestamp minTimestamp = buildTimestamp(minDateMask); Timestamp maxTimestamp = buildTimestamp(maxDateMask); - return retrieveMeasurementsJooq(conn, officeId, locationId, unitSystem, minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities, minTimestamp, maxTimestamp); + return retrieveMeasurementsJooq(conn, officeId, locationId, unitSystem, minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities, minTimestamp, maxTimestamp, measurementId); }); } - private static List retrieveMeasurementsJooq(Connection conn, String officeId, String locationId, String unitSystem, Number minHeight, Number maxHeight, Number minFlow, Number maxFlow, String minNum, String maxNum, String agencies, String qualities, Timestamp minTimestamp, Timestamp maxTimestamp) throws JsonProcessingException { - String xml = CWMS_STREAM_PACKAGE.call_RETRIEVE_MEAS_XML(DSL.using(conn).configuration(), locationId, unitSystem, minTimestamp, maxTimestamp, - minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities, "UTC", officeId); - List retVal = fromDbXml(xml); - if(retVal.isEmpty()) { + private static List retrieveMeasurementsJooq(Connection conn, String officeId, String locationId, String unitSystem, Number minHeight, Number maxHeight, Number minFlow, Number maxFlow, String minNum, String maxNum, String agencies, String qualities, Timestamp minTimestamp, Timestamp maxTimestamp, String measurementId) throws JsonProcessingException { + String xml; + if (measurementId == null) { + xml = CWMS_STREAM_PACKAGE.call_RETRIEVE_MEAS_XML(DSL.using(conn).configuration(), locationId, unitSystem, minTimestamp, maxTimestamp, + minHeight, maxHeight, minFlow, maxFlow, minNum, maxNum, agencies, qualities, "UTC", officeId); + } else { + xml = call_RETRIEVE_MEAS_XML_BY_ID(DSL.using(conn).configuration(), locationId, measurementId, unitSystem, minTimestamp, maxTimestamp, + minHeight, maxHeight, minFlow, maxFlow, agencies, qualities, "UTC", officeId); + } + List measurements = fromDbXml(xml); + if (measurements.isEmpty()) { throw new NotFoundException("No measurements found."); } - return retVal; + return measurements; } /** @@ -142,15 +142,21 @@ private void storeMeasurementsJooq(Connection conn, List measuremen * @param minNum */ public void deleteMeasurements(String officeId, String locationId, Instant minDateMask, Instant maxDateMask, String minNum, - String maxNum) { + String maxNum, String measurementId) { connection(dsl, conn -> { setOffice(conn, officeId); Timestamp minTimestamp = buildTimestamp(minDateMask); Timestamp maxTimestamp = buildTimestamp(maxDateMask); String timeZoneId = "UTC"; - verifyMeasurementsExists(conn, officeId, locationId, maxNum, maxNum); - CWMS_STREAM_PACKAGE.call_DELETE_STREAMFLOW_MEAS(DSL.using(conn).configuration(), locationId, minNum, minTimestamp, maxTimestamp, - null, null, null, null, maxNum, maxNum, null, null, timeZoneId, officeId); + verifyMeasurementsExists(conn, officeId, locationId, minNum, maxNum, measurementId); + String minNumF = minNum; + String maxNumF = maxNum; + if(measurementId != null) { + minNumF = null; + maxNumF = null; + } + CWMS_STREAM_PACKAGE.call_DELETE_STREAMFLOW_MEAS(DSL.using(conn).configuration(), locationId, "EN", minTimestamp, maxTimestamp, + null, null, null, null, minNumF, maxNumF, null, null, timeZoneId, officeId); }); } @@ -181,12 +187,9 @@ public List retrieveMeasurementTimeExtentsMap(String off .build()); } - private void verifyMeasurementsExists(Connection conn, String officeId, String locationId, String minNum, String maxNum) throws JsonProcessingException { - List measurements = retrieveMeasurementsJooq(conn, officeId, locationId, UnitSystem.EN.toString(), - null, null, null, null, minNum, maxNum, null, null, null, null); - if (measurements.isEmpty()) { - throw new NotFoundException("Could not find measurements for " + locationId + " in office " + officeId + "."); - } + private void verifyMeasurementsExists(Connection conn, String officeId, String locationId, String minNum, String maxNum, String measurementId) throws JsonProcessingException { + retrieveMeasurementsJooq(conn, officeId, locationId, UnitSystem.EN.toString(), + null, null, null, null, minNum, maxNum, null, null, null, null, measurementId); } static List fromDbXml(String xml) throws JsonProcessingException { @@ -207,7 +210,7 @@ static Measurement fromJooqMeasurementRecord(STREAMFLOW_MEAS2_T record) { .withName(locationTemplate.getLocationId()) .withOfficeId(locationTemplate.getOfficeId()) .build()) - .withNumber(record.getMEAS_NUMBER()) + .withMeasurementId(record.getMEAS_NUMBER()) .withAgency(record.getAGENCY_ID()) .withParty(record.getPARTY()) .withUsed(parseBool(record.getUSED())) @@ -270,7 +273,7 @@ static MeasurementXmlDto convertMeasurementToXmlDto(Measurement meas) .withHeightUnit(meas.getHeightUnit()) .withInstant(meas.getInstant()) .withLocationId(meas.getLocationId()) - .withNumber(meas.getNumber()) + .withNumber(meas.getMeasurementId()) .withOfficeId(meas.getOfficeId()) .withParty(meas.getParty()) .withTempUnit(meas.getTempUnit()) @@ -284,9 +287,11 @@ static MeasurementXmlDto convertMeasurementToXmlDto(Measurement meas) } private static List convertMeasurementsFromXmlDto(MeasurementsXmlDto xmlDto) { - return xmlDto.getMeasurements().stream() - .map(MeasurementDao::convertMeasurementFromXmlDto) - .collect(toList()); + List retVal = new ArrayList<>(); + for(MeasurementXmlDto dto : xmlDto.getMeasurements()) { + retVal.add(convertMeasurementFromXmlDto(dto)); + } + return retVal; } static Measurement convertMeasurementFromXmlDto(MeasurementXmlDto dto) { @@ -300,7 +305,7 @@ static Measurement convertMeasurementFromXmlDto(MeasurementXmlDto dto) { .withName(dto.getLocationId()) .withOfficeId(dto.getOfficeId()) .build()) - .withNumber(dto.getNumber()) + .withMeasurementId(dto.getNumber()) .withParty(dto.getParty()) .withTempUnit(dto.getTempUnit()) .withUsed(dto.isUsed()) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java index 491b035b9f..16b1ac37f5 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/Measurement.java @@ -23,6 +23,7 @@ */ package cwms.cda.data.dto.measurement; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -35,13 +36,16 @@ import cwms.cda.formatters.Formats; import cwms.cda.formatters.annotations.FormattableWith; import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; + import java.time.Instant; @FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class) @JsonDeserialize(builder = Measurement.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public final class Measurement extends CwmsDTOBase { +public class Measurement extends CwmsDTOBase { private final String heightUnit; private final String flowUnit; private final String tempUnit; @@ -55,13 +59,20 @@ public final class Measurement extends CwmsDTOBase { private final Instant instant; @JsonProperty(required = true) private final CwmsId id; - @JsonProperty(required = true) - private final String number; + private final String measurementId; + + @Override + protected void validateInternal(cwms.cda.data.dto.CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.required(id, "id"); + validator.required(measurementId, "measurementId"); + validator.required(instant, "instant"); + } private final StreamflowMeasurement streamflowMeasurement; private final SupplementalStreamflowMeasurement supplementalStreamflowMeasurement; private final UsgsMeasurement usgsMeasurement; - private Measurement(Builder builder) { + Measurement(Builder builder) { this.heightUnit = builder.heightUnit; this.flowUnit = builder.flowUnit; this.tempUnit = builder.tempUnit; @@ -75,7 +86,7 @@ private Measurement(Builder builder) { this.supplementalStreamflowMeasurement = builder.supplementalStreamflowMeasurement; this.usgsMeasurement = builder.usgsMeasurement; this.id = builder.id; - this.number = builder.number; + this.measurementId = builder.measurementId; this.instant = builder.instant; } @@ -134,8 +145,8 @@ public Instant getInstant() { return instant; } - public String getNumber() { - return number; + public String getMeasurementId() { + return measurementId; } public StreamflowMeasurement getStreamflowMeasurement() { @@ -150,7 +161,7 @@ public UsgsMeasurement getUsgsMeasurement() { return usgsMeasurement; } - public static final class Builder { + public static class Builder { private String heightUnit; private String flowUnit; private String tempUnit; @@ -164,7 +175,7 @@ public static final class Builder { private SupplementalStreamflowMeasurement supplementalStreamflowMeasurement; private UsgsMeasurement usgsMeasurement; private Instant instant; - private String number; + private String measurementId; private CwmsId id; public Builder withHeightUnit(String heightUnit) { @@ -232,8 +243,9 @@ public Builder withId(CwmsId id) { return this; } - public Builder withNumber(String number) { - this.number = number; + @JsonAlias("number") + public Builder withMeasurementId(String measurementId) { + this.measurementId = measurementId; return this; } @@ -246,4 +258,17 @@ public Measurement build() { return new Measurement(this); } } + + public abstract static class MeasurementV1Mixin { + @JsonProperty("number") + @JsonAlias("measurement-id") + abstract String getMeasurementId(); + + public abstract static class MeasurementBuilderV1Mixin { + @JsonProperty("number") + @JsonAlias("measurement-id") + abstract Measurement.Builder withMeasurementId(String measurementId); + } + } + } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/MeasurementLegacy.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/MeasurementLegacy.java new file mode 100644 index 0000000000..64d95c1915 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/measurement/MeasurementLegacy.java @@ -0,0 +1,42 @@ +package cwms.cda.data.dto.measurement; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; + +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@JsonDeserialize(builder = MeasurementLegacy.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +public class MeasurementLegacy extends Measurement { + + public MeasurementLegacy(Builder builder) { + super(builder); + } + + @JsonProperty("number") + @Override + public String getMeasurementId() { + return super.getMeasurementId(); + } + + public static class Builder extends Measurement.Builder { + @Override + public MeasurementLegacy build() { + return new MeasurementLegacy(this); + } + + @JsonProperty("number") + @JsonAlias("measurement-id") + @Override + public MeasurementLegacy.Builder withMeasurementId(String measurementId) { + return (MeasurementLegacy.Builder) super.withMeasurementId(measurementId); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV1.java b/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV1.java index 53a2df2e08..22e966ca5a 100644 --- a/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV1.java +++ b/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV1.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import cwms.cda.data.dto.CwmsDTOBase; import cwms.cda.data.dto.Office; +import cwms.cda.data.dto.measurement.Measurement; import cwms.cda.formatters.Formats; import cwms.cda.formatters.FormattingException; import cwms.cda.formatters.OfficeFormatV1; @@ -44,7 +45,7 @@ public JsonV1() { * Create a V1 instance using the project ObjectMapper. * @param om ObjectMapper with its own settings. */ - public JsonV1(ObjectMapper om) { + private JsonV1(ObjectMapper om) { this.om = om; } @@ -68,12 +69,19 @@ public static ObjectMapper buildObjectMapper() { module.addDeserializer(Instant.class, new FlexibleInstantDeserializer()); retVal.registerModule(module); + registerMixIns(retVal); + return retVal; } + public static void registerMixIns(ObjectMapper retVal) { + retVal.addMixIn(Measurement.class, Measurement.MeasurementV1Mixin.class) + .addMixIn(Measurement.Builder.class, Measurement.MeasurementV1Mixin.MeasurementBuilderV1Mixin.class); + } + @Override public String getContentType() { - return Formats.JSON; + return Formats.JSONV1; } @Override diff --git a/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV2.java b/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV2.java index b941a08a96..f1dc63a055 100644 --- a/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV2.java +++ b/cwms-data-api/src/main/java/cwms/cda/formatters/json/JsonV2.java @@ -34,6 +34,7 @@ import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import cwms.cda.data.dto.CwmsDTOBase; +import cwms.cda.data.dto.measurement.Measurement; import cwms.cda.formatters.Formats; import cwms.cda.formatters.FormattingException; import cwms.cda.formatters.OutputFormatter; diff --git a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java index cd16221244..71f334ed5f 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/MeasurementControllerTestIT.java @@ -127,7 +127,7 @@ static void tearDown() { db.connection(c -> { MeasurementDao measDao = new MeasurementDao(getDslContext(c, OFFICE_ID)); try { - measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null); + measDao.deleteMeasurements(OFFICE_ID, measLoc, null, null, null, null, null); } catch (Exception e) { LOGGER.atInfo().log("Failed to delete measurements for: " + measLoc + ". Measurement(s) likely already deleted"); } @@ -150,6 +150,17 @@ void test_create_retrieve_delete_measurement(String format) throws IOException { Measurement measurement = measurements.get(0); TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + given() + .log().ifValidationFails(LogDetail.ALL,true) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("measurements/StreamLoc321") + .then() + .log().ifValidationFails(LogDetail.ALL,true); + // Create the Measurement given() .log().ifValidationFails(LogDetail.ALL, true) @@ -170,7 +181,8 @@ void test_create_retrieve_delete_measurement(String format) throws IOException { .body(IDENTIFIER, isEmptyString()); String locationId = measurement.getLocationId(); - String number = measurement.getNumber(); + String number = measurement.getMeasurementId(); + Instant instant = measurement.getInstant(); // Retrieve the Measurement and assert that it exists given() @@ -201,7 +213,7 @@ void test_create_retrieve_delete_measurement(String format) throws IOException { .body("[0].party", equalTo(measurement.getParty())) .body("[0].wm-comments", equalTo(measurement.getWmComments())) .body("[0].instant", equalTo(measurement.getInstant().toString())) - .body("[0].number", equalTo(measurement.getNumber())) + .body("[0].number", equalTo(measurement.getMeasurementId())) .body("[0].id.name", equalTo(measurement.getLocationId())) .body("[0].id.office-id", equalTo(measurement.getOfficeId())) .body("[0].streamflow-measurement.gage-height", equalTo(measurement.getStreamflowMeasurement().getGageHeight().floatValue())) @@ -237,8 +249,8 @@ void test_create_retrieve_delete_measurement(String format) throws IOException { .accept(format) .header(AUTH_HEADER, user.toHeaderValue()) .queryParam(Controllers.OFFICE, measurement.getId().getOfficeId()) - .queryParam(Controllers.MIN_NUMBER, number) - .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.BEGIN, instant.toString()) + .queryParam(Controllers.END, instant.toString()) .when() .redirects().follow(true) .redirects().max(3) @@ -255,10 +267,10 @@ void test_create_retrieve_delete_measurement(String format) throws IOException { given() .log().ifValidationFails(LogDetail.ALL, true) .accept(format) - .queryParam(Controllers.OFFICE, measurement.getId().getOfficeId()) + .queryParam(Controllers.OFFICE_MASK, measurement.getId().getOfficeId()) .queryParam(Controllers.ID_MASK, measurement.getLocationId()) - .queryParam(Controllers.MIN_NUMBER, number) - .queryParam(Controllers.MAX_NUMBER, number) + .queryParam(Controllers.BEGIN, instant.toString()) + .queryParam(Controllers.END, instant.toString()) .queryParam(Controllers.UNIT_SYSTEM, UnitSystem.EN.getValue()) .when() .redirects().follow(true) @@ -284,6 +296,17 @@ void test_create_retrieve_delete_measurement_multiple(String format) throws IOEx Measurement measurement2 = measurements.get(1); TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + given() + .log().ifValidationFails(LogDetail.ALL,true) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("measurements/StreamLoc321") + .then() + .log().ifValidationFails(LogDetail.ALL,true); + // Create the Measurements given() .log().ifValidationFails(LogDetail.ALL, true) @@ -328,7 +351,7 @@ void test_create_retrieve_delete_measurement_multiple(String format) throws IOEx .body("[0].party", equalTo(measurement1.getParty())) .body("[0].wm-comments", equalTo(measurement1.getWmComments())) .body("[0].instant", equalTo(measurement1.getInstant().toString())) - .body("[0].number", equalTo(measurement1.getNumber())) + .body("[0].number", equalTo(measurement1.getMeasurementId())) .body("[0].id.name", equalTo(measurement1.getLocationId())) .body("[0].id.office-id", equalTo(measurement1.getOfficeId())) .body("[0].streamflow-measurement.gage-height", equalTo(measurement1.getStreamflowMeasurement().getGageHeight().floatValue())) @@ -367,7 +390,7 @@ void test_create_retrieve_delete_measurement_multiple(String format) throws IOEx .body("[1].party", equalTo(measurement2.getParty())) .body("[1].wm-comments", equalTo(measurement2.getWmComments())) .body("[1].instant", equalTo(measurement2.getInstant().toString())) - .body("[1].number", equalTo(measurement2.getNumber())) + .body("[1].number", equalTo(measurement2.getMeasurementId())) .body("[1].id.name", equalTo(measurement2.getLocationId())) .body("[1].id.office-id", equalTo(measurement2.getOfficeId())) .body("[1].streamflow-measurement.gage-height", equalTo(measurement2.getStreamflowMeasurement().getGageHeight().floatValue())) @@ -470,4 +493,91 @@ void test_delete_does_not_exist() { .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); } + @ParameterizedTest + @ValueSource(strings = {Formats.JSONV1, Formats.JSONV2}) + @MinimumSchema(260716) + void test_measurement_id_filter(String format) throws Exception { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/measurements_with_meas_id.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + List measurements = Formats.parseContentList(new ContentType(format), json, Measurement.class); + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + given() + .log().ifValidationFails(LogDetail.ALL,true) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("measurements/StreamLoc321") + .then() + .log().ifValidationFails(LogDetail.ALL,true); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .contentType(format) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(Formats.format(Formats.parseHeader(format, Measurement.class), measurements, Measurement.class)) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/measurements") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + // Filter by measurement ID + boolean isV2 = format.equals(Formats.JSONV2); + String numberField = isV2 ? "measurement-id" : "number"; + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(format) + .queryParam(Controllers.OFFICE_MASK, OFFICE_ID) + .queryParam(Controllers.ID_MASK, "StreamLoc321") + .queryParam(Controllers.MEASUREMENT_ID, "123456") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("size()", is(1)) + .body("[0]." + numberField, is("123456")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(format) + .queryParam(Controllers.OFFICE_MASK, OFFICE_ID) + .queryParam(Controllers.ID_MASK, "StreamLoc321") + .queryParam(Controllers.MEASUREMENT_ID, "550e8400-e29b-41d4-a716-446655440000") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/measurements") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("size()", is(1)) + .body("[0]." + numberField, is("550e8400-e29b-41d4-a716-446655440000")); + + given() + .log().ifValidationFails(LogDetail.ALL,true) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("measurements/StreamLoc321") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + } + } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java index 8b683ba62d..6f5eac9c5e 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTest.java @@ -60,7 +60,7 @@ void testFromJooqMeasurementRecord() { Measurement measurement = MeasurementDao.fromJooqMeasurementRecord(record); assertNotNull(measurement); - assertEquals("12345", measurement.getNumber()); + assertEquals("12345", measurement.getMeasurementId()); assertEquals("USGS", measurement.getAgency()); assertEquals("SomeParty", measurement.getParty()); assertEquals(instant, measurement.getInstant()); @@ -150,7 +150,7 @@ void testToDbXmlMultiple() throws Exception { private Measurement buildTestMeasurement() { return new Measurement.Builder() - .withNumber("12345") + .withMeasurementId("12345") .withAgency("USGS") .withParty("SomeParty") .withInstant(Instant.parse("2024-01-01T00:00:00Z")) @@ -202,7 +202,7 @@ private Measurement buildTestMeasurement() { private Measurement buildMeasurement2() { return new Measurement.Builder() - .withNumber("123456") + .withMeasurementId("123456") .withAgency("USGS") .withParty("SomeParty2") .withInstant(Instant.parse("2024-02-01T00:00:00Z")) @@ -279,7 +279,7 @@ private SUPP_STREAMFLOW_MEAS_T mockSupplementalStreamflowMeasurement() { } private static void assertMatch(Measurement meas, MeasurementDao.MeasurementXmlDto xmlDto) { - assertEquals(meas.getNumber(), xmlDto.getNumber()); + assertEquals(meas.getMeasurementId(), xmlDto.getNumber()); assertEquals(meas.getAgency(), xmlDto.getAgency()); assertEquals(meas.getParty(), xmlDto.getParty()); assertEquals(meas.getInstant(), xmlDto.getInstant()); diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java index d550376c84..ea66056479 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/MeasurementDaoTestIT.java @@ -176,14 +176,14 @@ void testRoundTripStore() throws Exception { measurementDao.storeMeasurements(measurements, false); List retrievedMeasurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), - null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null); assertEquals(2, retrievedMeasurements.size()); DTOMatch.assertMatch(meas1, retrievedMeasurements.get(0)); DTOMatch.assertMatch(meas1B, retrievedMeasurements.get(1)); List measurementsAll = measurementDao.retrieveMeasurements(OFFICE_ID, null, null, null, UnitSystem.EN.getValue(), - null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null); List meas1List = measurementsAll.stream() .filter(m -> m.getLocationId().equals(streamLocId)) .collect(Collectors.toList()); @@ -199,18 +199,18 @@ void testRoundTripStore() throws Exception { DTOMatch.assertMatch(meas2, meas2Found); retrievedMeasurements = measurementDao.retrieveMeasurements(OFFICE_ID, streamLocId, null, null, UnitSystem.EN.getValue(), - null, null, null, null, null, null, null, null); + null, null, null, null, null, null, null, null, null); Measurement finalMeas1 = meas1; Measurement finalMeas1B = meas1B; Measurement retrievedMeas1 = retrievedMeasurements.stream() - .filter(m -> m.getNumber().equals(finalMeas1.getNumber())) + .filter(m -> m.getMeasurementId().equals(finalMeas1.getMeasurementId())) .findFirst() .orElse(null); assertNotNull(retrievedMeas1); DTOMatch.assertMatch(meas1, retrievedMeas1); Measurement retrievedMeas1B = retrievedMeasurements.stream() - .filter(m -> m.getNumber().equals(finalMeas1B.getNumber())) + .filter(m -> m.getMeasurementId().equals(finalMeas1B.getMeasurementId())) .findFirst() .orElse(null); assertNotNull(retrievedMeas1B); @@ -227,15 +227,15 @@ void testRoundTripStore() throws Exception { assertEquals(meas1B.getInstant(), extentsFound.getTimeExtents().getLatestTime().toInstant()); //delete measurements - measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null); - measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null); + measurementDao.deleteMeasurements(meas1.getId().getOfficeId(), meas1.getId().getName(), null, null, null, null, null); + measurementDao.deleteMeasurements(meas2.getId().getOfficeId(), meas2.getId().getName(), null, null, null, null, null); final Measurement meas1F = meas1; final Measurement meas2F = meas2; assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas1F.getId().getOfficeId(), meas1F.getId().getName(), - null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); + null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null, null)); assertThrows(NotFoundException.class, () -> measurementDao.retrieveMeasurements(meas2F.getId().getOfficeId(), meas2F.getId().getName(), - null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null)); + null, null, UnitSystem.EN.getValue(), null, null, null, null, null, null, null, null, null)); } finally { //delete stream locations streamLocationDao.deleteStreamLocation( @@ -258,7 +258,7 @@ private Measurement buildMeasurement1(String streamLocId) { private Measurement buildMeasurement1(String streamLocId, double flow) { return new Measurement.Builder() - .withNumber("12345") + .withMeasurementId("12345") .withAgency("USGS") .withParty("SomeParty") .withInstant(Instant.parse("2024-01-01T00:00:00Z")) @@ -315,7 +315,7 @@ private Measurement buildMeasurement2(String streamLocId) { private Measurement buildMeasurement2(String streamLocId, double flow) { //same as buildMeasurement but with different values (same office) return new Measurement.Builder() - .withNumber("54321") + .withMeasurementId("54321") .withAgency("USGS") .withParty("SomeParty2") .withInstant(Instant.parse("2024-02-01T00:00:00Z")) @@ -367,7 +367,7 @@ private Measurement buildMeasurement2(String streamLocId, double flow) { private Measurement buildMeasurementDoesntExist(String streamLocId) { return new Measurement.Builder() - .withNumber("0981273") + .withMeasurementId("0981273") .withAgency("USGS") .withParty("SomeParty") .withInstant(Instant.parse("2024-01-01T00:00:00Z")) diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/MeasurementTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/MeasurementTest.java index d37f5616c6..e7b28ed24c 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/MeasurementTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/MeasurementTest.java @@ -39,6 +39,7 @@ import org.apache.commons.io.IOUtils; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -99,7 +100,7 @@ void createMeasurement_allFieldsProvided_success() { .withParty("Survey Party") .withWmComments("Measurement made during normal flow conditions.") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) - .withNumber("123456") + .withMeasurementId("123456") .withStreamflowMeasurement(sfm) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas) .withUsgsMeasurement(usgsMeas) @@ -115,7 +116,7 @@ void createMeasurement_allFieldsProvided_success() { () -> assertEquals("Survey Party", item.getParty(), "Party"), () -> assertEquals("Measurement made during normal flow conditions.", item.getWmComments(), "Comments"), () -> assertNotNull(item.getInstant(), "Instant"), - () -> assertEquals("123456", item.getNumber(), "Measurement number"), + () -> assertEquals("123456", item.getMeasurementId(), "Measurement number"), () -> DTOMatch.assertMatch(cwmsId, item.getId()), () -> DTOMatch.assertMatch(sfm, item.getStreamflowMeasurement()), () -> DTOMatch.assertMatch(supplementalStreamflowMeas, item.getSupplementalStreamflowMeasurement()), @@ -129,7 +130,7 @@ void createMeasurement_missingField_throwsFieldException() { Measurement item = new Measurement.Builder() .withHeightUnit("ft") .withFlowUnit("cfs") - .withNumber("123456") + .withMeasurementId("123456") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) .build(); @@ -202,7 +203,7 @@ void createMeasurement_serialize_roundtrip() { .withParty("Survey Party") .withWmComments("Measurement made during normal flow conditions.") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) - .withNumber("123456") + .withMeasurementId("123456") .withStreamflowMeasurement(sfm) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas) .withUsgsMeasurement(usgsMeas) @@ -269,13 +270,13 @@ void createMeasurement_deserialize() throws Exception { .withParty("Survey Party") .withWmComments("Measurement made during normal flow conditions.") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) - .withNumber("123456") + .withMeasurementId("123456") .withStreamflowMeasurement(sfm) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas) .withUsgsMeasurement(usgsMeas) .build(); - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/measurement.json"); + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/measurement_with_number.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); ContentType contentType = new ContentType(Formats.JSON); @@ -284,6 +285,75 @@ void createMeasurement_deserialize() throws Exception { DTOMatch.assertMatch(expectedMeasurement, deserialized); } + @Test + void createMeasurement_deserialize_meas_id() throws Exception { + CwmsId cwmsId = new CwmsId.Builder() + .withName("Buckhorn") + .withOfficeId("SPK") + .build(); + + StreamflowMeasurement sfm = new StreamflowMeasurement.Builder() + .withGageHeight(5.5) + .withFlow(250.0) + .withQuality("Good") + .build(); + + SupplementalStreamflowMeasurement supplementalStreamflowMeas = new SupplementalStreamflowMeasurement.Builder() + .withChannelFlow(300.0) + .withOverbankFlow(50.0) + .withOverbankMaxDepth(5.0) + .withChannelMaxDepth(10.0) + .withAvgVelocity(1.5) + .withSurfaceVelocity(2.0) + .withMaxVelocity(3.0) + .withEffectiveFlowArea(200.0) + .withCrossSectionalArea(250.0) + .withMeanGage(20.0) + .withTopWidth(30.0) + .withMainChannelArea(150.0) + .withOverbankArea(80.0) + .build(); + + UsgsMeasurement usgsMeas = new UsgsMeasurement.Builder() + .withRemarks("Remarks") + .withCurrentRating("Rating") + .withControlCondition("Condition") + .withFlowAdjustment("Adjustment") + .withShiftUsed(0.1) + .withPercentDifference(5.0) + .withDeltaHeight(0.05) + .withDeltaTime(10.0) + .withAirTemp(20.0) + .withWaterTemp(15.0) + .build(); + + Measurement expectedMeasurement = new Measurement.Builder() + .withId(cwmsId) + .withHeightUnit("ft") + .withFlowUnit("cfs") + .withTempUnit("F") + .withVelocityUnit("ft/s") + .withAreaUnit("sq ft") + .withUsed(true) + .withAgency("USGS") + .withParty("Survey Party") + .withWmComments("Measurement made during normal flow conditions.") + .withInstant(Instant.parse("2024-09-16T00:00:00Z")) + .withMeasurementId("123456") + .withStreamflowMeasurement(sfm) + .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas) + .withUsgsMeasurement(usgsMeas) + .build(); + + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/measurement_with_meas_id.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + ContentType contentType = new ContentType(Formats.JSONV2); + Measurement deserialized = Formats.parseContent(contentType, json, Measurement.class); + + DTOMatch.assertMatch(expectedMeasurement, deserialized); + } + @Test void createMeasurement_deserialize_multiple() throws Exception { CwmsId cwmsId1 = new CwmsId.Builder() @@ -338,7 +408,7 @@ void createMeasurement_deserialize_multiple() throws Exception { .withParty("Survey Party") .withWmComments("Measurement made during normal flow conditions.") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) - .withNumber("123456") + .withMeasurementId("123456") .withStreamflowMeasurement(sfm1) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas1) .withUsgsMeasurement(usgsMeas1) @@ -397,14 +467,14 @@ void createMeasurement_deserialize_multiple() throws Exception { .withParty("Second Survey Party") .withWmComments("Measurement made after recent rainfall.") .withInstant(Instant.parse("2024-09-17T12:00:00Z")) - .withNumber("654321") + .withMeasurementId("654321") .withStreamflowMeasurement(sfm2) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas2) .withUsgsMeasurement(usgsMeas2) .build(); // Reading and Deserializing the JSON - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/measurements.json"); + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/measurements_with_number.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); ContentType contentType = new ContentType(Formats.JSON); @@ -417,7 +487,7 @@ void createMeasurement_deserialize_multiple() throws Exception { } @Test - void testRoundTripMultiple() + void testRoundTripMultiple_v1_and_v2() { CwmsId cwmsId1 = new CwmsId.Builder() .withName("Buckhorn") @@ -471,7 +541,7 @@ void testRoundTripMultiple() .withParty("Survey Party") .withWmComments("Measurement made during normal flow conditions.") .withInstant(Instant.parse("2024-09-16T00:00:00Z")) - .withNumber("123456") + .withMeasurementId("123456") .withStreamflowMeasurement(sfm1) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas1) .withUsgsMeasurement(usgsMeas1) @@ -530,7 +600,7 @@ void testRoundTripMultiple() .withParty("Second Survey Party") .withWmComments("Measurement made after recent rainfall.") .withInstant(Instant.parse("2024-09-17T12:00:00Z")) - .withNumber("654321") + .withMeasurementId("654321") .withStreamflowMeasurement(sfm2) .withSupplementalStreamflowMeasurement(supplementalStreamflowMeas2) .withUsgsMeasurement(usgsMeas2) @@ -540,12 +610,23 @@ void testRoundTripMultiple() measurements.add(expectedMeasurement1); measurements.add(expectedMeasurement2); - ContentType contentType = new ContentType(Formats.JSON); + ContentType contentType = new ContentType(Formats.JSONV1); String serialized = Formats.format(contentType, measurements, Measurement.class); + assertTrue(serialized.contains("number")); List deserialized = Formats.parseContentList(contentType, serialized, Measurement.class); assertEquals(2, deserialized.size(), "Expected 2 measurements"); DTOMatch.assertMatch(expectedMeasurement1, deserialized.get(0)); DTOMatch.assertMatch(expectedMeasurement2, deserialized.get(1)); + + contentType = new ContentType(Formats.JSONV2); + serialized = Formats.format(contentType, measurements, Measurement.class); + assertFalse(serialized.contains("number")); + assertTrue(serialized.contains("measurement-id")); + deserialized = Formats.parseContentList(contentType, serialized, Measurement.class); + + assertEquals(2, deserialized.size(), "Expected 2 measurements"); + DTOMatch.assertMatch(expectedMeasurement1, deserialized.get(0)); + DTOMatch.assertMatch(expectedMeasurement2, deserialized.get(1)); } } diff --git a/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java b/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java index 415f7b7f12..edaa8561a1 100644 --- a/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java +++ b/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java @@ -523,7 +523,7 @@ public static void assertMatch(Measurement first, Measurement second) { () -> assertEquals(first.getParty(), second.getParty(), "Party does not match"), () -> assertEquals(first.getWmComments(), second.getWmComments(), "WM Comments do not match"), () -> assertEquals(first.getInstant(), second.getInstant(), "Instant does not match"), - () -> assertEquals(first.getNumber(), second.getNumber(), "Number does not match"), + () -> assertEquals(first.getMeasurementId(), second.getMeasurementId(), "Number does not match"), () -> assertMatch(first.getStreamflowMeasurement(), second.getStreamflowMeasurement()), () -> assertMatch(first.getSupplementalStreamflowMeasurement(), second.getSupplementalStreamflowMeasurement()), () -> assertMatch(first.getUsgsMeasurement(), second.getUsgsMeasurement()) diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/measurements_with_meas_id.json b/cwms-data-api/src/test/resources/cwms/cda/api/measurements_with_meas_id.json new file mode 100644 index 0000000000..01eabec932 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/measurements_with_meas_id.json @@ -0,0 +1,100 @@ +[ + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "measurement-id": "123456", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.5, + "flow": 250.0, + "quality": "Good" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "1", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } + }, + { + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "fps", + "area-unit": "ft2", + "used": true, + "agency": "USGS", + "party": "SPK", + "wm-comments": "Measurement made after recent rainfall.", + "instant": "2024-09-17T12:00:00Z", + "measurement-id": "550e8400-e29b-41d4-a716-446655440000", + "id": { + "name": "StreamLoc321", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 6.0, + "flow": 275.0, + "quality": "Fair" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 320.0, + "overbank-flow": 45.0, + "overbank-max-depth": 4.5, + "channel-max-depth": 9.5, + "avg-velocity": 1.8, + "surface-velocity": 2.5, + "max-velocity": 3.5, + "effective-flow-area": 220.0, + "cross-sectional-area": 260.0, + "mean-gage": 21.0, + "top-width": 32.0, + "main-channel-area": 160.0, + "overbank-area": 85.0 + }, + "usgs-measurement": { + "remarks": "Post-rain conditions.", + "current-rating": "2", + "control-condition": "FILL", + "flow-adjustment": "OTHR", + "shift-used": 0.15, + "percent-difference": 4.5, + "delta-height": 0.1, + "delta-time": 15.0, + "air-temp": 18.0, + "water-temp": 16.0 + } + } +] diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement_with_meas_id.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement_with_meas_id.json new file mode 100644 index 0000000000..b3c64031b2 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement_with_meas_id.json @@ -0,0 +1,49 @@ +{ + "height-unit": "ft", + "flow-unit": "cfs", + "temp-unit": "F", + "velocity-unit": "ft/s", + "area-unit": "sq ft", + "used": true, + "agency": "USGS", + "party": "Survey Party", + "wm-comments": "Measurement made during normal flow conditions.", + "instant": "2024-09-16T00:00:00Z", + "measurement-id": "123456", + "id": { + "name": "Buckhorn", + "office-id": "SPK" + }, + "streamflow-measurement": { + "gage-height": 5.5, + "flow": 250.0, + "quality": "Good" + }, + "supplemental-streamflow-measurement": { + "channel-flow": 300.0, + "overbank-flow": 50.0, + "overbank-max-depth": 5.0, + "channel-max-depth": 10.0, + "avg-velocity": 1.5, + "surface-velocity": 2.0, + "max-velocity": 3.0, + "effective-flow-area": 200.0, + "cross-sectional-area": 250.0, + "mean-gage": 20.0, + "top-width": 30.0, + "main-channel-area": 150.0, + "overbank-area": 80.0 + }, + "usgs-measurement": { + "remarks": "Remarks", + "current-rating": "Rating", + "control-condition": "Condition", + "flow-adjustment": "Adjustment", + "shift-used": 0.1, + "percent-difference": 5.0, + "delta-height": 0.05, + "delta-time": 10.0, + "air-temp": 20.0, + "water-temp": 15.0 + } +} diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement_with_number.json similarity index 100% rename from cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement.json rename to cwms-data-api/src/test/resources/cwms/cda/data/dto/measurement_with_number.json diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurements.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/measurements_with_number.json similarity index 100% rename from cwms-data-api/src/test/resources/cwms/cda/data/dto/measurements.json rename to cwms-data-api/src/test/resources/cwms/cda/data/dto/measurements_with_number.json