diff --git a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java index df67d8d6d..5e37ea63a 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java @@ -223,8 +223,7 @@ public void delete(@NotNull Context ctx, @NotNull String levelId) { + "the default SI units for their parameters." + "\n* `Other` " + "Any unit returned in the response to the units URI request that is " - + "appropriate for the requested parameters. The " + Formats.JSONV2 - + " format currently only supports SI."), + + "appropriate for the requested parameters. "), @OpenApiParam(name = DATUM, description = "Specifies the elevation datum of" + " the response. This field affects only elevation location levels. " + "Valid values for this field are:" @@ -284,7 +283,7 @@ public void getAll(@NotNull Context ctx) { name(LevelsController.class.getName(), GET_ALL)); String office = ctx.queryParam(OFFICE); - String unit = ctx.queryParam(UNIT); + String unit = ctx.queryParamAsClass(UNIT, String.class).getOrDefault(UnitSystem.SI.getValue()); String datum = ctx.queryParam(DATUM); String begin = ctx.queryParam(BEGIN); String end = ctx.queryParam(END); @@ -294,14 +293,6 @@ public void getAll(@NotNull Context ctx) { if ("2".equals(version)) { - if (unit == null) { - // The dao currently only supports SI. - unit = UnitSystem.SI.getValue(); - } - if (!UnitSystem.SI.getValue().equals(unit)) { - throw new IllegalArgumentException("Levels Version 2 currently only supports SI"); - } - String cursor = ctx.queryParamAsClass(PAGE, String.class) .getOrDefault(""); int pageSize = ctx.queryParamAsClass(PAGE_SIZE, Integer.class) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDaoImpl.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDaoImpl.java index 86e203326..3053f6552 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDaoImpl.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDaoImpl.java @@ -37,23 +37,20 @@ import cwms.cda.data.dto.LocationLevels; import cwms.cda.data.dto.SeasonalValueBean; import cwms.cda.data.dto.TimeSeries; -import hec.data.DataSetException; -import hec.data.DataSetIllegalArgumentException; +import hec.data.Duration; import hec.data.Parameter; +import hec.data.ParameterType; import hec.data.Units; -import hec.data.UnitsConversionException; import hec.data.level.IAttributeParameterTypedValue; import hec.data.level.ILocationLevelRef; -import hec.data.level.ISeasonalValue; -import hec.data.level.JDomLocationLevelImpl; +import hec.data.level.IParameterTypedValue; +import hec.data.level.ISpecifiedLevel; +import hec.data.level.JDomLocationLevelRef; import hec.data.level.JDomSeasonalIntervalImpl; import hec.data.level.JDomSeasonalValueImpl; -import hec.data.level.JDomSeasonalValuesImpl; import hec.data.location.LocationTemplate; import java.math.BigDecimal; import java.math.BigInteger; -import java.math.MathContext; -import java.math.RoundingMode; import java.sql.Timestamp; import java.time.Instant; import java.time.ZoneId; @@ -70,7 +67,6 @@ import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; import java.util.regex.Pattern; import mil.army.usace.hec.metadata.Interval; import mil.army.usace.hec.metadata.IntervalFactory; @@ -84,9 +80,9 @@ import org.jooq.Record1; import org.jooq.SelectLimitPercentAfterOffsetStep; import org.jooq.TableField; +import org.jooq.conf.ParamType; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; -import org.jooq.types.DayToSecond; import usace.cwms.db.dao.ifc.level.CwmsDbLevel; import usace.cwms.db.dao.ifc.level.LocationLevelPojo; import usace.cwms.db.dao.util.OracleTypeMap; @@ -143,8 +139,6 @@ public LocationLevels getLocationLevels(String cursor, int pageSize, usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; - - // Only supports SI for now. Controller will throw an exception if not SI. Condition whereCondition = DSL.upper(view.UNIT_SYSTEM).eq(unit.toUpperCase()); if (office != null && !office.isEmpty()) { @@ -165,7 +159,7 @@ public LocationLevels getLocationLevels(String cursor, int pageSize, Timestamp.from(endZdt.toInstant()))); } - Map levelMap = new LinkedHashMap<>(); + Map builderMap = new LinkedHashMap<>(); SelectLimitPercentAfterOffsetStep query = dsl.selectDistinct(getAddSeasonalValueFields()) .from(view) @@ -176,14 +170,13 @@ public LocationLevels getLocationLevels(String cursor, int pageSize, .offset(offset) .limit(pageSize); - // logger.fine(() -> "getLocationLevels query: " + query.getSQL(ParamType.INLINED)); + logger.info(() -> "getLocationLevels query: " + query.getSQL(ParamType.INLINED)); - query.stream().forEach(r -> addSeasonalValue(r, levelMap)); + query.stream().forEach(r -> addSeasonalValue(r, builderMap)); List levels = new java.util.ArrayList<>(); - for (JDomLocationLevelImpl levelImpl : levelMap.values()) { - LocationLevel level = new LocationLevel.Builder(levelImpl).build(); - levels.add(level); + for (LocationLevel.Builder builder : builderMap.values()) { + levels.add(builder.build()); } LocationLevels.Builder builder = new LocationLevels.Builder(offset, pageSize, total); @@ -191,6 +184,40 @@ public LocationLevels getLocationLevels(String cursor, int pageSize, return builder.build(); } + private static class LevelLookup { + private final JDomLocationLevelRef locationLevelRef; + private final Date effectiveDate; + + public LevelLookup(String officeId, String locLevelId, String attributeId, String attributeValue, String attributeUnits, Date effectiveDate) { + this(new JDomLocationLevelRef(officeId, locLevelId, attributeId, attributeValue, attributeUnits), effectiveDate); + } + + public LevelLookup(JDomLocationLevelRef locationLevelRef, Date effectiveDate) { + this.locationLevelRef = locationLevelRef; + this.effectiveDate = effectiveDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LevelLookup that = (LevelLookup) o; + return Objects.equals(locationLevelRef, that.locationLevelRef) && Objects.equals(effectiveDate, that.effectiveDate); + } + + @Override + public int hashCode() { + int result = Objects.hashCode(locationLevelRef); + result = 31 * result + Objects.hashCode(effectiveDate); + return result; + } + } + @Override public void storeLocationLevel(LocationLevel locationLevel, ZoneId zoneId) { @@ -283,22 +310,20 @@ public static SeasonalValueBean buildSeasonalValue(usace.cwms.db.dao.ifc.level.S } @NotNull - private static JDomSeasonalValueImpl buildSeasonalValue(JDomLocationLevelImpl locationLevelImpl, - String levelSiUnit, Double seasLevel, - JDomSeasonalIntervalImpl newSeasonalOffset) { + private static JDomSeasonalValueImpl buildSeasonalValue(String levelSiUnit, Double seasLevel, + JDomSeasonalIntervalImpl newSeasonalOffset, IParameterTypedValue prototypeLevel, String parameterUnits) { // create new seasonal value with current record information JDomSeasonalValueImpl newSeasonalValue = new JDomSeasonalValueImpl(); newSeasonalValue.setOffset(newSeasonalOffset); - newSeasonalValue.setPrototypeParameterType(locationLevelImpl.getPrototypeLevel()); + newSeasonalValue.setPrototypeParameterType(prototypeLevel); // make sure that it is in the correct units. - String parameterUnits = locationLevelImpl.getParameter().getUnitsString(); if (Units.canConvertBetweenUnits(levelSiUnit, parameterUnits)) { seasLevel = Units.convertUnits(seasLevel, levelSiUnit, parameterUnits); // constant value newSeasonalValue.setSiParameterUnitsValue(seasLevel); - locationLevelImpl.setUnits(parameterUnits); + //locationLevelImpl.setUnits(parameterUnits); // pretty sure we don't have to do this. } else { newSeasonalValue.setSiParameterUnitsValue(seasLevel); } @@ -403,8 +428,8 @@ private LocationLevel getLevelFromPojo(LocationLevelPojo copyFromPojo, // These are all the fields that we need to pull out of jOOQ record for addSeasonalValue - private Collection getAddSeasonalValueFields() { - Set retval = new LinkedHashSet<>(); + private Collection> getAddSeasonalValueFields() { + Set> retval = new LinkedHashSet<>(); retval.add(AV_LOCATION_LEVEL.OFFICE_ID); retval.add(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID); @@ -425,104 +450,133 @@ private Collection getAddSeasonalValueFields() { return retval; } + private void addSeasonalValue(Record r, - Map levelMap) { + Map builderMap) { usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; - JDomLocationLevelImpl locationLevelImpl = buildLocationLevel(r); - - String levelSiUnit = r.get(view.LEVEL_UNIT); + Timestamp levelDateTimestamp = r.get(view.LEVEL_DATE); + String attrId = r.get(view.ATTRIBUTE_ID); + Double oattrVal = r.get(view.ATTRIBUTE_VALUE); + String locLevelId = r.get(view.LOCATION_LEVEL_ID); + String officeId = r.get(view.OFFICE_ID); + String levelUnit = r.get(view.LEVEL_UNIT); + String attrUnit = r.get(AV_LOCATION_LEVEL.ATTRIBUTE_UNIT); - String interp = r.get(view.INTERPOLATE); - Boolean boolInterp = null; - if (interp != null) { - boolInterp = OracleTypeMap.parseBool(interp); + Date levelDate = null; + if (levelDateTimestamp != null) { + levelDate = new Date(levelDateTimestamp.getTime()); } - // set interpolated value - locationLevelImpl.setInterpolateSeasonal(boolInterp); - String levelComment = r.get(view.LEVEL_COMMENT); - if (levelMap.containsKey(locationLevelImpl)) { - locationLevelImpl = levelMap.get(locationLevelImpl); - } else { - levelMap.put(locationLevelImpl, locationLevelImpl); - - // always SI parameter units - Double constLevel = r.get(view.CONSTANT_LEVEL); - String attrComment = r.get(view.ATTRIBUTE_COMMENT); - - setLevelData(r.get(view.TSID), constLevel, attrComment, locationLevelImpl, - levelSiUnit, levelComment); + String attrStr = null; + if (oattrVal != null) { + attrStr = oattrVal.toString(); // this is weird. allow it for now but maybe this should be doing some rounding? } + JDomLocationLevelRef locationLevelRef = new JDomLocationLevelRef(officeId, locLevelId, attrId, attrStr, attrUnit); + LevelLookup levelLookup = new LevelLookup(locationLevelRef, levelDate); - // seasonal stuff - Timestamp intervalOriginDateTimeStamp = r.get(view.INTERVAL_ORIGIN); - Date intervalOriginDate = null; - if (intervalOriginDateTimeStamp != null) { - intervalOriginDate = new Date(intervalOriginDateTimeStamp.getTime()); - } - - parseSeasonalValues(r, intervalOriginDate, locationLevelImpl, levelSiUnit); - } + LocationLevel.Builder builder; + if (builderMap.containsKey(levelLookup)) { + builder = builderMap.get(levelLookup); + } else { + ZonedDateTime levelZdt = null; + if (levelDate != null) { + levelZdt = ZonedDateTime.ofInstant(levelDate.toInstant(), ZoneId.of("UTC")); + } + builder = new LocationLevel.Builder(locLevelId, levelZdt); + builder = withLocationLevelRef(builder, locationLevelRef); - @NotNull - private JDomLocationLevelImpl buildLocationLevel(Record r) { - usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; + builder.withAttributeParameterId(attrId); + builder.withAttributeUnitsId(attrUnit); + builder.withLevelUnitsId(levelUnit); - Timestamp levelDateTimestamp = r.get(view.LEVEL_DATE); + if (oattrVal != null) { + builder.withAttributeValue(BigDecimal.valueOf(oattrVal)); + } + builder.withLevelComment(r.get(view.LEVEL_COMMENT)); + builder.withAttributeComment(r.get(view.ATTRIBUTE_COMMENT)); + builder.withConstantValue(r.get(view.CONSTANT_LEVEL)); + builder.withSeasonalTimeSeriesId(r.get(view.TSID)); - Date levelDate = null; - if (levelDateTimestamp != null) { - levelDate = new Date(levelDateTimestamp.getTime()); + builderMap.put(levelLookup, builder); } - String attrId = r.get(view.ATTRIBUTE_ID); - Double oattrVal = r.get(view.ATTRIBUTE_VALUE); - StringValueUnits attrVal = parseAttributeValue(r, attrId, oattrVal); - String locLevelId = r.get(view.LOCATION_LEVEL_ID); - - String officeId = r.get(view.OFFICE_ID); - String levelSiUnit = r.get(view.LEVEL_UNIT); + String interp = r.get(view.INTERPOLATE); + builder.withInterpolateString(interp); - // built to compare the loc level ref and eff date. + Double seasonalLevel = r.get(view.SEASONAL_LEVEL); - return new JDomLocationLevelImpl(officeId, locLevelId, - levelDate, levelSiUnit, attrId, attrVal.value, attrVal.units); + if (seasonalLevel != null) { +// JDomSeasonalValuesImpl seasonalValuesImpl = new JDomSeasonalValuesImpl(); +// +// Timestamp intervalOriginDateTimeStamp = r.get(view.INTERVAL_ORIGIN); +// // seasonal stuff +// Date intervalOriginDate = null; +// if (intervalOriginDateTimeStamp != null) { +// intervalOriginDate = new Date(intervalOriginDateTimeStamp.getTime()); +// } +// +// seasonalValuesImpl.setOrigin(intervalOriginDate); +// +// String calInterval = r.get(view.CALENDAR_INTERVAL); +// DayToSecond dayToSecond = r.get(view.TIME_INTERVAL); +// JDomSeasonalIntervalImpl offset = new JDomSeasonalIntervalImpl(); +// offset.setYearMonthString(calInterval); +// if (dayToSecond != null) { +// offset.setDaysHoursMinutesString(dayToSecond.toString()); +// } +// seasonalValuesImpl.setOffset(offset); + // TODO: LocationLevel is missing seasonal origin and offset. + + String calOffset = r.get(view.CALENDAR_OFFSET); + String timeOffset = r.get(view.TIME_OFFSET); + JDomSeasonalIntervalImpl newSeasonalOffset = buildSeasonalOffset(calOffset, timeOffset); + SeasonalValueBean seasonalValue = buildSeasonalValueBean(seasonalLevel, newSeasonalOffset) ; + builder.withSeasonalValue(seasonalValue); + } } - private void setLevelData(String tsid, Double constLevel, String attrComment, - JDomLocationLevelImpl locationLevelImpl, String levelSiUnit, - String levelComment) throws UnitsConversionException { - locationLevelImpl.setLevelComment(levelComment); + private LocationLevel.Builder withLocationLevelRef(LocationLevel.Builder builder, JDomLocationLevelRef locationLevelRef) { + ISpecifiedLevel specifiedLevel = locationLevelRef.getSpecifiedLevel(); + if (specifiedLevel != null) { + builder = builder.withSpecifiedLevelId(specifiedLevel.getId()); + } - if (locationLevelImpl.getLocationLevelRef().getAttribute() != null) { - locationLevelImpl.getLocationLevelRef().getAttribute().setComment(attrComment); + Parameter parameter = locationLevelRef.getParameter(); + if (parameter != null) { + builder = builder.withParameterId(parameter.toString()); } - // set the level value - if (constLevel != null) { - // make sure that it is in the correct units. - String parameterUnits = locationLevelImpl.getParameter().getUnitsString(); - if (Units.canConvertBetweenUnits(levelSiUnit, parameterUnits)) { - constLevel = Units.convertUnits(constLevel, levelSiUnit, parameterUnits); - // constant value - locationLevelImpl.setSiParameterUnitsConstantValue(constLevel); - locationLevelImpl.setUnits(parameterUnits); - } else { - locationLevelImpl.setSiParameterUnitsConstantValue(constLevel); - } + + ParameterType parameterType = locationLevelRef.getParameterType(); + if (parameterType != null) { + builder = builder.withParameterTypeId(parameterType.toString()); } - // seasonal time series - if (tsid != null) { - locationLevelImpl.setSeasonalTimeSeriesId(tsid); + Duration duration = locationLevelRef.getDuration(); + if (duration != null) { + builder = builder.withDurationId(duration.toString()); } + + + return builder + .withOfficeId(locationLevelRef.getOfficeId()) + ; + } + + private SeasonalValueBean buildSeasonalValueBean(Double seasonalLevel, + JDomSeasonalIntervalImpl offset) { + // Avoiding JDomSeasonalValueImpl b/c it does units conversion to SI. + return new SeasonalValueBean.Builder(seasonalLevel) + .withOffsetMinutes(BigInteger.valueOf(offset.getTotalMinutes())) + .withOffsetMonths(offset.getTotalMonths()) + .build(); } // These are all the fields that we need to pull out of jOOQ record for parseSeasonalValues - private Collection getParseSeasonalValuesFields() { - Set retval = new LinkedHashSet<>(); + private Collection> getParseSeasonalValuesFields() { + Set> retval = new LinkedHashSet<>(); retval.add(AV_LOCATION_LEVEL.SEASONAL_LEVEL); retval.add(AV_LOCATION_LEVEL.CALENDAR_INTERVAL); @@ -534,44 +588,6 @@ private Collection getParseSeasonalValuesFields() { } - private void parseSeasonalValues(Record rs, Date intervalOriginDate, - JDomLocationLevelImpl locationLevelImpl, String levelSiUnit) - throws DataSetException { - usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; - // seasonal val - Double seasonalLevel = rs.get(view.SEASONAL_LEVEL); - - if (seasonalLevel != null) { - // retrieve existing seasonal value stuff - JDomSeasonalValuesImpl seasonalValuesImpl = locationLevelImpl.getSeasonalValuesObject(); - if (seasonalValuesImpl == null) { - seasonalValuesImpl = new JDomSeasonalValuesImpl(); - seasonalValuesImpl.setOrigin(intervalOriginDate); - JDomSeasonalIntervalImpl offset = new JDomSeasonalIntervalImpl(); - String calInterval = rs.get(view.CALENDAR_INTERVAL); - offset.setYearMonthString(calInterval); - DayToSecond dayToSecond = rs.get(view.TIME_INTERVAL); - if (dayToSecond != null) { - offset.setDaysHoursMinutesString(dayToSecond.toString()); - } - seasonalValuesImpl.setOffset(offset); - locationLevelImpl.setSeasonalValuesObject(seasonalValuesImpl); - } - - // retrieve list of existing seasonal values - List seasonalValues = seasonalValuesImpl.getSeasonalValues(); - - String calOffset = rs.get(view.CALENDAR_OFFSET); - String timeOffset = rs.get(view.TIME_OFFSET); - JDomSeasonalIntervalImpl newSeasonalOffset = buildSeasonalOffset(calOffset, timeOffset); - JDomSeasonalValueImpl newSeasonalValue = buildSeasonalValue(locationLevelImpl, - levelSiUnit, seasonalLevel, newSeasonalOffset); - // add new seasonal value to existing seasonal values - seasonalValues.add(newSeasonalValue); - seasonalValuesImpl.setSeasonalValues(seasonalValues); - } - } - @NotNull private static JDomSeasonalIntervalImpl buildSeasonalOffset(String calOffset, String timeOffset) { @@ -582,51 +598,6 @@ private static JDomSeasonalIntervalImpl buildSeasonalOffset(String calOffset, return newSeasonalOffset; } - private static class StringValueUnits { - String value; - String units; - } - - private StringValueUnits parseAttributeValue(Record rs, String attrId, Double oattrVal) { - // query pulls SI parameter units - String attrSiUnit = rs.get(AV_LOCATION_LEVEL.ATTRIBUTE_UNIT); - StringValueUnits attrVal = new StringValueUnits(); - if (attrId != null) { - // we want are attributes in en parameter units. - // this should be done via an oracle procedure call. - Matcher matcher = attributeIdParsingPattern.matcher(attrId); - if (!matcher.matches() || matcher.groupCount() != 3) { - throw new DataSetException("Illegal location level attribute identifier: " + attrId); - } - // Flow.Max.6Hours - String sattrParam = matcher.group(1); - getEnAttributeValue(attrVal, oattrVal, attrSiUnit, sattrParam); - } - return attrVal; - } - - private void getEnAttributeValue(StringValueUnits stringValueUnits, Double oattrVal, - String attrSiUnit, - String parameterId) throws DataSetIllegalArgumentException, - UnitsConversionException { - String attrEnUnit = null; - String attrVal = null; - if (oattrVal != null && attrSiUnit != null && parameterId != null) { - Parameter parameter = new Parameter(parameterId); - attrEnUnit = parameter.getUnitsStringForSystem(Units.ENGLISH_ID); - double siAttrVal = ((Number) oattrVal).doubleValue(); - if (Units.canConvertBetweenUnits(attrSiUnit, attrEnUnit)) { - double enSiVal = Units.convertUnits(siAttrVal, attrSiUnit, attrEnUnit); - BigDecimal bd = BigDecimal.valueOf(enSiVal); - BigDecimal setScale = bd.setScale(9, RoundingMode.HALF_UP); - BigDecimal round = setScale.round(new MathContext(9, RoundingMode.HALF_UP)); - attrVal = round.toPlainString(); - } - } - stringValueUnits.value = attrVal; - stringValueUnits.units = attrEnUnit; - } - @Override public TimeSeries retrieveLocationLevelAsTimeSeries(ILocationLevelRef levelRef, Instant start, Instant end, diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java index 5c009196f..55d4681db 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java @@ -50,8 +50,8 @@ public final class LocationLevel extends CwmsDTO { @Schema(description = "Generic name of this location level. Common names are 'Top of Dam', " + "'Streambed', 'Bottom of Dam'.") private final String specifiedLevelId; - @Schema(description = "To indicate if single or aggregate value", allowableValues = {"Inst", - "Ave", "Min", "Max", "Total"}) + @Schema(description = "To indicate if single or aggregate value", + allowableValues = {"Inst", "Ave", "Min", "Max", "Total"}) private final String parameterTypeId; @Schema(description = "Data Type such as Stage, Elevation, or others.") private final String parameterId; @@ -291,10 +291,8 @@ public Builder(JDomLocationLevelImpl copyFrom) { withParameterId(copyFrom.getParameterId()); withParameterTypeId(copyFrom.getParameterTypeId()); withSeasonalTimeSeriesId(copyFrom.getSeasonalTimeSeriesId()); - ISeasonalValues values = copyFrom.getSeasonalValues(); - if (values != null) { - withSeasonalValues(buildSeasonalValues(values)); - } + withISeasonalValues(copyFrom.getSeasonalValues()); + IParameterTypedValue constantLevel = copyFrom.getConstantLevel(); if (constantLevel != null) { withConstantValue(constantLevel.getSiParameterUnitsValue()); @@ -374,13 +372,44 @@ public Builder withSeasonalValues(List seasonalValues) { return this; } + @JsonIgnore + public Builder withISeasonalValues(ISeasonalValues values) { + if (values != null) { + // TODO: handle values.offset and values.origin + withSeasonalValues(buildSeasonalValues(values)); + } else { + this.seasonalValues = null; + } + + return this; + } + + public Builder withSeasonalValue(SeasonalValueBean seasonalValue) { + if (seasonalValues == null) { + seasonalValues = new ArrayList<>(); + } + seasonalValues.add(seasonalValue); + return this; + } + public static SeasonalValueBean buildSeasonalValueBean(ISeasonalValue seasonalValue) { - ISeasonalInterval offset = seasonalValue.getOffset(); - IParameterTypedValue value = seasonalValue.getValue(); - return new SeasonalValueBean.Builder(value.getSiParameterUnitsValue()) - .withOffsetMinutes(BigInteger.valueOf(offset.getTotalMinutes())) - .withOffsetMonths(offset.getTotalMonths()) - .build(); + SeasonalValueBean retval = null; + if (seasonalValue != null) { + IParameterTypedValue value = seasonalValue.getValue(); + + if (value != null) { + SeasonalValueBean.Builder builder = + new SeasonalValueBean.Builder(value.getSiParameterUnitsValue()); + + ISeasonalInterval offset = seasonalValue.getOffset(); + if (offset != null) { + builder.withOffsetMinutes(BigInteger.valueOf(offset.getTotalMinutes())) + .withOffsetMonths(offset.getTotalMonths()); + } + retval = builder.build(); + } + } + return retval; } public static List buildSeasonalValues(ISeasonalValues seasonalValues) { diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java index 70aa1f93e..858ae7501 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/LevelsControllerTestIT.java @@ -32,12 +32,13 @@ import fixtures.TestAccounts; import io.restassured.filter.log.LogDetail; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; import org.jooq.DSLContext; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import javax.servlet.http.HttpServletResponse; -import java.sql.Connection; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; @@ -47,6 +48,7 @@ import static cwms.cda.api.Controllers.*; import static io.restassured.RestAssured.given; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -66,7 +68,7 @@ void test_location_level() throws Exception { .withLevelUnitsId("ac-ft") .build(); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - DSLContext dsl = dslContext((Connection) c, OFFICE); + DSLContext dsl = dslContext(c, OFFICE); LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); dao.storeLocationLevel(level, level.getLevelDate().getZone()); }); @@ -86,11 +88,11 @@ void test_location_level() throws Exception { .assertThat() .log().ifValidationFails(LogDetail.ALL,true) .statusCode(is(HttpServletResponse.SC_OK)) - .body("level-units-id",equalTo("m3")) + .body("level-units-id", equalTo("m3")) // I think we need to create a custom matcher. // This really shouldn't use equals but due to a quirk in // RestAssured it appears to be necessary. - .body("constant-value",equalTo(1233.4818f)); // 1 ac-ft to m3 + .body("constant-value", equalTo(1233.4818f)); // 1 ac-ft to m3 given() .log().ifValidationFails(LogDetail.ALL,true) @@ -129,7 +131,7 @@ void test_level_as_timeseries() throws Exception { .build(); levels.put(level.getLevelDate().toInstant(), level); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - DSLContext dsl = dslContext((Connection) c, OFFICE); + DSLContext dsl = dslContext(c, OFFICE); LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); dao.storeLocationLevel(level, level.getLevelDate().getZone()); }); @@ -165,13 +167,181 @@ void test_level_as_timeseries() throws Exception { assertEquals(24 * effectiveDateCount + 1, timeSeries.getTotal()); List values = timeSeries.getValues(); for (int i = 0; i < values.size(); i++) { - TimeSeries.Record record = values.get(i); - assertEquals(time.plusHours(i).toInstant(), record.getDateTime().toInstant(), "Time check failed at iteration: " + i); - assertEquals(0, record.getQualityCode(), "Quality check failed at iteration: " + i); - Double constantValue = levels.floorEntry(record.getDateTime().toInstant()) + TimeSeries.Record tsrec = values.get(i); + assertEquals(time.plusHours(i).toInstant(), tsrec.getDateTime().toInstant(), "Time check failed at iteration: " + i); + assertEquals(0, tsrec.getQualityCode(), "Quality check failed at iteration: " + i); + Double constantValue = levels.floorEntry(tsrec.getDateTime().toInstant()) .getValue() .getConstantValue(); - assertEquals(constantValue, record.getValue(), 0.0001, "Value check failed at iteration: " + i); + assertEquals(constantValue, tsrec.getValue(), 0.0001, "Value check failed at iteration: " + i); } } + + + @Test + void test_get_all_location_level() throws Exception { + String locId = "level_get_all_loc1"; + String levelId = locId + ".Stor.Ave.1Day.Regulating"; + createLocation(locId, true, OFFICE); + final ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America" + + "/Los_Angeles")); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + LocationLevel level = new LocationLevel.Builder(levelId, time) + .withOfficeId(OFFICE) + .withConstantValue(1.0) + .withLevelUnitsId("ac-ft") + .build(); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level, level.getLevelDate().getZone()); + }); + + String locId2 = "level_get_all_loc2"; + String levelId2 = locId2 + ".Stor.Ave.1Day.Regulating"; + createLocation(locId2, true, OFFICE); + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + + LocationLevel level = new LocationLevel.Builder(levelId2, time) + .withOfficeId(OFFICE) + .withConstantValue(2.0) + .withLevelUnitsId("ac-ft") + .build(); + DSLContext dsl = dslContext(c, OFFICE); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level, level.getLevelDate().getZone()); + }); + + String startStr = "2023-06-01T00:00:00Z"; + String endStr = "2023-06-02T00:00:00Z"; + + + + //Read level without unit + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(LEVEL_ID_MASK, "level_get_all.*") + .queryParam(BEGIN, startStr) + .queryParam(END, endStr) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .assertThat() + .log().ifValidationFails(LogDetail.ALL, true) + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + assertThat(response.path("levels.size()"),is(2)); + + assertThat(response.path("levels[0].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[0].location-level-id"),equalTo(levelId)); + assertThat(response.path("levels[0].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[0].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[0].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[0].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[0].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[0].duration-id"),equalTo("1Day")); + double actual0 = Float.valueOf((float) response.path("levels[0].constant-value")).doubleValue(); + assertThat(actual0, closeTo(1233.0, 10.0)); + + assertThat(response.path("levels[1].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[1].location-level-id"),equalTo(levelId2)); + assertThat(response.path("levels[1].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[1].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[1].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[1].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[1].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[1].duration-id"),equalTo("1Day")); + double actual1 = Float.valueOf((float) response.path("levels[1].constant-value")).doubleValue(); + assertThat(actual1, closeTo(2466.9636f, 1.0)); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(UNIT, "SI") + .queryParam(LEVEL_ID_MASK, "level_get_all.*") + .queryParam(BEGIN, startStr) + .queryParam(END, endStr) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .assertThat() + .log().ifValidationFails(LogDetail.ALL, true) + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + assertThat(response.path("levels.size()"),is(2)); + + assertThat(response.path("levels[0].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[0].location-level-id"),equalTo(levelId)); + assertThat(response.path("levels[0].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[0].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[0].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[0].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[0].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[0].duration-id"),equalTo("1Day")); + actual0 = Float.valueOf((float) response.path("levels[0].constant-value")).doubleValue(); + assertThat(actual0, closeTo(1233.4818, 1.0)); + + assertThat(response.path("levels[1].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[1].location-level-id"),equalTo(levelId2)); + assertThat(response.path("levels[1].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[1].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[1].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[1].level-units-id"),equalTo("m3")); + assertThat(response.path("levels[1].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[1].duration-id"),equalTo("1Day")); + actual1 = Float.valueOf((float) response.path("levels[1].constant-value")).doubleValue(); + assertThat(actual1, closeTo(2466.9636, 1.0)); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(UNIT, "EN") + .queryParam(LEVEL_ID_MASK, "level_get_all.*") + .queryParam(BEGIN, startStr) + .queryParam(END, endStr) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .assertThat() + .log().ifValidationFails(LogDetail.ALL, true) + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + assertThat(response.path("levels.size()"),is(2)); + + assertThat(response.path("levels[0].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[0].location-level-id"),equalTo(levelId)); + assertThat(response.path("levels[0].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[0].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[0].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[0].level-units-id"),equalTo("ac-ft")); + assertThat(response.path("levels[0].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[0].duration-id"),equalTo("1Day")); + actual0 = Float.valueOf((float) response.path("levels[0].constant-value")).doubleValue(); + assertThat(actual0, closeTo(1.0, 0.01)); + + assertThat(response.path("levels[1].office-id"),equalTo(OFFICE)); + assertThat(response.path("levels[1].location-level-id"),equalTo(levelId2)); + assertThat(response.path("levels[1].specified-level-id"),equalTo("Regulating")); + assertThat(response.path("levels[1].parameter-type-id"),equalTo("Ave")); + assertThat(response.path("levels[1].parameter-id"),equalTo("Stor")); + assertThat(response.path("levels[1].level-units-id"),equalTo("ac-ft")); + assertThat(response.path("levels[1].level-date"),equalTo("2023-06-01T07:00:00Z")); + assertThat(response.path("levels[1].duration-id"),equalTo("1Day")); + actual1 = Float.valueOf((float) response.path("levels[1].constant-value")).doubleValue(); + assertThat(actual1, closeTo(2.0, 0.01)); + } + }