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 34cf9943c..f48b50e19 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 @@ -25,34 +25,7 @@ package cwms.cda.api; import static com.codahale.metrics.MetricRegistry.name; -import static cwms.cda.api.Controllers.BEGIN; -import static cwms.cda.api.Controllers.CASCADE_DELETE; -import static cwms.cda.api.Controllers.CREATE; -import static cwms.cda.api.Controllers.DATE; -import static cwms.cda.api.Controllers.DATUM; -import static cwms.cda.api.Controllers.DELETE; -import static cwms.cda.api.Controllers.EFFECTIVE_DATE; -import static cwms.cda.api.Controllers.END; -import static cwms.cda.api.Controllers.FORMAT; -import static cwms.cda.api.Controllers.GET_ALL; -import static cwms.cda.api.Controllers.GET_ONE; -import static cwms.cda.api.Controllers.LEVEL_ID; -import static cwms.cda.api.Controllers.LEVEL_ID_MASK; -import static cwms.cda.api.Controllers.NAME; -import static cwms.cda.api.Controllers.OFFICE; -import static cwms.cda.api.Controllers.PAGE; -import static cwms.cda.api.Controllers.PAGE_SIZE; -import static cwms.cda.api.Controllers.RESULTS; -import static cwms.cda.api.Controllers.SIZE; -import static cwms.cda.api.Controllers.STATUS_200; -import static cwms.cda.api.Controllers.TIMEZONE; -import static cwms.cda.api.Controllers.UNIT; -import static cwms.cda.api.Controllers.UPDATE; -import static cwms.cda.api.Controllers.VERSION; -import static cwms.cda.api.Controllers.addDeprecatedContentTypeWarning; -import static cwms.cda.api.Controllers.queryParamAsClass; -import static cwms.cda.api.Controllers.queryParamAsZdt; -import static cwms.cda.api.Controllers.requiredParam; +import static cwms.cda.api.Controllers.*; import static cwms.cda.data.dao.JooqDao.getDslContext; import com.codahale.metrics.Histogram; @@ -70,9 +43,12 @@ import cwms.cda.api.enums.UnitSystem; import cwms.cda.data.dao.LocationLevelsDao; import cwms.cda.data.dao.LocationLevelsDaoImpl; -import cwms.cda.data.dto.LocationLevel; -import cwms.cda.data.dto.LocationLevels; -import cwms.cda.data.dto.SeasonalValueBean; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevels; +import cwms.cda.data.dto.locationlevel.SeasonalLocationLevel; +import cwms.cda.data.dto.locationlevel.TimeSeriesLocationLevel; +import cwms.cda.data.dto.locationlevel.VirtualLocationLevel; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; import cwms.cda.formatters.FormattingException; @@ -89,11 +65,16 @@ import io.javalin.plugin.openapi.annotations.OpenApiParam; import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; import io.javalin.plugin.openapi.annotations.OpenApiResponse; -import java.math.BigDecimal; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.NotNull; import org.jooq.DSLContext; @@ -122,7 +103,6 @@ private Timer.Context markAndTime(String subject) { requestBody = @OpenApiRequestBody( content = { @OpenApiContent(from = LocationLevel.class, type = Formats.JSON), - @OpenApiContent(from = LocationLevel.class, type = Formats.XML) }, required = true), method = HttpMethod.POST, @@ -133,15 +113,34 @@ private Timer.Context markAndTime(String subject) { public void create(@NotNull Context ctx) { try (final Timer.Context ignored = markAndTime(CREATE)) { - String formatHeader = ctx.req.getContentType(); - ContentType contentType = Formats.parseHeader(formatHeader, LocationLevel.class); - LocationLevel level = Formats.parseContent(contentType, ctx.body(), LocationLevel.class); + LocationLevel level = deserializeLocationLevel(ctx); level.validate(); DSLContext dsl = getDslContext(ctx); LocationLevelsDao levelsDao = getLevelsDao(dsl); levelsDao.storeLocationLevel(level); ctx.status(HttpServletResponse.SC_OK).json("Created Location Level"); + } catch(IOException e) { + throw new IllegalArgumentException("Unable to parse the request body", e); + } + } + + private LocationLevel deserializeLocationLevel(Context ctx) throws IOException { + String formatHeader = ctx.req.getContentType(); + ContentType contentType = Formats.parseHeader(formatHeader, LocationLevel.class); + StringWriter writer = new StringWriter(); + IOUtils.copy(ctx.bodyAsInputStream(), writer, StandardCharsets.UTF_8); + String body = writer.toString(); + if (body.contains("constituent")) { + return Formats.parseContent(contentType, body, VirtualLocationLevel.class); + } else if (body.contains("seasonal-timeseries-id")) { + return Formats.parseContent(contentType, body, TimeSeriesLocationLevel.class); + } else if (body.contains("seasonal-values")) { + return Formats.parseContent(contentType, body, SeasonalLocationLevel.class); + } else if (body.contains("constant-value")) { + return Formats.parseContent(contentType, body, ConstantLocationLevel.class); + } else { + throw new UnsupportedFormatException("Unsupported format for Location Level data"); } } @@ -240,12 +239,13 @@ public void delete(@NotNull Context ctx, @NotNull String levelId) { + "request you are. This is an opaque value, and can be obtained from " + "the 'next-page' value in the response."), @OpenApiParam(name = PAGE_SIZE, type = Integer.class, description = "How " - + "many entries per page returned. Default " + DEFAULT_PAGE_SIZE + ".")}, + + "many entries per page returned. Default " + DEFAULT_PAGE_SIZE + "."), + }, responses = { @OpenApiResponse(status = STATUS_200, content = { @OpenApiContent(type = Formats.JSON), @OpenApiContent(type = ""), - @OpenApiContent(from = LocationLevels.class, type = Formats.JSONV2) + @OpenApiContent(from = LocationLevels.class, type = Formats.JSONV2), }) }, tags = TAG) @@ -286,7 +286,8 @@ public void getAll(@NotNull Context ctx) { ZonedDateTime endZdt = queryParamAsZdt(ctx, END); ZonedDateTime beginZdt = queryParamAsZdt(ctx, BEGIN); - LocationLevels levels = levelsDao.getLocationLevels(cursor, pageSize, levelIdMask, + LocationLevels levels; + levels = levelsDao.getLocationLevels(cursor, pageSize, levelIdMask, office, unit, datum, beginZdt, endZdt); String result = Formats.format(contentType, levels); @@ -365,6 +366,7 @@ String.class, null, metrics, name(LevelsController.class.getName(), ZonedDateTime unmarshalledDateTime = DateUtils.parseUserDate(dateString, timezone); LocationLevelsDao levelsDao = getLevelsDao(dsl); + //retrieveLocationLevel will throw an error if level does not exist LocationLevel locationLevel = levelsDao.retrieveLocationLevel(levelId, units, unmarshalledDateTime, office); ctx.json(locationLevel); @@ -384,7 +386,6 @@ String.class, null, metrics, name(LevelsController.class.getName(), requestBody = @OpenApiRequestBody( content = { @OpenApiContent(from = LocationLevel.class, type = Formats.JSON), - @OpenApiContent(from = LocationLevel.class, type = Formats.XML) }, required = true), description = "Update CWMS Location Level", @@ -399,8 +400,7 @@ public void update(@NotNull Context ctx, @NotNull String oldLevelId) { String formatHeader = ctx.req.getContentType(); ContentType contentType = Formats.parseHeader(formatHeader, LocationLevel.class); - LocationLevel levelFromBody = Formats.parseContent(contentType, ctx.body(), - LocationLevel.class); + LocationLevel levelFromBody = deserializeLocationLevel(ctx); String officeId = levelFromBody.getOfficeId(); if (officeId == null) { throw new HttpResponseException(HttpCode.BAD_REQUEST.getStatus(), @@ -428,105 +428,19 @@ public void update(@NotNull Context ctx, @NotNull String oldLevelId) { existingLevelLevel = updatedClearedFields(ctx.body(), contentType.getType(), existingLevelLevel); //only store (update) if level does exist - LocationLevel updatedLocationLevel = getUpdatedLocationLevel(existingLevelLevel, - levelFromBody); - updatedLocationLevel = new LocationLevel.Builder(updatedLocationLevel) - .withLevelDate(unmarshalledDateTime).build(); + LocationLevel updatedLocationLevel = LocationLevel.getUpdatedLocationLevel(existingLevelLevel, + levelFromBody, unmarshalledDateTime); + levelsDao.storeLocationLevel(updatedLocationLevel); ctx.status(HttpServletResponse.SC_OK).json("Updated Location Level"); } } catch (JsonProcessingException ex) { throw new FormattingException("Failed to format location level update request", ex); + } catch (IOException ex) { + throw new IllegalArgumentException("Unable to parse the request body", ex); } } - private LocationLevel getUpdatedLocationLevel(LocationLevel existingLevel, - LocationLevel updatedLevel) { - String seasonalTimeSeriesId = (updatedLevel.getSeasonalTimeSeriesId() == null - ? existingLevel.getSeasonalTimeSeriesId() : updatedLevel.getSeasonalTimeSeriesId()); - List seasonalValues = (updatedLevel.getSeasonalValues() == null - ? existingLevel.getSeasonalValues() : updatedLevel.getSeasonalValues()); - String specifiedLevelId = (updatedLevel.getSpecifiedLevelId() == null - ? existingLevel.getSpecifiedLevelId() : updatedLevel.getSpecifiedLevelId()); - String parameterTypeId = (updatedLevel.getParameterTypeId() == null - ? existingLevel.getParameterTypeId() : updatedLevel.getParameterTypeId()); - String parameterId = (updatedLevel.getParameterId() == null - ? existingLevel.getParameterId() : updatedLevel.getParameterId()); - Double siParameterUnitsConstantValue = (updatedLevel.getConstantValue() == null - ? existingLevel.getConstantValue() : updatedLevel.getConstantValue()); - String levelUnitsId = (updatedLevel.getLevelUnitsId() == null - ? existingLevel.getLevelUnitsId() : updatedLevel.getLevelUnitsId()); - ZonedDateTime levelDate = (updatedLevel.getLevelDate() == null - ? existingLevel.getLevelDate() : updatedLevel.getLevelDate()); - String levelComment = (updatedLevel.getLevelComment() == null - ? existingLevel.getLevelComment() : updatedLevel.getLevelComment()); - ZonedDateTime intervalOrigin = (updatedLevel.getIntervalOrigin() == null - ? existingLevel.getIntervalOrigin() : updatedLevel.getIntervalOrigin()); - Integer intervalMinutes = (updatedLevel.getIntervalMinutes() == null - ? existingLevel.getIntervalMinutes() : updatedLevel.getIntervalMinutes()); - Integer intervalMonths = (updatedLevel.getIntervalMonths() == null - ? existingLevel.getIntervalMonths() : updatedLevel.getIntervalMonths()); - String interpolateString = (updatedLevel.getInterpolateString() == null - ? existingLevel.getInterpolateString() : updatedLevel.getInterpolateString()); - String durationId = (updatedLevel.getDurationId() == null - ? existingLevel.getDurationId() : updatedLevel.getDurationId()); - BigDecimal attributeValue = (updatedLevel.getAttributeValue() == null - ? existingLevel.getAttributeValue() : updatedLevel.getAttributeValue()); - String attributeUnitsId = (updatedLevel.getAttributeUnitsId() == null - ? existingLevel.getAttributeUnitsId() : updatedLevel.getAttributeUnitsId()); - String attributeParameterTypeId = (updatedLevel.getAttributeParameterTypeId() == null - ? existingLevel.getAttributeParameterTypeId() : - updatedLevel.getAttributeParameterTypeId()); - String attributeParameterId = (updatedLevel.getAttributeParameterId() == null - ? existingLevel.getAttributeParameterId() : updatedLevel.getAttributeParameterId()); - String attributeDurationId = (updatedLevel.getAttributeDurationId() == null - ? existingLevel.getAttributeDurationId() : updatedLevel.getAttributeDurationId()); - String attributeComment = (updatedLevel.getAttributeComment() == null - ? existingLevel.getAttributeComment() : updatedLevel.getAttributeComment()); - String locationId = (updatedLevel.getLocationLevelId() == null - ? existingLevel.getLocationLevelId() : updatedLevel.getLocationLevelId()); - String officeId = (updatedLevel.getOfficeId() == null - ? existingLevel.getOfficeId() : updatedLevel.getOfficeId()); - if (existingLevel.getIntervalMonths() != null && existingLevel.getIntervalMonths() > 0) { - intervalMinutes = null; - } else if (existingLevel.getIntervalMinutes() != null - && existingLevel.getIntervalMinutes() > 0) { - intervalMonths = null; - } - if (existingLevel.getAttributeValue() == null) { - attributeUnitsId = null; - } - if (!existingLevel.getSeasonalValues().isEmpty()) { - siParameterUnitsConstantValue = null; - seasonalTimeSeriesId = null; - } else if (existingLevel.getSeasonalTimeSeriesId() != null - && !existingLevel.getSeasonalTimeSeriesId().isEmpty()) { - siParameterUnitsConstantValue = null; - seasonalValues = null; - } - return new LocationLevel.Builder(locationId, levelDate) - .withSeasonalValues(seasonalValues) - .withSeasonalTimeSeriesId(seasonalTimeSeriesId) - .withSpecifiedLevelId(specifiedLevelId) - .withParameterTypeId(parameterTypeId) - .withParameterId(parameterId) - .withConstantValue(siParameterUnitsConstantValue) - .withLevelUnitsId(levelUnitsId) - .withLevelComment(levelComment) - .withIntervalOrigin(intervalOrigin) - .withIntervalMinutes(intervalMinutes) - .withIntervalMonths(intervalMonths) - .withInterpolateString(interpolateString) - .withDurationId(durationId) - .withAttributeValue(attributeValue) - .withAttributeUnitsId(attributeUnitsId) - .withAttributeParameterTypeId(attributeParameterTypeId) - .withAttributeParameterId(attributeParameterId) - .withAttributeDurationId(attributeDurationId) - .withAttributeComment(attributeComment) - .withOfficeId(officeId).build(); - } - public static LocationLevelsDao getLevelsDao(DSLContext dsl) { return new LocationLevelsDaoImpl(dsl); } @@ -553,20 +467,76 @@ private LocationLevel updatedClearedFields(String body, String format, JavaType javaType = om.getTypeFactory().constructType(LocationLevel.class); BeanDescription beanDescription = om.getSerializationConfig().introspect(javaType); List properties = beanDescription.findProperties(); - LocationLevel retVal = new LocationLevel.Builder(existingLevel).build(); - try { - for (BeanPropertyDefinition propertyDefinition : properties) { - String propertyName = propertyDefinition.getName(); - JsonNode propertyValue = root.findValue(propertyName); - if (propertyValue != null && "".equals(propertyValue.textValue())) { - retVal = new LocationLevel.Builder(retVal) - .withProperty(propertyName, null).build(); + if (existingLevel instanceof ConstantLocationLevel) { + ConstantLocationLevel constantLevel = (ConstantLocationLevel) existingLevel; + ConstantLocationLevel retVal = new ConstantLocationLevel.Builder(constantLevel).build(); + try { + for (BeanPropertyDefinition propertyDefinition : properties) { + String propertyName = propertyDefinition.getName(); + JsonNode propertyValue = root.findValue(propertyName); + if (propertyValue != null && "".equals(propertyValue.textValue())) { + retVal = new ConstantLocationLevel.Builder(retVal) + .withProperty(propertyName, null).build(); + } + } + } catch (NullPointerException e) { + //gets thrown if required field is null + throw new IllegalArgumentException(e); + } + return retVal; + } else if (existingLevel instanceof VirtualLocationLevel) { + VirtualLocationLevel virtualLevel = (VirtualLocationLevel) existingLevel; + VirtualLocationLevel retVal = new VirtualLocationLevel.Builder(virtualLevel).build(); + try { + for (BeanPropertyDefinition propertyDefinition : properties) { + String propertyName = propertyDefinition.getName(); + JsonNode propertyValue = root.findValue(propertyName); + if (propertyValue != null && "".equals(propertyValue.textValue())) { + retVal = new VirtualLocationLevel.Builder(retVal) + .withProperty(propertyName, null).build(); + } } + } catch (NullPointerException e) { + //gets thrown if required field is null + throw new IllegalArgumentException(e); } - } catch (NullPointerException e) { - //gets thrown if required field is null - throw new IllegalArgumentException(e.getMessage()); + return retVal; + } else if (existingLevel instanceof TimeSeriesLocationLevel) { + TimeSeriesLocationLevel timeSeriesLevel = (TimeSeriesLocationLevel) existingLevel; + TimeSeriesLocationLevel retVal = new TimeSeriesLocationLevel.Builder(timeSeriesLevel).build(); + try { + for (BeanPropertyDefinition propertyDefinition : properties) { + String propertyName = propertyDefinition.getName(); + JsonNode propertyValue = root.findValue(propertyName); + if (propertyValue != null && "".equals(propertyValue.textValue())) { + retVal = new TimeSeriesLocationLevel.Builder(retVal) + .withProperty(propertyName, null).build(); + } + } + } catch (NullPointerException e) { + //gets thrown if required field is null + throw new IllegalArgumentException(e); + } + return retVal; + } else if (existingLevel instanceof SeasonalLocationLevel) { + SeasonalLocationLevel seasonalLevel = (SeasonalLocationLevel) existingLevel; + SeasonalLocationLevel retVal = new SeasonalLocationLevel.Builder(seasonalLevel).build(); + try { + for (BeanPropertyDefinition propertyDefinition : properties) { + String propertyName = propertyDefinition.getName(); + JsonNode propertyValue = root.findValue(propertyName); + if (propertyValue != null && propertyValue.textValue().isEmpty()) { + retVal = new SeasonalLocationLevel.Builder(retVal) + .withProperty(propertyName, null).build(); + } + } + } catch (NullPointerException e) { + //gets thrown if required field is null + throw new IllegalArgumentException(e); + } + return retVal; + } else { + throw new UnsupportedFormatException("Unsupported location level type"); } - return retVal; } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDao.java index 58ff3a8e4..822e8ff8e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/LocationLevelsDao.java @@ -24,14 +24,14 @@ package cwms.cda.data.dao; -import cwms.cda.data.dto.LocationLevel; -import cwms.cda.data.dto.LocationLevels; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevels; import cwms.cda.data.dto.TimeSeries; import hec.data.level.ILocationLevelRef; + import mil.army.usace.hec.metadata.Interval; import java.time.Instant; -import java.time.ZoneId; import java.time.ZonedDateTime; public interface LocationLevelsDao { 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 90fbcd55d..ae0874e23 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 @@ -28,14 +28,17 @@ import static mil.army.usace.hec.metadata.IntervalFactory.equalsName; import static mil.army.usace.hec.metadata.IntervalFactory.isRegular; import static usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL.AV_LOCATION_LEVEL; +import static usace.cwms.db.jooq.codegen.tables.AV_VIRTUAL_LOCATION_LEVEL.AV_VIRTUAL_LOCATION_LEVEL; import cwms.cda.api.enums.UnitSystem; import cwms.cda.api.enums.VersionType; import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dto.CwmsDTOPaginated; -import cwms.cda.data.dto.LocationLevel; -import cwms.cda.data.dto.LocationLevels; -import cwms.cda.data.dto.SeasonalValueBean; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevels; +import cwms.cda.data.dto.locationlevel.SeasonalLocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalValueBean; import cwms.cda.data.dto.TimeSeries; import hec.data.Duration; import hec.data.Parameter; @@ -52,6 +55,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -61,10 +65,13 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; + +import cwms.cda.data.dto.locationlevel.TimeSeriesLocationLevel; +import cwms.cda.data.dto.locationlevel.VirtualLocationLevel; +import cwms.cda.formatters.UnsupportedFormatException; import mil.army.usace.hec.metadata.Interval; import mil.army.usace.hec.metadata.IntervalFactory; import mil.army.usace.hec.metadata.constants.NumericalConstants; @@ -73,19 +80,22 @@ import org.jooq.Configuration; import org.jooq.DSLContext; import org.jooq.Record; -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.jooq.codegen.packages.CWMS_ENV_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_LEVEL_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE; import usace.cwms.db.jooq.codegen.packages.CWMS_UTIL_PACKAGE; -import usace.cwms.db.jooq.codegen.packages.cwms_level.RETRIEVE_LOCATION_LEVEL3; +import usace.cwms.db.jooq.codegen.udt.records.LOCATION_LEVEL_T; import usace.cwms.db.jooq.codegen.udt.records.SEASONAL_VALUE_T; import usace.cwms.db.jooq.codegen.udt.records.SEASONAL_VALUE_TAB_T; +import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_T; +import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_TAB_T; import usace.cwms.db.jooq.codegen.udt.records.ZTSV_ARRAY; import usace.cwms.db.jooq.codegen.udt.records.ZTSV_TYPE; @@ -99,6 +109,43 @@ public class LocationLevelsDaoImpl extends JooqDao implements Loc public static final Pattern locationLevelIdParsingPattern = Pattern.compile(LOCATION_LEVEL_ID_PARSING_REGEXP); + private static final Collection> LOCATION_LEVEL_FIELDS = new LinkedHashSet<>(); + + static { + usace.cwms.db.jooq.codegen.tables.AV_VIRTUAL_LOCATION_LEVEL virtView = AV_VIRTUAL_LOCATION_LEVEL; + usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; + LOCATION_LEVEL_FIELDS.add(virtView.OFFICE_ID); + LOCATION_LEVEL_FIELDS.add(virtView.LOCATION_LEVEL_ID); + LOCATION_LEVEL_FIELDS.add(virtView.ATTRIBUTE_ID); + LOCATION_LEVEL_FIELDS.add(virtView.DURATION_CODE); + LOCATION_LEVEL_FIELDS.add(virtView.EFFECTIVE_DATE_UTC); + LOCATION_LEVEL_FIELDS.add(virtView.CONNECTIONS); + LOCATION_LEVEL_FIELDS.add(virtView.DURATION_ID); + LOCATION_LEVEL_FIELDS.add(virtView.EXPIRATION_DATE_UTC); + LOCATION_LEVEL_FIELDS.add(virtView.ATTR_UNIT_EN); + LOCATION_LEVEL_FIELDS.add(virtView.ATTR_VALUE_EN); + LOCATION_LEVEL_FIELDS.add(virtView.ATTR_UNIT_SI); + LOCATION_LEVEL_FIELDS.add(virtView.ATTR_VALUE_SI); + LOCATION_LEVEL_FIELDS.add(view.OFFICE_ID); + LOCATION_LEVEL_FIELDS.add(view.LOCATION_LEVEL_ID); + LOCATION_LEVEL_FIELDS.add(view.LEVEL_DATE); + LOCATION_LEVEL_FIELDS.add(view.TSID); + LOCATION_LEVEL_FIELDS.add(view.CONSTANT_LEVEL); + LOCATION_LEVEL_FIELDS.add(view.INTERVAL_ORIGIN); + LOCATION_LEVEL_FIELDS.add(view.INTERPOLATE); + LOCATION_LEVEL_FIELDS.add(view.ATTRIBUTE_ID); + LOCATION_LEVEL_FIELDS.add(view.ATTRIBUTE_VALUE); + LOCATION_LEVEL_FIELDS.add(view.ATTRIBUTE_UNIT); + LOCATION_LEVEL_FIELDS.add(view.ATTRIBUTE_COMMENT); + LOCATION_LEVEL_FIELDS.add(view.LEVEL_UNIT); + LOCATION_LEVEL_FIELDS.add(view.LEVEL_COMMENT); + LOCATION_LEVEL_FIELDS.add(view.SEASONAL_LEVEL); + LOCATION_LEVEL_FIELDS.add(view.CALENDAR_INTERVAL); + LOCATION_LEVEL_FIELDS.add(view.TIME_INTERVAL); + LOCATION_LEVEL_FIELDS.add(view.CALENDAR_OFFSET); + LOCATION_LEVEL_FIELDS.add(view.TIME_OFFSET); + } + public LocationLevelsDaoImpl(DSLContext dsl) { super(dsl); } @@ -136,45 +183,64 @@ public LocationLevels getLocationLevels(String cursor, int pageSize, } usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; + usace.cwms.db.jooq.codegen.tables.AV_VIRTUAL_LOCATION_LEVEL virtView = AV_VIRTUAL_LOCATION_LEVEL; - Condition whereCondition = DSL.upper(view.UNIT_SYSTEM).eq(unit.toUpperCase()); + Condition whereCondition = (DSL.upper(view.UNIT_SYSTEM).eq(unit.toUpperCase())) + .or(virtView.LOCATION_LEVEL_ID.isNotNull()); if (office != null && !office.isEmpty()) { - whereCondition = whereCondition.and(DSL.upper(view.OFFICE_ID).eq(office.toUpperCase())); + whereCondition = whereCondition.and((DSL.upper(view.OFFICE_ID).eq(office.toUpperCase())) + .or(DSL.upper(virtView.OFFICE_ID).eq(office.toUpperCase()))); } if (levelIdMask != null && !levelIdMask.isEmpty()) { - whereCondition = whereCondition.and(JooqDao.caseInsensitiveLikeRegex( - view.LOCATION_LEVEL_ID, levelIdMask)); + whereCondition = whereCondition.and((JooqDao.caseInsensitiveLikeRegex(view.LOCATION_LEVEL_ID, levelIdMask)) + .or(JooqDao.caseInsensitiveLikeRegex(virtView.LOCATION_LEVEL_ID, levelIdMask))); } if (beginZdt != null) { - whereCondition = whereCondition.and(view.LEVEL_DATE.greaterOrEqual( - Timestamp.from(beginZdt.toInstant()))); + whereCondition = whereCondition.and((view.LEVEL_DATE.greaterOrEqual(Timestamp.from(beginZdt.toInstant()))) + .or(virtView.EFFECTIVE_DATE_UTC.greaterOrEqual(Timestamp.from(beginZdt.toInstant())))); } if (endZdt != null) { - whereCondition = whereCondition.and(view.LEVEL_DATE.lessThan( - Timestamp.from(endZdt.toInstant()))); + whereCondition = whereCondition.and((view.LEVEL_DATE.lessThan(Timestamp.from(endZdt.toInstant()))) + .or(virtView.EFFECTIVE_DATE_UTC.lessThan(Timestamp.from(endZdt.toInstant())))); } Map builderMap = new LinkedHashMap<>(); - SelectLimitPercentAfterOffsetStep query = dsl.selectDistinct(getAddSeasonalValueFields()) + SelectLimitPercentAfterOffsetStep query = dsl.selectDistinct(LOCATION_LEVEL_FIELDS) .from(view) + .fullOuterJoin(virtView) + .on(view.LOCATION_LEVEL_CODE.eq(virtView.LOCATION_LEVEL_CODE)) .where(whereCondition) .orderBy(DSL.upper(view.OFFICE_ID), DSL.upper(view.LOCATION_LEVEL_ID), - view.LEVEL_DATE, view.CALENDAR_OFFSET + view.LEVEL_DATE, view.CALENDAR_OFFSET, DSL.upper(virtView.OFFICE_ID), + DSL.upper(virtView.LOCATION_LEVEL_ID), + virtView.EFFECTIVE_DATE_UTC ) .offset(offset) .limit(pageSize); - logger.info(() -> "getLocationLevels query: " + query.getSQL(ParamType.INLINED)); + final SelectLimitPercentAfterOffsetStep queryFinal = query; + + logger.fine(() -> "getLocationLevels query: " + queryFinal.getSQL(ParamType.INLINED)); - query.stream().forEach(r -> addSeasonalValue(r, builderMap)); + query.stream().forEach(r -> parseLevels(r, builderMap, unit)); List levels = new java.util.ArrayList<>(); for (LocationLevel.Builder builder : builderMap.values()) { - levels.add(builder.build()); + if (builder instanceof TimeSeriesLocationLevel.Builder) { + levels.add(((TimeSeriesLocationLevel.Builder) builder).build()); + } else if (builder instanceof SeasonalLocationLevel.Builder) { + levels.add(((SeasonalLocationLevel.Builder) builder).build()); + } else if (builder instanceof ConstantLocationLevel.Builder) { + levels.add(((ConstantLocationLevel.Builder) builder).build()); + } else if (builder instanceof VirtualLocationLevel.Builder) { + levels.add(((VirtualLocationLevel.Builder) builder).build()); + } else { + throw new IllegalArgumentException("Unknown builder type: " + builder.getClass().getName()); + } } LocationLevels.Builder builder = new LocationLevels.Builder(offset, pageSize, total); @@ -221,35 +287,91 @@ public int hashCode() { @Override public void storeLocationLevel(LocationLevel locationLevel) { - BigInteger months = locationLevel.getIntervalMonths() == null ? null : - BigInteger.valueOf(locationLevel.getIntervalMonths()); - BigInteger minutes = locationLevel.getIntervalMinutes() == null ? null : - BigInteger.valueOf(locationLevel.getIntervalMinutes()); + if (locationLevel instanceof VirtualLocationLevel) { + storeVirtualLocationLevel((VirtualLocationLevel) locationLevel); + } else { + storeNormalLocationLevel(locationLevel); + } + } + + private void storeNormalLocationLevel(LocationLevel locationLevel) { + BigInteger months = null; + BigInteger minutes = null; + Timestamp intervalOrigin = null; Timestamp date = Timestamp.from(locationLevel.getLevelDate().toInstant()); - Timestamp intervalOrigin = locationLevel.getIntervalOrigin() == null ? null : - Timestamp.from(locationLevel.getIntervalOrigin().toInstant()); - SEASONAL_VALUE_TAB_T seasonalValues = getSeasonalValues(locationLevel); + SEASONAL_VALUE_TAB_T seasonalValues = null; + Number constantValue = null; + String seasonalTimeSeriesId = null; + String interpolateString = null; + + if (locationLevel instanceof SeasonalLocationLevel) { + SeasonalLocationLevel seasonalLocationLevel = (SeasonalLocationLevel) locationLevel; + months = seasonalLocationLevel.getIntervalMonths() == null ? null : + BigInteger.valueOf(seasonalLocationLevel.getIntervalMonths()); + minutes = seasonalLocationLevel.getIntervalMinutes() == null ? null : + BigInteger.valueOf(seasonalLocationLevel.getIntervalMinutes()); + intervalOrigin = seasonalLocationLevel.getIntervalOrigin() == null ? null : + Timestamp.from(seasonalLocationLevel.getIntervalOrigin().toInstant()); + interpolateString = seasonalLocationLevel.getInterpolateString(); + + seasonalValues = getSeasonalValues(seasonalLocationLevel); + } else if (locationLevel instanceof ConstantLocationLevel) { + constantValue = ((ConstantLocationLevel) locationLevel).getConstantValue(); + } else if (locationLevel instanceof TimeSeriesLocationLevel) { + seasonalTimeSeriesId = ((TimeSeriesLocationLevel) locationLevel).getSeasonalTimeSeriesId(); + } + + final Number constantValueFinal = constantValue; + final Timestamp dateFinal = date; + final Timestamp intervalOriginFinal = intervalOrigin; + final BigInteger monthsFinal = months; + final BigInteger minutesFinal = minutes; + final SEASONAL_VALUE_TAB_T seasonalValuesFinal = seasonalValues; + final String seasonalTimeSeriesIdFinal = seasonalTimeSeriesId; + final String interpolateStringFinal = interpolateString; + connection(dsl, c -> { String officeId = locationLevel.getOfficeId(); setOffice(c, officeId); CWMS_LEVEL_PACKAGE.call_STORE_LOCATION_LEVEL3(DSL.using(c).configuration(), - locationLevel.getLocationLevelId(), locationLevel.getConstantValue(), locationLevel.getLevelUnitsId(), - locationLevel.getLevelComment(), - date, "UTC", locationLevel.getAttributeValue(), locationLevel.getAttributeUnitsId(), - locationLevel.getAttributeDurationId(), locationLevel.getAttributeComment(), intervalOrigin, months, - minutes, locationLevel.getInterpolateString(), locationLevel.getSeasonalTimeSeriesId(), seasonalValues, - "F", - officeId); + locationLevel.getLocationLevelId(), constantValueFinal, locationLevel.getLevelUnitsId(), + locationLevel.getLevelComment(), + dateFinal, "UTC", locationLevel.getAttributeValue(), locationLevel.getAttributeUnitsId(), + locationLevel.getAttributeDurationId(), locationLevel.getAttributeComment(), intervalOriginFinal, monthsFinal, + minutesFinal, interpolateStringFinal, seasonalTimeSeriesIdFinal, seasonalValuesFinal, + "F", + officeId); + }); + } + + private void storeVirtualLocationLevel(VirtualLocationLevel locationLevel) { + VirtualLocationLevel virtualLocationLevel = locationLevel; + Timestamp date = Timestamp.from(locationLevel.getLevelDate().toInstant()); + Timestamp expirationDate = Timestamp.from(virtualLocationLevel.getExpirationDate().toInstant()); + STR_TAB_TAB_T constituentTab = new STR_TAB_TAB_T(); + for (VirtualLocationLevel.RatingConstituent constituent : virtualLocationLevel.getConstituents()) { + constituentTab.add(new STR_TAB_T(constituent.getConstituentList())); + } + connection(dsl, c -> { + String officeId = locationLevel.getOfficeId(); + setOffice(c, officeId); + CWMS_LEVEL_PACKAGE.call_STORE_VIRTUAL_LOCATION_LEVEL(DSL.using(c).configuration(), + locationLevel.getLocationLevelId(), constituentTab, virtualLocationLevel.getConstituentConnections(), + locationLevel.getLevelComment(), locationLevel.getAttributeDurationId(), + locationLevel.getAttributeValue(), locationLevel.getAttributeUnitsId(), + locationLevel.getAttributeComment(), date, expirationDate, + "UTC", "F", "F", + officeId); }); } - private static SEASONAL_VALUE_TAB_T getSeasonalValues(LocationLevel locationLevel) { + private static SEASONAL_VALUE_TAB_T getSeasonalValues(SeasonalLocationLevel locationLevel) { List seasonalValues = locationLevel.getSeasonalValues(); SEASONAL_VALUE_TAB_T pSeasonalValues = null; if (seasonalValues != null && !seasonalValues.isEmpty()) { pSeasonalValues = new SEASONAL_VALUE_TAB_T(); - for(SeasonalValueBean seasonalValue : seasonalValues) { + for (SeasonalValueBean seasonalValue : seasonalValues) { SEASONAL_VALUE_T seasonalValueT = new SEASONAL_VALUE_T(); seasonalValueT.setOFFSET_MINUTES(toBigDecimal(seasonalValue.getOffsetMinutes())); if (seasonalValue.getOffsetMonths() != null) { @@ -262,10 +384,18 @@ private static SEASONAL_VALUE_TAB_T getSeasonalValues(LocationLevel locationLeve return pSeasonalValues; } + public static SeasonalValueBean buildSeasonalValue(SEASONAL_VALUE_T fromBean) { + return new SeasonalValueBean.Builder(fromBean.getVALUE().doubleValue()) + .withOffsetMonths(fromBean.getOFFSET_MONTHS()) + .withOffsetMinutes(Optional.ofNullable(fromBean.getOFFSET_MINUTES()) + .map(BigDecimal::toBigInteger).orElse(null)) + .build(); + } + @NotNull - private List buildSeasonalValues(RETRIEVE_LOCATION_LEVEL3 level) { + private List buildSeasonalValues(LOCATION_LEVEL_T level) { List seasonalValues = Collections.emptyList(); - SEASONAL_VALUE_TAB_T values = level.getP_SEASONAL_VALUES(); + SEASONAL_VALUE_TAB_T values = level.getSEASONAL_VALUES(); if (values != null) { seasonalValues = values.stream() .filter(Objects::nonNull) @@ -275,14 +405,6 @@ private List buildSeasonalValues(RETRIEVE_LOCATION_LEVEL3 lev return seasonalValues; } - public static SeasonalValueBean buildSeasonalValue(SEASONAL_VALUE_T fromBean) { - return new SeasonalValueBean.Builder(fromBean.getVALUE().doubleValue()) - .withOffsetMonths(fromBean.getOFFSET_MONTHS()) - .withOffsetMinutes(Optional.ofNullable(fromBean.getOFFSET_MINUTES()) - .map(BigDecimal::toBigInteger).orElse(null)) - .build(); - } - @Override public void deleteLocationLevel(String locationLevelName, ZonedDateTime zonedDateTime, String officeId, Boolean cascadeDelete) { @@ -304,15 +426,12 @@ public void deleteLocationLevel(String locationLevelName, ZonedDateTime zonedDat null, null, cascade, officeId, "VN"); }); } else { - Record1 levelCode = dsl.selectDistinct(AV_LOCATION_LEVEL.LOCATION_LEVEL_CODE) - - .from(AV_LOCATION_LEVEL) - .where(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID.eq(locationLevelName)) - .and(AV_LOCATION_LEVEL.OFFICE_ID.eq(officeId) - ) - .fetchOne(); - CWMS_LEVEL_PACKAGE.call_DELETE_LOCATION_LEVEL__2(dsl.configuration(), - BigInteger.valueOf(levelCode.value1()), cascadeDelete ? "T" : "F"); + connection(dsl, c -> + CWMS_LEVEL_PACKAGE.call_DELETE_LOCATION_LEVEL3(getDslContext(c, officeId).configuration(), locationLevelName, + date, "UTC", null, null, + null, cascadeDelete ? "T" : "F", "F", "F", + officeId, "VN", "F", "T", "T") + ); } } catch (DataAccessException ex) { @@ -337,75 +456,135 @@ public LocationLevel retrieveLocationLevel(String locationLevelName, String pUni } String parameter = levelIdParts[1]; return connectionResult(dsl, c -> { + String units = pUnits; Configuration configuration = getDslContext(c, officeId).configuration(); if (units != null && (units.equalsIgnoreCase("SI") || units.equalsIgnoreCase("EN"))) { units = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS(configuration, parameter, units); - } - RETRIEVE_LOCATION_LEVEL3 level = CWMS_LEVEL_PACKAGE.call_RETRIEVE_LOCATION_LEVEL3( - configuration, locationLevelName, units, date, - "UTC", null, null, units, - "F", officeId); - List seasonalValues = buildSeasonalValues(level); - if (units == null) { + } else if (units == null) { logger.info("Getting default units for " + parameter); String defaultUnits = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS( - configuration, parameter, UnitSystem.SI.getValue()); + configuration, parameter, UnitSystem.SI.getValue()); logger.info("Default units are " + defaultUnits); units = defaultUnits; } - Timestamp pEffectiveDate = level.getP_EFFECTIVE_DATE(); + LOCATION_LEVEL_T level = CWMS_LEVEL_PACKAGE.call_RETRIEVE_LOCATION_LEVEL__2( + configuration, locationLevelName, units, date, + "UTC", null, null, + null, "T", officeId, "VN"); + if (level == null) { + throw new NotFoundException("Location level not found: " + locationLevelName); + } + Timestamp pEffectiveDate = level.getLEVEL_DATE(); ZonedDateTime realEffectiveDate = ZonedDateTime.ofInstant(pEffectiveDate.toInstant(), effectiveDate.getZone()); - return new LocationLevel.Builder(locationLevelName, realEffectiveDate) + List constituents = new ArrayList<>(); + STR_TAB_TAB_T constituentTab = level.getCONSTITUENTS(); + Double constantValue = Optional.ofNullable(level.getLEVEL_VALUE()) + .map(BigDecimal::doubleValue).orElse(null); + List seasonalValues = buildSeasonalValues(level); + String seasonalTimeSeriesId = level.getTSID(); + if (constituentTab != null) { + return buildVirtualLocationLevel(level, officeId, units, effectiveDate, constituentTab, + constituents, locationLevelName, realEffectiveDate); + } else if (!seasonalValues.isEmpty()) { + return buildSeasonalLocationLevel(level, officeId, units, locationLevelName, + effectiveDate, realEffectiveDate, seasonalValues); + } else if (constantValue != null) { + return buildConstantLocationLevel(level, officeId, units, locationLevelName, + realEffectiveDate, constantValue); + } else if (seasonalTimeSeriesId != null) { + return buildTimeSeriesLocationLevel(level, officeId, units, locationLevelName, realEffectiveDate); + } else { + throw new IllegalArgumentException("Location level does not match expected level type: " + locationLevelName); + } + }); + } + + private VirtualLocationLevel buildVirtualLocationLevel(LOCATION_LEVEL_T level, String officeId, String units, ZonedDateTime effectiveDate, + STR_TAB_TAB_T constituentTab, List constituents, String locationLevelName, ZonedDateTime realEffectiveDate) { + ZonedDateTime expirationDate = ZonedDateTime.ofInstant(level.getEXPIRATION_DATE().toInstant(), effectiveDate.getZone()); + + constituentTab.forEach(constituent -> { + if (constituent.size() > 3) { + VirtualLocationLevel.LocationLevelConstituent.Builder constituentBuilder = new VirtualLocationLevel.LocationLevelConstituent + .Builder(constituent.get(0), constituent.get(1), constituent.get(2), + constituent.get(3), Double.valueOf(constituent.get(4))); + + if (constituent.size() > 5 && constituent.get(5) != null) { + constituentBuilder.withAttributeUnits(constituent.get(5)); + } + constituents.add(constituentBuilder.build()); + } else { + VirtualLocationLevel.RatingConstituent.Builder constituentBuilder = new VirtualLocationLevel.RatingConstituent + .Builder(constituent.get(0), constituent.get(1), constituent.get(2)); + constituents.add(constituentBuilder.build()); + } + }); + + return new VirtualLocationLevel.Builder(locationLevelName, realEffectiveDate) + .withConstituents(constituents) + .withConstituentConnections(level.getCONNECTIONS()) + .withExpirationDate(expirationDate) .withLevelUnitsId(units) .withAttributeUnitsId(units) - .withInterpolateString(level.getP_INTERPOLATE()) - .withIntervalMinutes(Optional.ofNullable(level.getP_INTERVAL_MINUTES()) - .map(BigInteger::intValue).orElse(null)) - .withIntervalMonths(Optional.ofNullable(level.getP_INTERVAL_MONTHS()) - .map(BigInteger::intValue).orElse(null)) - .withIntervalOrigin(level.getP_INTERVAL_ORIGIN(), effectiveDate) - .withLevelComment(level.getP_LEVEL_COMMENT()) + .withLevelComment(level.getLEVEL_COMMENT()) .withOfficeId(officeId) - .withAttributeParameterId(level.get(RETRIEVE_LOCATION_LEVEL3.P_ATTRIBUTE_ID)) - .withSeasonalTimeSeriesId(level.get(RETRIEVE_LOCATION_LEVEL3.P_TSID)) - .withSeasonalValues(seasonalValues) - .withConstantValue(Optional.ofNullable(level.getP_LEVEL_VALUE()) - .map(BigDecimal::doubleValue).orElse(null)) + .withAttributeParameterId(level.getATTRIBUTE_PARAMETER_ID()) + .withInterpolateString(level.getINTERPOLATE()) .build(); - }); } - // These are all the fields that we need to pull out of jOOQ record for addSeasonalValue - private Collection> getAddSeasonalValueFields() { - Set> retval = new LinkedHashSet<>(); - - retval.add(AV_LOCATION_LEVEL.OFFICE_ID); - retval.add(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID); - retval.add(AV_LOCATION_LEVEL.LEVEL_DATE); - retval.add(AV_LOCATION_LEVEL.TSID); - retval.add(AV_LOCATION_LEVEL.CONSTANT_LEVEL); - retval.add(AV_LOCATION_LEVEL.INTERVAL_ORIGIN); - retval.add(AV_LOCATION_LEVEL.INTERPOLATE); - retval.add(AV_LOCATION_LEVEL.ATTRIBUTE_ID); - retval.add(AV_LOCATION_LEVEL.ATTRIBUTE_VALUE); - retval.add(AV_LOCATION_LEVEL.ATTRIBUTE_UNIT); - retval.add(AV_LOCATION_LEVEL.ATTRIBUTE_COMMENT); - retval.add(AV_LOCATION_LEVEL.LEVEL_UNIT); - retval.add(AV_LOCATION_LEVEL.LEVEL_COMMENT); - - retval.addAll(getParseSeasonalValuesFields()); - - return retval; + private ConstantLocationLevel buildConstantLocationLevel(LOCATION_LEVEL_T level, String officeId, String units, + String locationLevelName, ZonedDateTime realEffectiveDate, Double constantValue) { + return new ConstantLocationLevel.Builder(locationLevelName, realEffectiveDate) + .withLevelUnitsId(units) + .withAttributeUnitsId(units) + .withLevelComment(level.getLEVEL_COMMENT()) + .withOfficeId(officeId) + .withAttributeParameterId(level.getATTRIBUTE_PARAMETER_ID()) + .withInterpolateString(level.getINTERPOLATE()) + .withConstantValue(constantValue) + .build(); } + private SeasonalLocationLevel buildSeasonalLocationLevel(LOCATION_LEVEL_T level, String officeId, String units, + String locationLevelName, ZonedDateTime effectiveDate, ZonedDateTime realEffectiveDate, List seasonalValues) { + return new SeasonalLocationLevel.Builder(locationLevelName, realEffectiveDate) + .withLevelUnitsId(units) + .withAttributeUnitsId(units) + .withInterpolateString(level.getINTERPOLATE()) + .withLevelComment(level.getLEVEL_COMMENT()) + .withOfficeId(officeId) + .withAttributeParameterId(level.getATTRIBUTE_PARAMETER_ID()) + .withSeasonalValues(seasonalValues) + .withIntervalMinutes(Optional.ofNullable(level.getINTERVAL_MINUTES()) + .map(BigInteger::intValue).orElse(null)) + .withIntervalMonths(Optional.ofNullable(level.getINTERVAL_MONTHS()) + .map(BigInteger::intValue).orElse(null)) + .withIntervalOrigin(level.getINTERVAL_ORIGIN(), effectiveDate) + .build(); + } - private void addSeasonalValue(Record r, - Map builderMap) { + private TimeSeriesLocationLevel buildTimeSeriesLocationLevel(LOCATION_LEVEL_T level, String officeId, String units, + String locationLevelName, ZonedDateTime realEffectiveDate) { + return new TimeSeriesLocationLevel.Builder(locationLevelName, realEffectiveDate, level.getTSID()) + .withLevelUnitsId(units) + .withAttributeUnitsId(units) + .withLevelComment(level.getLEVEL_COMMENT()) + .withOfficeId(officeId) + .withAttributeParameterId(level.getATTRIBUTE_PARAMETER_ID()) + .withInterpolateString(level.getINTERPOLATE()) + .build(); + } + + private void parseLevels(Record r, + Map builderMap, String unit) { usace.cwms.db.jooq.codegen.tables.AV_LOCATION_LEVEL view = AV_LOCATION_LEVEL; + usace.cwms.db.jooq.codegen.tables.AV_VIRTUAL_LOCATION_LEVEL virtView = AV_VIRTUAL_LOCATION_LEVEL; + boolean virtual = false; Timestamp levelDateTimestamp = r.get(view.LEVEL_DATE); String attrId = r.get(view.ATTRIBUTE_ID); Double oattrVal = r.get(view.ATTRIBUTE_VALUE); @@ -414,82 +593,143 @@ private void addSeasonalValue(Record r, String levelUnit = r.get(view.LEVEL_UNIT); String attrUnit = r.get(AV_LOCATION_LEVEL.ATTRIBUTE_UNIT); + // Virtual fields + Timestamp virtualLevelDateTimestamp = r.get(virtView.EFFECTIVE_DATE_UTC); + String virtAttrId = r.get(virtView.ATTRIBUTE_ID); + String virtLocLevelId = r.get(virtView.LOCATION_LEVEL_ID); + String virtOfficeId = r.get(virtView.OFFICE_ID); + String connections = r.get(virtView.CONNECTIONS); + Timestamp expirationDate = r.get(virtView.EXPIRATION_DATE_UTC); + ZonedDateTime expireDate = null; + Date levelDate = null; if (levelDateTimestamp != null) { levelDate = new Date(levelDateTimestamp.getTime()); + } else if (virtualLevelDateTimestamp != null) { + levelDate = new Date(virtualLevelDateTimestamp.getTime()); } 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); - - 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); - - builder.withAttributeParameterId(attrId); - builder.withAttributeUnitsId(attrUnit); - builder.withLevelUnitsId(levelUnit); + if (unit.equalsIgnoreCase(UnitSystem.EN.value())) { + attrUnit = r.get(virtView.ATTR_UNIT_EN); + if (attrUnit != null) { + oattrVal = r.get(virtView.ATTR_VALUE_EN).doubleValue(); + } - if (oattrVal != null) { - builder.withAttributeValue(BigDecimal.valueOf(oattrVal)); + } else { + attrUnit = r.get(virtView.ATTR_UNIT_SI); + if (attrUnit != null) { + oattrVal = r.get(virtView.ATTR_VALUE_SI).doubleValue(); + } } - 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)); + } - builderMap.put(levelLookup, builder); + // handle virtual fields + if (officeId == null) { + officeId = virtOfficeId; + } + if (attrId == null) { + attrId = virtAttrId; + } + if (locLevelId == null) { + locLevelId = virtLocLevelId; + virtual = true; + } + if (expirationDate != null) { + expireDate = ZonedDateTime.ofInstant(expirationDate.toInstant(), ZoneId.of("UTC")); } + JDomLocationLevelRef locationLevelRef = new JDomLocationLevelRef(officeId, locLevelId, attrId, attrStr, attrUnit); + LevelLookup levelLookup = new LevelLookup(locationLevelRef, levelDate); - String interp = r.get(view.INTERPOLATE); - builder.withInterpolateString(interp); + ZonedDateTime levelZdt = null; + if (levelDate != null) { + levelZdt = ZonedDateTime.ofInstant(levelDate.toInstant(), ZoneId.of("UTC")); + } Double seasonalLevel = r.get(view.SEASONAL_LEVEL); + Double constantLevel = r.get(view.CONSTANT_LEVEL); + String tsId = r.get(view.TSID); + String interp = r.get(view.INTERPOLATE); + String calOffset = r.get(view.CALENDAR_OFFSET); + String timeOffset = r.get(view.TIME_OFFSET); + String levelComment = r.get(view.LEVEL_COMMENT); + String attributeComment = r.get(view.ATTRIBUTE_COMMENT); + DayToSecond timeInterval = r.get(view.TIME_INTERVAL); + String calendarInterval = r.get(view.CALENDAR_INTERVAL); + Timestamp intervalOrigin = r.get(view.INTERVAL_ORIGIN); + + if (constantLevel != null) { + ConstantLocationLevel.Builder constantBuilder = new ConstantLocationLevel.Builder(locLevelId, levelZdt); + constantBuilder.withConstantValue(constantLevel); + constantBuilder = withLocationLevelRef(constantBuilder, locationLevelRef); + + constantBuilder.withAttributeParameterId(attrId); + constantBuilder.withAttributeUnitsId(attrUnit); + constantBuilder.withLevelUnitsId(levelUnit); + + if (oattrVal != null) { + constantBuilder.withAttributeValue(BigDecimal.valueOf(oattrVal)); + } + constantBuilder.withLevelComment(levelComment); + constantBuilder.withAttributeComment(attributeComment); + builderMap.put(levelLookup, constantBuilder); + } else if (seasonalLevel != null) { - 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); + SeasonalLocationLevel.Builder seasonalBuilder = new SeasonalLocationLevel.Builder(locLevelId, levelZdt); + seasonalBuilder.withSeasonalValue(seasonalValue); + seasonalBuilder.withInterpolateString(interp); + seasonalBuilder.withIntervalMinutes(timeInterval.getMinutes()); + seasonalBuilder.withAttributeParameterId(attrId); + seasonalBuilder.withAttributeUnitsId(attrUnit); + seasonalBuilder.withLevelUnitsId(levelUnit); + seasonalBuilder.withLevelComment(levelComment); + seasonalBuilder.withAttributeComment(attributeComment); + seasonalBuilder = withLocationLevelRef(seasonalBuilder, locationLevelRef); + JDomSeasonalIntervalImpl offset = new JDomSeasonalIntervalImpl(); + offset.setYearMonthString(calendarInterval); + seasonalBuilder.withIntervalMonths(offset.getMonths()); + seasonalBuilder.withIntervalOrigin(intervalOrigin, levelZdt); + + builderMap.put(levelLookup, seasonalBuilder); + } else if (tsId != null) { + TimeSeriesLocationLevel.Builder timeSeriesBuilder = new TimeSeriesLocationLevel.Builder(locLevelId, levelZdt, tsId); + timeSeriesBuilder.withAttributeParameterId(attrId); + timeSeriesBuilder.withAttributeUnitsId(attrUnit); + timeSeriesBuilder.withLevelUnitsId(levelUnit); + timeSeriesBuilder.withLevelComment(levelComment); + timeSeriesBuilder.withAttributeComment(attributeComment); + timeSeriesBuilder = withLocationLevelRef(timeSeriesBuilder, locationLevelRef); + builderMap.put(levelLookup, timeSeriesBuilder); + } else if (virtual) { + VirtualLocationLevel.Builder builder; + if (!builderMap.containsKey(levelLookup)) { + if (levelDate != null) { + levelZdt = ZonedDateTime.ofInstant(levelDate.toInstant(), ZoneId.of("UTC")); + } + builder = new VirtualLocationLevel.Builder(locLevelId, levelZdt); + builder = withLocationLevelRef(builder, locationLevelRef); + builder.withAttributeParameterId(attrId); + builder.withAttributeUnitsId(attrUnit); + // Constituents are not currently included in returned VirtualLocationLevels for catalog operations + builder.withConstituentConnections(connections); + builder.withExpirationDate(expireDate); + if (oattrVal != null) { + builder.withAttributeValue(BigDecimal.valueOf(oattrVal)); + } + builderMap.put(levelLookup, builder); + } + } else { + throw new UnsupportedFormatException("Location level record is not of a recognized type: " + locLevelId); } } - private LocationLevel.Builder withLocationLevelRef(LocationLevel.Builder builder, JDomLocationLevelRef locationLevelRef) { + private > T withLocationLevelRef(T builder, JDomLocationLevelRef locationLevelRef) { ISpecifiedLevel specifiedLevel = locationLevelRef.getSpecifiedLevel(); if (specifiedLevel != null) { builder = builder.withSpecifiedLevelId(specifiedLevel.getId()); @@ -510,10 +750,7 @@ private LocationLevel.Builder withLocationLevelRef(LocationLevel.Builder builder builder = builder.withDurationId(duration.toString()); } - - return builder - .withOfficeId(locationLevelRef.getOfficeId()) - ; + return builder.withOfficeId(locationLevelRef.getOfficeId()); } private SeasonalValueBean buildSeasonalValueBean(Double seasonalLevel, @@ -525,20 +762,6 @@ private SeasonalValueBean buildSeasonalValueBean(Double seasonalLevel, .build(); } - // These are all the fields that we need to pull out of jOOQ record for parseSeasonalValues - private Collection> getParseSeasonalValuesFields() { - Set> retval = new LinkedHashSet<>(); - - retval.add(AV_LOCATION_LEVEL.SEASONAL_LEVEL); - retval.add(AV_LOCATION_LEVEL.CALENDAR_INTERVAL); - retval.add(AV_LOCATION_LEVEL.TIME_INTERVAL); - retval.add(AV_LOCATION_LEVEL.CALENDAR_OFFSET); - retval.add(AV_LOCATION_LEVEL.TIME_OFFSET); - - return retval; - } - - @NotNull private static JDomSeasonalIntervalImpl buildSeasonalOffset(String calOffset, String timeOffset) { 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 deleted file mode 100644 index 953eaf40d..000000000 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java +++ /dev/null @@ -1,589 +0,0 @@ -package cwms.cda.data.dto; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonRootName; -import com.fasterxml.jackson.databind.PropertyNamingStrategies; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonNaming; -import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; -import cwms.cda.formatters.Formats; -import cwms.cda.formatters.annotations.FormattableWith; -import cwms.cda.formatters.json.JsonV1; -import cwms.cda.formatters.json.JsonV2; -import cwms.cda.formatters.xml.XMLv2; -import hec.data.level.ILocationLevelRef; -import hec.data.level.IParameterTypedValue; -import hec.data.level.ISeasonalInterval; -import hec.data.level.ISeasonalValue; -import hec.data.level.ISeasonalValues; -import hec.data.level.JDomLocationLevelImpl; -import io.swagger.v3.oas.annotations.media.Schema; -import rma.util.RMAConst; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -@JsonRootName("LocationLevel") -@JsonDeserialize(builder = LocationLevel.Builder.class) -@JsonInclude(JsonInclude.Include.NON_NULL) -@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) -@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) -@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) -public final class LocationLevel extends CwmsDTO { - @JsonProperty(required = true) - @Schema(description = "Name of the location level") - - private final String locationLevelId; - @Schema(description = "Timeseries ID (e.g. from the times series catalog) to use as the " - + "location level. Mutually exclusive with seasonalValues and " - + "siParameterUnitsConstantValue") - - private final String seasonalTimeSeriesId; - @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"}) - - private final String parameterTypeId; - @Schema(description = "Data Type such as Stage, Elevation, or others.") - - private final String parameterId; - @Schema(description = "Single value for this location level. Mutually exclusive with " - + "seasonableTimeSeriesId and seasonValues.") - - private final Double constantValue; - @Schema(description = "Units the provided levels are in") - - private final String levelUnitsId; - @Schema(description = "The date/time at which this location level configuration takes effect.") - @JsonFormat(shape = JsonFormat.Shape.STRING) - - private final ZonedDateTime levelDate; - - private final String levelComment; - @Schema(description = "The start point of provided seasonal values") - @JsonFormat(shape = JsonFormat.Shape.STRING) - - private final ZonedDateTime intervalOrigin; - - private final Integer intervalMonths; - - private final Integer intervalMinutes; - @Schema(description = "Indicating whether or not to interpolate between seasonal values.", - allowableValues = {"T", "F"}) - - private final String interpolateString; - @Schema(description = "0 if parameterTypeId is Inst. Otherwise duration indicating the time " - + "window of the aggregate value.") - - private final String durationId; - - private final BigDecimal attributeValue; - - private final String attributeUnitsId; - - private final String attributeParameterTypeId; - - private final String attributeParameterId; - - private final String attributeDurationId; - - private final String attributeComment; - - @Schema(description = "List of Repeating seasonal values. The values repeater after the " - + "specified interval." - + " A yearly interval seasonable could have 12 different values, one for each month for" - + " example. Mutually exclusive with seasonalTimeSeriesId and " - + "siParameterUnitsConstantValue") - - private final List seasonalValues; - - private LocationLevel(Builder builder) { - super(builder.officeId); - seasonalTimeSeriesId = builder.seasonalTimeSeriesId; - seasonalValues = builder.seasonalValues; - specifiedLevelId = builder.specifiedLevelId; - parameterTypeId = builder.parameterTypeId; - parameterId = builder.parameterId; - constantValue = builder.constantValue; - levelUnitsId = builder.levelUnitsId; - levelDate = builder.levelDate; - levelComment = builder.levelComment; - intervalOrigin = builder.intervalOrigin; - intervalMonths = builder.intervalMonths; - intervalMinutes = builder.intervalMinutes; - interpolateString = builder.interpolateString; - durationId = builder.durationId; - attributeValue = builder.attributeValue; - attributeUnitsId = builder.attributeUnitsId; - attributeParameterTypeId = builder.attributeParameterTypeId; - attributeParameterId = builder.attributeParameterId; - attributeDurationId = builder.attributeDurationId; - attributeComment = builder.attributeComment; - locationLevelId = builder.locationId; - } - - public String getSeasonalTimeSeriesId() { - return seasonalTimeSeriesId; - } - - public List getSeasonalValues() { - return seasonalValues; - } - - public String getSpecifiedLevelId() { - return specifiedLevelId; - } - - public String getParameterTypeId() { - return parameterTypeId; - } - - public String getParameterId() { - return parameterId; - } - - public Double getConstantValue() { - return constantValue; - } - - public String getLevelUnitsId() { - return levelUnitsId; - } - - public ZonedDateTime getLevelDate() { - return levelDate; - } - - public String getLevelComment() { - return levelComment; - } - - public ZonedDateTime getIntervalOrigin() { - return intervalOrigin; - } - - public Integer getIntervalMonths() { - return intervalMonths; - } - - public Integer getIntervalMinutes() { - return intervalMinutes; - } - - public String getInterpolateString() { - return interpolateString; - } - - public String getDurationId() { - return durationId; - } - - public BigDecimal getAttributeValue() { - return attributeValue; - } - - public String getAttributeUnitsId() { - return attributeUnitsId; - } - - public String getAttributeParameterTypeId() { - return attributeParameterTypeId; - } - - public String getAttributeParameterId() { - return attributeParameterId; - } - - public String getAttributeDurationId() { - return attributeDurationId; - } - - public String getAttributeComment() { - return attributeComment; - } - - public String getLocationLevelId() { - return locationLevelId; - } - - @JsonPOJOBuilder - @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) - public static class Builder { - private String seasonalTimeSeriesId; - private List seasonalValues; - private String specifiedLevelId; - private String parameterTypeId; - private String parameterId; - private Double constantValue; - private String levelUnitsId; - private ZonedDateTime levelDate; - private String levelComment; - private ZonedDateTime intervalOrigin; - private Integer intervalMonths; - private Integer intervalMinutes; - private String interpolateString; - private String durationId; - private BigDecimal attributeValue; - private String attributeUnitsId; - private String attributeParameterTypeId; - private String attributeParameterId; - private String attributeDurationId; - private String attributeComment; - private String locationId; - private String officeId; - private final Map> propertyFunctionMap = new HashMap<>(); - - @JsonCreator - public Builder(@JsonProperty(value = "location-level-id", required = true) String name, - @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate) { - locationId = name; - levelDate = lvlDate; - buildPropertyFunctions(); - } - - public Builder(LocationLevel copyFrom) { - withAttributeComment(copyFrom.getAttributeComment()); - withAttributeDurationId(copyFrom.getAttributeDurationId()); - withAttributeParameterId(copyFrom.getAttributeParameterId()); - withLocationLevelId(copyFrom.getLocationLevelId()); - withAttributeValue(copyFrom.getAttributeValue()); - withAttributeParameterTypeId(copyFrom.getAttributeParameterTypeId()); - withAttributeUnitsId(copyFrom.getAttributeUnitsId()); - withDurationId(copyFrom.getDurationId()); - withInterpolateString(copyFrom.getInterpolateString()); - withIntervalMinutes(copyFrom.getIntervalMinutes()); - withIntervalMonths(copyFrom.getIntervalMonths()); - withIntervalOrigin(copyFrom.getIntervalOrigin()); - withLevelComment(copyFrom.getLevelComment()); - withLevelDate(copyFrom.getLevelDate()); - withLevelUnitsId(copyFrom.getLevelUnitsId()); - withOfficeId(copyFrom.getOfficeId()); - withParameterId(copyFrom.getParameterId()); - withParameterTypeId(copyFrom.getParameterTypeId()); - withSeasonalTimeSeriesId(copyFrom.getSeasonalTimeSeriesId()); - withSeasonalValues(copyFrom.getSeasonalValues()); - withConstantValue(copyFrom.getConstantValue()); - withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); - buildPropertyFunctions(); - } - - public Builder(JDomLocationLevelImpl copyFrom) { - withAttributeComment(copyFrom.getAttributeComment()); - withAttributeDurationId(copyFrom.getAttributeDurationId()); - withAttributeParameterId(copyFrom.getAttributeParameterId()); - ILocationLevelRef locationLevelRef = copyFrom.getLocationLevelRef(); - if (locationLevelRef != null) { - withLocationLevelId(locationLevelRef.getLocationLevelId()); - } - withAttributeValue(copyFrom.getAttributeValue()); - withAttributeParameterTypeId(copyFrom.getAttributeParameterTypeId()); - withAttributeUnitsId(copyFrom.getAttributeUnitsId()); - withDurationId(copyFrom.getDurationId()); - withInterpolateString(copyFrom.getInterpolateString()); - withIntervalMinutes(copyFrom.getIntervalMinutes()); - withIntervalMonths(copyFrom.getIntervalMonths()); - Date intervalOriginDate = copyFrom.getIntervalOrigin(); - if (intervalOriginDate != null) { - withIntervalOrigin(ZonedDateTime.ofInstant(intervalOriginDate.toInstant(), - ZoneId.of("UTC"))); - } - - withLevelComment(copyFrom.getLevelComment()); - Date copyLevelDate = copyFrom.getLevelDate(); - if (copyLevelDate != null) { - withLevelDate(ZonedDateTime.ofInstant(copyLevelDate.toInstant(), ZoneId.of("UTC"))); - } - - withLevelUnitsId(copyFrom.getLevelUnitsId()); - withOfficeId(copyFrom.getOfficeId()); - withParameterId(copyFrom.getParameterId()); - withParameterTypeId(copyFrom.getParameterTypeId()); - withSeasonalTimeSeriesId(copyFrom.getSeasonalTimeSeriesId()); - withISeasonalValues(copyFrom.getSeasonalValues()); - - IParameterTypedValue constantLevel = copyFrom.getConstantLevel(); - if (constantLevel != null) { - withConstantValue(constantLevel.getSiParameterUnitsValue()); - } - withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); - buildPropertyFunctions(); - } - - @JsonIgnore - private void buildPropertyFunctions() { - propertyFunctionMap.clear(); - propertyFunctionMap.put("location-level-id", - nameVal -> withLocationLevelId((String) nameVal)); - propertyFunctionMap.put("seasonal-time-series-id", - tsIdVal -> withSeasonalTimeSeriesId((String) tsIdVal)); - propertyFunctionMap.put("seasonal-values", - seasonalVals -> withSeasonalValues((List) seasonalVals)); - propertyFunctionMap.put("office-id", officeIdVal -> withOfficeId((String) officeIdVal)); - propertyFunctionMap.put("specified-level-id", - specifiedLevelIdVal -> withSpecifiedLevelId((String) specifiedLevelIdVal)); - propertyFunctionMap.put("parameter-type-id", - parameterTypeIdVal -> withParameterTypeId((String) parameterTypeIdVal)); - propertyFunctionMap.put("parameter-id", - parameterIdVal -> withParameterId((String) parameterIdVal)); - propertyFunctionMap.put("si-parameter-units-constant-value", - paramUnitsConstVal -> withConstantValue((Double) paramUnitsConstVal)); - propertyFunctionMap.put("level-units-id", - levelUnitsIdVal -> withLevelUnitsId((String) levelUnitsIdVal)); - propertyFunctionMap.put("level-date", - levelDateVal -> withLevelDate((ZonedDateTime) levelDateVal)); - propertyFunctionMap.put("level-comment", - levelCommentVal -> withLevelComment((String) levelCommentVal)); - propertyFunctionMap.put("interval-origin", - intervalOriginVal -> withIntervalOrigin((ZonedDateTime) intervalOriginVal)); - propertyFunctionMap.put("interval-months", - months -> withIntervalMonths((Integer) months)); - propertyFunctionMap.put("interval-minutes", - mins -> withIntervalMinutes((Integer) mins)); - propertyFunctionMap.put("interpolate-string", - interpolateStr -> withInterpolateString((String) interpolateStr)); - propertyFunctionMap.put("duration-id", - durationIdVal -> withDurationId((String) durationIdVal)); - propertyFunctionMap.put("attribute-value", - attributeVal -> withAttributeValue(BigDecimal.valueOf((Double) attributeVal))); - propertyFunctionMap.put("attribute-units-id", - attributeUnitsIdVal -> withAttributeUnitsId((String) attributeUnitsIdVal)); - propertyFunctionMap.put("attribute-parameter-type-id", - attributeParameterTypeIdVal -> - withAttributeParameterTypeId((String) attributeParameterTypeIdVal)); - propertyFunctionMap.put("attribute-parameter-id", - attributeParameterIdVal -> - withAttributeParameterId((String) attributeParameterIdVal)); - propertyFunctionMap.put("attribute-duration-id", - attributeDurationIdVal -> withAttributeDurationId((String) attributeDurationIdVal)); - propertyFunctionMap.put("attribute-comment", - attributeCommentVal -> withAttributeComment((String) attributeCommentVal)); - } - - @JsonIgnore - public Builder withProperty(String propertyName, Object value) { - Consumer function = propertyFunctionMap.get(propertyName); - if (function == null) { - throw new IllegalArgumentException("Property Name does not exist for Location " - + "Level"); - } - function.accept(value); - return this; - } - - public Builder withSeasonalTimeSeriesId(String seasonalTimeSeriesId) { - this.seasonalTimeSeriesId = seasonalTimeSeriesId; - return this; - } - - public Builder withSeasonalValues(List seasonalValues) { - this.seasonalValues = 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) { - 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) { - List retval = null; - if (seasonalValues != null) { - retval = new ArrayList<>(); - for (ISeasonalValue seasonalValue : seasonalValues.getSeasonalValues()) { - retval.add(buildSeasonalValueBean(seasonalValue)); - } - } - return retval; - } - - public Builder withSpecifiedLevelId(String specifiedLevelId) { - this.specifiedLevelId = specifiedLevelId; - return this; - } - - public Builder withParameterTypeId(String parameterTypeId) { - this.parameterTypeId = parameterTypeId; - return this; - } - - public Builder withParameterId(String parameterId) { - this.parameterId = parameterId; - return this; - } - - public Builder withConstantValue(Double value) { - if (value != null && RMAConst.isUndefinedValue(value)) { - value = null; - } - this.constantValue = value; - return this; - } - - public Builder withLevelUnitsId(String levelUnitsId) { - this.levelUnitsId = levelUnitsId; - return this; - } - - public Builder withLevelDate(ZonedDateTime levelDate) { - this.levelDate = levelDate; - return this; - } - - public Builder withLevelComment(String levelComment) { - this.levelComment = levelComment; - return this; - } - - public Builder withIntervalOrigin(ZonedDateTime intervalOrigin) { - this.intervalOrigin = intervalOrigin; - return this; - } - - public Builder withIntervalOrigin(Date intervalOriginDate, ZonedDateTime effectiveDate) { - if (intervalOriginDate != null && effectiveDate != null) { - return withIntervalOrigin(ZonedDateTime.ofInstant(intervalOriginDate.toInstant(), - effectiveDate.getZone())); - } else { - this.intervalOrigin = null; - return this; - } - } - - public Builder withIntervalMonths(Integer months) { - if (months != null && RMAConst.isUndefinedValue(months)) { - months = null; - } - this.intervalMonths = months; - return this; - } - - public Builder withIntervalMinutes(Integer minutes) { - if (minutes != null && RMAConst.isUndefinedValue(minutes)) { - minutes = null; - } - this.intervalMinutes = minutes; - return this; - } - - public Builder withInterpolateString(String interpolateString) { - this.interpolateString = interpolateString; - return this; - } - - public Builder withDurationId(String durationId) { - this.durationId = durationId; - return this; - } - - public Builder withAttributeValue(BigDecimal attributeValue) { - this.attributeValue = attributeValue; - return this; - } - - public Builder withAttributeUnitsId(String attributeUnitsId) { - this.attributeUnitsId = attributeUnitsId; - return this; - } - - public Builder withAttributeParameterTypeId(String attributeParameterTypeId) { - this.attributeParameterTypeId = attributeParameterTypeId; - return this; - } - - public Builder withAttributeParameterId(String attributeParameterId) { - this.attributeParameterId = attributeParameterId; - return this; - } - - public Builder withAttributeDurationId(String attributeDurationId) { - this.attributeDurationId = attributeDurationId; - return this; - } - - public Builder withAttributeComment(String attributeComment) { - this.attributeComment = attributeComment; - return this; - } - - public Builder withLocationLevelId(String locationId) { - this.locationId = locationId; - return this; - } - - public Builder withOfficeId(String officeId) { - this.officeId = officeId; - return this; - } - - public LocationLevel build() { - return new LocationLevel(this); - } - } - - @Override - protected void validateInternal(CwmsDTOValidator validator) { - super.validateInternal(validator); - validator.required(getOfficeId(), "office-id"); - validator.required(getLocationLevelId(), "location-level-id"); - if(getConstantValue() == null && getSeasonalTimeSeriesId() == null) { - validator.required(getSeasonalValues(), "seasonal-values"); - } else if(getSeasonalValues() == null && getSeasonalTimeSeriesId() == null) { - validator.required(getConstantValue(), "constant-value"); - } else if(getConstantValue() == null && getSeasonalValues() == null) { - validator.required(getSeasonalTimeSeriesId(), "seasonable-time-series-id"); - } - validator.mutuallyExclusive("Only one of the following can be defined for location levels: constant-value, seasonal-values, seasonable-time-series-id", - getConstantValue(), getSeasonalValues(), getSeasonalTimeSeriesId()); - } -} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/ConstantLocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/ConstantLocationLevel.java new file mode 100644 index 000000000..c03bfe370 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/ConstantLocationLevel.java @@ -0,0 +1,119 @@ +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; + +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dto.CwmsDTOValidator; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; +import cwms.cda.formatters.xml.XMLv2; +import io.swagger.v3.oas.annotations.media.Schema; + +import hec.data.level.IParameterTypedValue; +import hec.data.level.JDomLocationLevelImpl; +import rma.util.RMAConst; + +@JsonRootName("ConstantLocationLevel") +@JsonDeserialize(builder = ConstantLocationLevel.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) +@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) +public final class ConstantLocationLevel extends LocationLevel { + @Schema(description = "Single value for this location level.") + + private final Double constantValue; + + private ConstantLocationLevel(ConstantLocationLevel.Builder builder) { + super(builder); + constantValue = builder.constantValue; + } + + public Double getConstantValue() { + return constantValue; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Builder extends LocationLevel.Builder { + private Double constantValue; + + @JsonCreator + public Builder(@JsonProperty(value = "location-level-id", required = true) String name, + @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate) { + super(name, lvlDate); + } + + public Builder(ConstantLocationLevel copyFrom) { + super(copyFrom); + withConstantValue(copyFrom.getConstantValue()); + } + + public Builder(JDomLocationLevelImpl copyFrom) { + super(copyFrom); + IParameterTypedValue constantLevel = copyFrom.getConstantLevel(); + if (constantLevel != null) { + withConstantValue(constantLevel.getSiParameterUnitsValue()); + } + } + + public ConstantLocationLevel.Builder withConstantValue(Double value) { + if (value != null && RMAConst.isUndefinedValue(value)) { + value = null; + } + this.constantValue = value; + return this; + } + + @Override + public ConstantLocationLevel build() { + return new ConstantLocationLevel(this); + } + } + + @Override + protected void validateInternal(CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.required(getOfficeId(), "office-id"); + validator.required(getLocationLevelId(), "location-level-id"); + validator.required(getConstantValue(), "constant-value"); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevel.java new file mode 100644 index 000000000..ee15631f5 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevel.java @@ -0,0 +1,507 @@ +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; + +import java.math.BigDecimal; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dto.CwmsDTO; +import cwms.cda.data.dto.CwmsDTOValidator; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.UnsupportedFormatException; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; +import cwms.cda.formatters.xml.XMLv2; +import io.swagger.v3.oas.annotations.media.Schema; + +import hec.data.level.ILocationLevelRef; +import hec.data.level.JDomLocationLevelImpl; + +@JsonRootName("LocationLevel") +@JsonDeserialize(builder = LocationLevel.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) +@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class LocationLevel extends CwmsDTO { + @JsonProperty(required = true) + @Schema(description = "Name of the location level") + + private final String locationLevelId; + @Schema(description = "Timeseries ID (e.g. from the times series catalog) to use as the " + + "location level. Mutually exclusive with seasonalValues and " + + "siParameterUnitsConstantValue") + + private final String specifiedLevelId; + + private final ZonedDateTime expirationDate; + + @Schema(description = "Data Type such as Stage, Elevation, or others.") + + private final String parameterId; + @Schema(description = "To indicate if single or aggregate value", + allowableValues = {"Inst", "Ave", "Min", "Max", "Total"}) + + private final String parameterTypeId; + @Schema(description = "Indicating whether or not to interpolate between seasonal values.", + allowableValues = {"T", "F"}) + + private final String interpolateString; + + @Schema(description = "Units the provided levels are in") + + private final String levelUnitsId; + @Schema(description = "The date/time at which this location level configuration takes effect.") + @JsonFormat(shape = JsonFormat.Shape.STRING) + + private final ZonedDateTime levelDate; + + private final String levelComment; + @Schema(description = "0 if parameterTypeId is Inst. Otherwise duration indicating the time " + + "window of the aggregate value.") + + private final String durationId; + + private final BigDecimal attributeValue; + + private final String attributeUnitsId; + + private final String attributeParameterTypeId; + + private final String attributeParameterId; + + private final String attributeDurationId; + + private final String attributeComment; + + LocationLevel(LocationLevel.Builder builder) { + super(builder.officeId); + specifiedLevelId = builder.specifiedLevelId; + parameterTypeId = builder.parameterTypeId; + levelUnitsId = builder.levelUnitsId; + levelDate = builder.levelDate; + levelComment = builder.levelComment; + durationId = builder.durationId; + attributeValue = builder.attributeValue; + attributeUnitsId = builder.attributeUnitsId; + attributeParameterTypeId = builder.attributeParameterTypeId; + attributeParameterId = builder.attributeParameterId; + attributeDurationId = builder.attributeDurationId; + attributeComment = builder.attributeComment; + locationLevelId = builder.locationId; + parameterId = builder.parameterId; + interpolateString = builder.interpolateString; + expirationDate = builder.expirationDate; + } + + public String getSpecifiedLevelId() { + return specifiedLevelId; + } + + public String getParameterTypeId() { + return parameterTypeId; + } + + public String getLevelUnitsId() { + return levelUnitsId; + } + + public ZonedDateTime getLevelDate() { + return levelDate; + } + + public ZonedDateTime getExpirationDate() { + return expirationDate; + } + + public String getLevelComment() { + return levelComment; + } + + public String getInterpolateString() { + return interpolateString; + } + + public String getDurationId() { + return durationId; + } + + public BigDecimal getAttributeValue() { + return attributeValue; + } + + public String getAttributeUnitsId() { + return attributeUnitsId; + } + + public String getAttributeParameterTypeId() { + return attributeParameterTypeId; + } + + public String getAttributeParameterId() { + return attributeParameterId; + } + + public String getAttributeDurationId() { + return attributeDurationId; + } + + public String getAttributeComment() { + return attributeComment; + } + + public String getLocationLevelId() { + return locationLevelId; + } + + public String getParameterId() { + return parameterId; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public abstract static class Builder> { + String specifiedLevelId; + String parameterTypeId; + String parameterId; + String levelUnitsId; + ZonedDateTime levelDate; + String levelComment; + String durationId; + BigDecimal attributeValue; + String attributeUnitsId; + String attributeParameterTypeId; + String attributeParameterId; + String attributeDurationId; + String attributeComment; + String locationId; + String officeId; + String interpolateString; + ZonedDateTime expirationDate; + final Map> propertyFunctionMap = new HashMap<>(); + + @JsonCreator + protected Builder(@JsonProperty(value = "location-level-id", required = true) String name, + @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate) { + locationId = name; + levelDate = lvlDate; + } + + Builder(LocationLevel copyFrom) { + withAttributeComment(copyFrom.getAttributeComment()); + withAttributeDurationId(copyFrom.getAttributeDurationId()); + withAttributeParameterId(copyFrom.getAttributeParameterId()); + withLocationLevelId(copyFrom.getLocationLevelId()); + withAttributeValue(copyFrom.getAttributeValue()); + withAttributeParameterTypeId(copyFrom.getAttributeParameterTypeId()); + withAttributeUnitsId(copyFrom.getAttributeUnitsId()); + withDurationId(copyFrom.getDurationId()); + withLevelComment(copyFrom.getLevelComment()); + withLevelDate(copyFrom.getLevelDate()); + withLevelUnitsId(copyFrom.getLevelUnitsId()); + withOfficeId(copyFrom.getOfficeId()); + withParameterTypeId(copyFrom.getParameterTypeId()); + withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); + } + + Builder(JDomLocationLevelImpl copyFrom) { + withAttributeComment(copyFrom.getAttributeComment()); + withAttributeDurationId(copyFrom.getAttributeDurationId()); + withAttributeParameterId(copyFrom.getAttributeParameterId()); + ILocationLevelRef locationLevelRef = copyFrom.getLocationLevelRef(); + if (locationLevelRef != null) { + withLocationLevelId(locationLevelRef.getLocationLevelId()); + } + withAttributeValue(copyFrom.getAttributeValue()); + withAttributeParameterTypeId(copyFrom.getAttributeParameterTypeId()); + withAttributeUnitsId(copyFrom.getAttributeUnitsId()); + withDurationId(copyFrom.getDurationId()); + withLevelComment(copyFrom.getLevelComment()); + Date copyLevelDate = copyFrom.getLevelDate(); + if (copyLevelDate != null) { + withLevelDate(ZonedDateTime.ofInstant(copyLevelDate.toInstant(), ZoneId.of("UTC"))); + } + withLevelUnitsId(copyFrom.getLevelUnitsId()); + withOfficeId(copyFrom.getOfficeId()); + withParameterId(copyFrom.getParameterId()); + withParameterTypeId(copyFrom.getParameterTypeId()); + withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); + } + + protected T self() { + return (T) this; + } + + @JsonIgnore + public T withProperty(String propertyName, Object value) { + Consumer function = propertyFunctionMap.get(propertyName); + if (function == null) { + throw new IllegalArgumentException("Property Name does not exist for Location " + + "Level"); + } + function.accept(value); + return self(); + } + + public T withSpecifiedLevelId(String specifiedLevelId) { + this.specifiedLevelId = specifiedLevelId; + return self(); + } + + public T withParameterTypeId(String parameterTypeId) { + this.parameterTypeId = parameterTypeId; + return self(); + } + + public T withParameterId(String parameterId) { + this.parameterId = parameterId; + return self(); + } + + public T withExpirationDate(ZonedDateTime expirationDate) { + this.expirationDate = expirationDate; + return self(); + } + + public T withLevelUnitsId(String levelUnitsId) { + this.levelUnitsId = levelUnitsId; + return self(); + } + + public T withLevelDate(ZonedDateTime levelDate) { + this.levelDate = levelDate; + return self(); + } + + public T withLevelComment(String levelComment) { + this.levelComment = levelComment; + return self(); + } + + public T withInterpolateString(String interpolateString) { + this.interpolateString = interpolateString; + return self(); + } + + public T withDurationId(String durationId) { + this.durationId = durationId; + return self(); + } + + public T withAttributeValue(BigDecimal attributeValue) { + this.attributeValue = attributeValue; + return self(); + } + + public T withAttributeUnitsId(String attributeUnitsId) { + this.attributeUnitsId = attributeUnitsId; + return self(); + } + + public T withAttributeParameterTypeId(String attributeParameterTypeId) { + this.attributeParameterTypeId = attributeParameterTypeId; + return self(); + } + + public T withAttributeParameterId(String attributeParameterId) { + this.attributeParameterId = attributeParameterId; + return self(); + } + + public T withAttributeDurationId(String attributeDurationId) { + this.attributeDurationId = attributeDurationId; + return self(); + } + + public T withAttributeComment(String attributeComment) { + this.attributeComment = attributeComment; + return self(); + } + + public T withLocationLevelId(String locationId) { + this.locationId = locationId; + return self(); + } + + public T withOfficeId(String officeId) { + this.officeId = officeId; + return self(); + } + + public abstract LocationLevel build(); + } + + public static LocationLevel getUpdatedLocationLevel(LocationLevel existingLevel, + LocationLevel updatedLevel, ZonedDateTime unmarshalledDate) { + + String specifiedLevelId = (updatedLevel.getSpecifiedLevelId() == null + ? existingLevel.getSpecifiedLevelId() : updatedLevel.getSpecifiedLevelId()); + String parameterTypeId = (updatedLevel.getParameterTypeId() == null + ? existingLevel.getParameterTypeId() : updatedLevel.getParameterTypeId()); + String parameterId = (updatedLevel.getParameterId() == null + ? existingLevel.getParameterId() : updatedLevel.getParameterId()); + + String levelUnitsId = (updatedLevel.getLevelUnitsId() == null + ? existingLevel.getLevelUnitsId() : updatedLevel.getLevelUnitsId()); + String levelComment = (updatedLevel.getLevelComment() == null + ? existingLevel.getLevelComment() : updatedLevel.getLevelComment()); + + String durationId = (updatedLevel.getDurationId() == null + ? existingLevel.getDurationId() : updatedLevel.getDurationId()); + BigDecimal attributeValue = (updatedLevel.getAttributeValue() == null + ? existingLevel.getAttributeValue() : updatedLevel.getAttributeValue()); + String attributeUnitsId = (updatedLevel.getAttributeUnitsId() == null + ? existingLevel.getAttributeUnitsId() : updatedLevel.getAttributeUnitsId()); + String attributeParameterTypeId = (updatedLevel.getAttributeParameterTypeId() == null + ? existingLevel.getAttributeParameterTypeId() : + updatedLevel.getAttributeParameterTypeId()); + String attributeParameterId = (updatedLevel.getAttributeParameterId() == null + ? existingLevel.getAttributeParameterId() : updatedLevel.getAttributeParameterId()); + String attributeDurationId = (updatedLevel.getAttributeDurationId() == null + ? existingLevel.getAttributeDurationId() : updatedLevel.getAttributeDurationId()); + String attributeComment = (updatedLevel.getAttributeComment() == null + ? existingLevel.getAttributeComment() : updatedLevel.getAttributeComment()); + String locationId = (updatedLevel.getLocationLevelId() == null + ? existingLevel.getLocationLevelId() : updatedLevel.getLocationLevelId()); + String officeId = (updatedLevel.getOfficeId() == null + ? existingLevel.getOfficeId() : updatedLevel.getOfficeId()); + + if (existingLevel.getAttributeValue() == null) { + attributeUnitsId = null; + } + + LocationLevel.Builder builder; + + if (existingLevel instanceof VirtualLocationLevel) { + VirtualLocationLevel virtualLevel = (VirtualLocationLevel) existingLevel; + VirtualLocationLevel updatedVirtualLevel = (VirtualLocationLevel) updatedLevel; + + String constituentConnections = (updatedVirtualLevel.getConstituentConnections() == null + ? virtualLevel.getConstituentConnections() : updatedVirtualLevel.getConstituentConnections()); + List constituents = updatedVirtualLevel.getConstituents() == null + ? virtualLevel.getConstituents() : updatedVirtualLevel.getConstituents(); + + builder = new VirtualLocationLevel.Builder(locationId, unmarshalledDate) + .withExpirationDate(updatedVirtualLevel.getExpirationDate() == null + ? virtualLevel.getExpirationDate() : updatedVirtualLevel.getExpirationDate()) + .withConstituents(constituents) + .withConstituentConnections(constituentConnections); + } else if (existingLevel instanceof ConstantLocationLevel) { + ConstantLocationLevel constantLevel = (ConstantLocationLevel) existingLevel; + ConstantLocationLevel updatedConstantLevel = (ConstantLocationLevel) updatedLevel; + + Double siParameterUnitsConstantValue = (updatedConstantLevel.getConstantValue() == null + ? constantLevel.getConstantValue() : updatedConstantLevel.getConstantValue()); + + builder = new ConstantLocationLevel.Builder(locationId, unmarshalledDate) + .withConstantValue(siParameterUnitsConstantValue); + } else if (existingLevel instanceof TimeSeriesLocationLevel) { + TimeSeriesLocationLevel timeSeriesLevel = (TimeSeriesLocationLevel) existingLevel; + TimeSeriesLocationLevel updatedTimeSeriesLevel = (TimeSeriesLocationLevel) updatedLevel; + + String seasonalTimeSeriesId = (updatedTimeSeriesLevel.getSeasonalTimeSeriesId() == null + ? timeSeriesLevel.getSeasonalTimeSeriesId() : updatedTimeSeriesLevel.getSeasonalTimeSeriesId()); + + builder = new TimeSeriesLocationLevel.Builder(locationId, unmarshalledDate, seasonalTimeSeriesId); + } else if (existingLevel instanceof SeasonalLocationLevel) { + SeasonalLocationLevel seasonalLevel = (SeasonalLocationLevel) existingLevel; + SeasonalLocationLevel updatedSeasonalLevel = (SeasonalLocationLevel) updatedLevel; + + ZonedDateTime intervalOrigin = (updatedSeasonalLevel.getIntervalOrigin() == null + ? seasonalLevel.getIntervalOrigin() : updatedSeasonalLevel.getIntervalOrigin()); + Integer intervalMinutes = (updatedSeasonalLevel.getIntervalMinutes() == null + ? seasonalLevel.getIntervalMinutes() : updatedSeasonalLevel.getIntervalMinutes()); + Integer intervalMonths = (updatedSeasonalLevel.getIntervalMonths() == null + ? seasonalLevel.getIntervalMonths() : updatedSeasonalLevel.getIntervalMonths()); + String interpolateString = (updatedSeasonalLevel.getInterpolateString() == null + ? seasonalLevel.getInterpolateString() : updatedSeasonalLevel.getInterpolateString()); + + List seasonalValues = (updatedSeasonalLevel.getSeasonalValues() == null + ? seasonalLevel.getSeasonalValues() : updatedSeasonalLevel.getSeasonalValues()); + + if (seasonalLevel.getIntervalMonths() != null && seasonalLevel.getIntervalMonths() > 0) { + intervalMinutes = null; + } else if (seasonalLevel.getIntervalMinutes() != null + && seasonalLevel.getIntervalMinutes() > 0) { + intervalMonths = null; + } + + builder = new SeasonalLocationLevel.Builder(locationId, unmarshalledDate) + .withSeasonalValues(seasonalValues) + .withIntervalMinutes(intervalMinutes) + .withIntervalMonths(intervalMonths) + .withIntervalOrigin(intervalOrigin) + .withInterpolateString(interpolateString); + } else { + throw new UnsupportedFormatException("Unsupported Location Level type: " + + existingLevel.getClass().getName()); + } + + return builder.withParameterTypeId(parameterTypeId) + .withSpecifiedLevelId(specifiedLevelId) + .withParameterId(parameterId) + .withLevelUnitsId(levelUnitsId) + .withLevelComment(levelComment) + .withDurationId(durationId) + .withAttributeValue(attributeValue) + .withAttributeUnitsId(attributeUnitsId) + .withAttributeParameterTypeId(attributeParameterTypeId) + .withAttributeParameterId(attributeParameterId) + .withAttributeDurationId(attributeDurationId) + .withAttributeComment(attributeComment) + .withOfficeId(officeId) + .build(); + } + + @Override + protected void validateInternal(CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.required(getOfficeId(), "office-id"); + validator.required(getLocationLevelId(), "location-level-id"); + validator.required(getLevelDate(), "level-date"); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevels.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevels.java similarity index 66% rename from cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevels.java rename to cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevels.java index efda98db3..76fbbd204 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevels.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/LocationLevels.java @@ -1,8 +1,35 @@ -package cwms.cda.data.dto; +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import cwms.cda.data.dto.CwmsDTOPaginated; import cwms.cda.formatters.Formats; import cwms.cda.formatters.annotations.FormattableWith; import cwms.cda.formatters.json.JsonV2; diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalLocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalLocationLevel.java new file mode 100644 index 000000000..c1e046eb1 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalLocationLevel.java @@ -0,0 +1,269 @@ +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; + +import java.math.BigInteger; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dto.CwmsDTOValidator; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; +import cwms.cda.formatters.xml.XMLv2; +import io.swagger.v3.oas.annotations.media.Schema; + +import hec.data.level.IParameterTypedValue; +import hec.data.level.ISeasonalInterval; +import hec.data.level.ISeasonalValue; +import hec.data.level.ISeasonalValues; +import hec.data.level.JDomLocationLevelImpl; +import rma.util.RMAConst; + +@JsonRootName("SeasonalLocationLevel") +@JsonDeserialize(builder = SeasonalLocationLevel.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) +@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) +public final class SeasonalLocationLevel extends LocationLevel { + @Schema(description = "The start point of provided seasonal values") + @JsonFormat(shape = JsonFormat.Shape.STRING) + + private final ZonedDateTime intervalOrigin; + + private final Integer intervalMonths; + + private final Integer intervalMinutes; + + + @Schema(description = "List of Repeating seasonal values. The values repeat after the " + + "specified interval." + + " A yearly interval seasonable could have 12 different values, one for each month for" + + " example. Mutually exclusive with seasonalTimeSeriesId and " + + "siParameterUnitsConstantValue") + + private final List seasonalValues; + + private SeasonalLocationLevel(SeasonalLocationLevel.Builder builder) { + super(builder); + seasonalValues = builder.seasonalValues; + intervalOrigin = builder.intervalOrigin; + intervalMonths = builder.intervalMonths; + intervalMinutes = builder.intervalMinutes; + validate(); + } + + public List getSeasonalValues() { + return seasonalValues; + } + + public ZonedDateTime getIntervalOrigin() { + return intervalOrigin; + } + + public Integer getIntervalMonths() { + return intervalMonths; + } + + public Integer getIntervalMinutes() { + return intervalMinutes; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder extends LocationLevel.Builder { + private List seasonalValues; + private ZonedDateTime intervalOrigin; + private Integer intervalMonths; + private Integer intervalMinutes; + + @JsonCreator + public Builder(@JsonProperty(value = "location-level-id", required = true) String name, + @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate) { + super(name, lvlDate); + } + + public Builder(SeasonalLocationLevel copyFrom) { + super(copyFrom); + withAttributeComment(copyFrom.getAttributeComment()); + withAttributeDurationId(copyFrom.getAttributeDurationId()); + withAttributeParameterId(copyFrom.getAttributeParameterId()); + withLocationLevelId(copyFrom.getLocationLevelId()); + withAttributeValue(copyFrom.getAttributeValue()); + withAttributeParameterTypeId(copyFrom.getAttributeParameterTypeId()); + withAttributeUnitsId(copyFrom.getAttributeUnitsId()); + withDurationId(copyFrom.getDurationId()); + withInterpolateString(copyFrom.getInterpolateString()); + withIntervalMinutes(copyFrom.getIntervalMinutes()); + withIntervalMonths(copyFrom.getIntervalMonths()); + withIntervalOrigin(copyFrom.getIntervalOrigin()); + withLevelComment(copyFrom.getLevelComment()); + withLevelDate(copyFrom.getLevelDate()); + withLevelUnitsId(copyFrom.getLevelUnitsId()); + withOfficeId(copyFrom.getOfficeId()); + withParameterTypeId(copyFrom.getParameterTypeId()); + withSeasonalValues(copyFrom.getSeasonalValues()); + withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); + } + + public Builder(JDomLocationLevelImpl copyFrom) { + super(copyFrom); + withInterpolateString(copyFrom.getInterpolateString()); + withIntervalMinutes(copyFrom.getIntervalMinutes()); + withIntervalMonths(copyFrom.getIntervalMonths()); + Date intervalOriginDate = copyFrom.getIntervalOrigin(); + if (intervalOriginDate != null) { + withIntervalOrigin(ZonedDateTime.ofInstant(intervalOriginDate.toInstant(), + ZoneId.of("UTC"))); + } + + withISeasonalValues(copyFrom.getSeasonalValues()); + + withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); + } + + public SeasonalLocationLevel.Builder withSeasonalValues(List seasonalValues) { + this.seasonalValues = seasonalValues; + return this; + } + + @JsonIgnore + public SeasonalLocationLevel.Builder withISeasonalValues(ISeasonalValues values) { + if (values != null) { + withSeasonalValues(buildSeasonalValues(values)); + } else { + this.seasonalValues = null; + } + + return this; + } + + public SeasonalLocationLevel.Builder withSeasonalValue(SeasonalValueBean seasonalValue) { + if (seasonalValues == null) { + seasonalValues = new ArrayList<>(); + } + seasonalValues.add(seasonalValue); + return this; + } + + public static SeasonalValueBean buildSeasonalValueBean(ISeasonalValue seasonalValue) { + 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) { + List retval = null; + if (seasonalValues != null) { + retval = new ArrayList<>(); + for (ISeasonalValue seasonalValue : seasonalValues.getSeasonalValues()) { + retval.add(buildSeasonalValueBean(seasonalValue)); + } + } + return retval; + } + + public SeasonalLocationLevel.Builder withIntervalOrigin(ZonedDateTime intervalOrigin) { + this.intervalOrigin = intervalOrigin; + return this; + } + + public SeasonalLocationLevel.Builder withIntervalOrigin(Date intervalOriginDate, ZonedDateTime effectiveDate) { + if (intervalOriginDate != null && effectiveDate != null) { + return withIntervalOrigin(ZonedDateTime.ofInstant(intervalOriginDate.toInstant(), + effectiveDate.getZone())); + } else { + this.intervalOrigin = null; + return this; + } + } + + public SeasonalLocationLevel.Builder withIntervalMonths(Integer months) { + if (months != null && RMAConst.isUndefinedValue(months)) { + months = null; + } + this.intervalMonths = months; + return this; + } + + public SeasonalLocationLevel.Builder withIntervalMinutes(Integer minutes) { + if(minutes != null && RMAConst.isUndefinedValue(minutes)) { + minutes = null; + } + this.intervalMinutes = minutes; + return this; + } + + @Override + public SeasonalLocationLevel build() { + return new SeasonalLocationLevel(this); + } + } + + @Override + protected void validateInternal(CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.required(getOfficeId(), "office-id"); + validator.required(getLocationLevelId(), "location-level-id"); + validator.required(getSeasonalValues(), "seasonal-values"); + validator.mutuallyExclusive("Only one of the following can be defined at once for a seasonal location level: " + + "interval-minutes, interval-months", + getIntervalMinutes(), getIntervalMonths()); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/SeasonalValueBean.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalValueBean.java similarity index 70% rename from cwms-data-api/src/main/java/cwms/cda/data/dto/SeasonalValueBean.java rename to cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalValueBean.java index 271b7cd4d..45bd6258e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/SeasonalValueBean.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/SeasonalValueBean.java @@ -1,4 +1,30 @@ -package cwms.cda.data.dto; +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/TimeSeriesLocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/TimeSeriesLocationLevel.java new file mode 100644 index 000000000..7b475d70a --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/TimeSeriesLocationLevel.java @@ -0,0 +1,118 @@ +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; + +import java.time.ZonedDateTime; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dto.CwmsDTOValidator; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; +import cwms.cda.formatters.xml.XMLv2; +import io.swagger.v3.oas.annotations.media.Schema; + +import hec.data.level.JDomLocationLevelImpl; + +@JsonRootName("TimeSeriesLocationLevel") +@JsonDeserialize(builder = TimeSeriesLocationLevel.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) +@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) +public final class TimeSeriesLocationLevel extends LocationLevel { + @JsonProperty(required = true) + @Schema(description = "Timeseries ID (e.g. from the times series catalog) to use as the " + + "location level. Mutually exclusive with seasonalValues and " + + "siParameterUnitsConstantValue") + + private final String seasonalTimeSeriesId; + + private TimeSeriesLocationLevel(TimeSeriesLocationLevel.Builder builder) { + super(builder); + seasonalTimeSeriesId = builder.seasonalTimeSeriesId; + } + + public String getSeasonalTimeSeriesId() { + return seasonalTimeSeriesId; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder extends LocationLevel.Builder { + @JsonProperty(required = true) + private String seasonalTimeSeriesId; + + @JsonCreator + public Builder(@JsonProperty(value = "location-level-id", required = true) String name, + @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate, + @JsonProperty(value = "seasonal-time-series-id", required = true) String seasonalTimeSeriesId) { + super(name, lvlDate); + this.seasonalTimeSeriesId = seasonalTimeSeriesId; + } + + public Builder(TimeSeriesLocationLevel copyFrom) { + super(copyFrom); + this.seasonalTimeSeriesId = copyFrom.getSeasonalTimeSeriesId(); + withSpecifiedLevelId(copyFrom.getSpecifiedLevelId()); + } + + public Builder(JDomLocationLevelImpl copyFrom) { + super(copyFrom); + this.seasonalTimeSeriesId = copyFrom.getSeasonalTimeSeriesId(); + } + + public TimeSeriesLocationLevel.Builder withSeasonalTimeSeriesId(String seasonalTimeSeriesId) { + this.seasonalTimeSeriesId = seasonalTimeSeriesId; + return this; + } + + @Override + public TimeSeriesLocationLevel build() { + return new TimeSeriesLocationLevel(this); + } + } + + @Override + protected void validateInternal(CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.required(getOfficeId(), "office-id"); + validator.required(getLocationLevelId(), "location-level-id"); + validator.required(getSeasonalTimeSeriesId(), "seasonal-time-series-id"); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/VirtualLocationLevel.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/VirtualLocationLevel.java new file mode 100644 index 000000000..b65650822 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/locationlevel/VirtualLocationLevel.java @@ -0,0 +1,257 @@ +/* + * + * MIT License + * + * Copyright (c) 2025 Hydrologic Engineering Center + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE + * SOFTWARE. + */ + +package cwms.cda.data.dto.locationlevel; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import cwms.cda.formatters.json.JsonV2; + +@JsonRootName("VirtualLocationLevel") +@JsonDeserialize(builder = VirtualLocationLevel.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class) +public final class VirtualLocationLevel extends LocationLevel { + private final List constituents; + + private final String constituentConnections; + + private VirtualLocationLevel(Builder builder) { + super(builder); + this.constituents = builder.constituents; + this.constituentConnections = builder.constituentConnections; + } + + public List getConstituents() { + return constituents; + } + + public String getConstituentConnections() { + return constituentConnections; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Builder extends LocationLevel.Builder { + private List constituents; + private String constituentConnections; + + @JsonCreator + public Builder(@JsonProperty(value = "location-level-id", required = true) String name, + @JsonProperty(value = "level-date", required = true) ZonedDateTime lvlDate) { + super(name, lvlDate); + } + + public Builder(VirtualLocationLevel copyFrom) { + super(copyFrom); + this.constituents = copyFrom.constituents; + this.constituentConnections = copyFrom.constituentConnections; + } + + public Builder withConstituents(List constituents) { + this.constituents = constituents; + return this; + } + + public Builder withConstituentConnections(String constituentConnections) { + this.constituentConnections = constituentConnections; + return this; + } + + @Override + public VirtualLocationLevel build() { + return new VirtualLocationLevel(this); + } + } + + @JsonRootName("RATING") + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonDeserialize(builder = RatingConstituent.Builder.class) + @FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) + @JsonSubTypes({ + @JsonSubTypes.Type(value = LocationLevelConstituent.class, name = "LOCATION_LEVEL"), + @JsonSubTypes.Type(value = RatingConstituent.class, name = "RATING") + }) + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true) + public static class RatingConstituent + { + private final String abbr; + private final String type; + private final String name; + + private RatingConstituent(Builder builder) { + this.abbr = builder.abbr; + this.type = builder.type; + this.name = builder.name; + } + + public String getAbbr() + { + return abbr; + } + + public String getType() + { + return type; + } + + public String getName() { + return name; + } + + public List getConstituentList() { + List retVal = new ArrayList<>(); + retVal.add(abbr); + retVal.add(type); + retVal.add(name); + return retVal; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class Builder { + private final String abbr; + private final String type; + private final String name; + + @JsonCreator + public Builder(@JsonProperty(value = "abbr", required = true) String abbr, + @JsonProperty(value = "type", required = true) String type, + @JsonProperty(value = "name", required = true) String name) { + this.abbr = abbr; + this.type = type; + this.name = name; + } + + public RatingConstituent build() { + return new RatingConstituent(this); + } + } + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonDeserialize(builder = LocationLevelConstituent.Builder.class) + @FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) + public static final class LocationLevelConstituent extends RatingConstituent + { + private final String attributeId; + private final Double attributeValue; + private final String attributeUnits; + + private LocationLevelConstituent(Builder builder) { + super(builder); + this.attributeId = builder.attributeId; + this.attributeValue = builder.attributeValue; + this.attributeUnits = builder.attributeUnits; + } + + public String getAttributeId() + { + return attributeId; + } + + public Double getAttributeValue() + { + return attributeValue; + } + + public String getAttributeUnits() + { + return attributeUnits; + } + + @Override + public List getConstituentList() { + List retVal = new ArrayList<>(); + retVal.add(super.getAbbr()); + retVal.add(super.getType()); + retVal.add(super.getName()); + if(attributeId != null) { + retVal.add(attributeId); + } + if(attributeValue != null) { + retVal.add(attributeValue.toString()); + } + if(attributeUnits != null) { + retVal.add(attributeUnits); + } + return retVal; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Builder extends RatingConstituent.Builder { + @JsonProperty(required = true) + private final String attributeId; + private final Double attributeValue; + private String attributeUnits; + + @JsonCreator + public Builder(@JsonProperty(value = "abbr", required = true) String abbr, + @JsonProperty(value = "type", required = true) String type, + @JsonProperty(value = "name", required = true) String name, + @JsonProperty(value = "attribute-id", required = true) String attributeId, + @JsonProperty(value = "attribute-value", required = true) Double attributeValue) { + super(abbr, type, name); + this.attributeId = attributeId; + this.attributeValue = attributeValue; + } + + public Builder withAttributeUnits(String constituentAttributeUnits) { + this.attributeUnits = constituentAttributeUnits; + return this; + } + + @Override + public LocationLevelConstituent build() { + return new LocationLevelConstituent(this); + } + } + } +} diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LevelControllerTest.java b/cwms-data-api/src/test/java/cwms/cda/api/LevelControllerTest.java index 5f02ac28c..acdc5511f 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/LevelControllerTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/LevelControllerTest.java @@ -1,8 +1,10 @@ package cwms.cda.api; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalLocationLevel; +import cwms.cda.data.dto.locationlevel.TimeSeriesLocationLevel; import org.junit.jupiter.api.Test; -import cwms.cda.data.dto.LocationLevel; import cwms.cda.formatters.Formats; import cwms.cda.formatters.xml.adapters.ZonedDateTimeAdapter; @@ -13,12 +15,11 @@ class LevelControllerTest extends ControllerTest { private static final String OFFICE_ID = "LRL"; @Test - void testDeserializeSeasonalLevelXml() throws Exception - { + void testDeserializeSeasonalLevelXml() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String xml = loadResourceAsString("cwms/cda/api/levels_seasonal_create.xml"); assertNotNull(xml); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, LocationLevel.class), xml, LocationLevel.class); + SeasonalLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, SeasonalLocationLevel.class), xml, SeasonalLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); @@ -28,12 +29,11 @@ void testDeserializeSeasonalLevelXml() throws Exception } @Test - void testDeserializeSeasonalLevelJSON() throws Exception - { + void testDeserializeSeasonalLevelJSON() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String json = loadResourceAsString("cwms/cda/api/levels_seasonal_create.json"); assertNotNull(json); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, LocationLevel.class), json, LocationLevel.class); + SeasonalLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, SeasonalLocationLevel.class), json, SeasonalLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); @@ -43,12 +43,11 @@ void testDeserializeSeasonalLevelJSON() throws Exception } @Test - void testDeserializeConstantLevelXml() throws Exception - { + void testDeserializeConstantLevelXml() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String xml = loadResourceAsString("cwms/cda/api/levels_constant_create.xml"); assertNotNull(xml); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, LocationLevel.class), xml, LocationLevel.class); + ConstantLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, ConstantLocationLevel.class), xml, ConstantLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); @@ -58,12 +57,11 @@ void testDeserializeConstantLevelXml() throws Exception } @Test - void testDeserializeConstantLevelJSON() throws Exception - { + void testDeserializeConstantLevelJSON() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String json = loadResourceAsString("cwms/cda/api/levels_constant_create.json"); assertNotNull(json); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, LocationLevel.class), json, LocationLevel.class); + ConstantLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, ConstantLocationLevel.class), json, ConstantLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); @@ -73,13 +71,12 @@ void testDeserializeConstantLevelJSON() throws Exception } @Test - void testDeserializeTimeSeriesLevelXml() throws Exception - { + void testDeserializeTimeSeriesLevelXml() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String xml = loadResourceAsString("cwms/cda/api/levels_timeseries_create.xml"); assertNotNull(xml); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, LocationLevel.class), - xml, LocationLevel.class); + TimeSeriesLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.XML, TimeSeriesLocationLevel.class), + xml, TimeSeriesLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); @@ -90,12 +87,11 @@ void testDeserializeTimeSeriesLevelXml() throws Exception } @Test - void testDeserializeTimeSeriesLevelJSON() throws Exception - { + void testDeserializeTimeSeriesLevelJSON() throws Exception { ZonedDateTimeAdapter dateTimeAdapter = new ZonedDateTimeAdapter(); String json = loadResourceAsString("cwms/cda/api/levels_timeseries_create.json"); assertNotNull(json); - LocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, LocationLevel.class), json, LocationLevel.class); + TimeSeriesLocationLevel level = Formats.parseContent(Formats.parseHeader(Formats.JSONV1, TimeSeriesLocationLevel.class), json, TimeSeriesLocationLevel.class); assertNotNull(level); assertEquals("LOC_TEST.Elev.Inst.0.Bottom of Inlet", level.getLocationLevelId()); assertEquals(OFFICE_ID, level.getOfficeId()); 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 9a4c821e6..54351d3bc 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 @@ -25,37 +25,55 @@ package cwms.cda.api; import cwms.cda.data.dao.LocationLevelsDaoImpl; -import cwms.cda.data.dto.LocationLevel; +import cwms.cda.data.dao.RatingDao; +import cwms.cda.data.dao.RatingSetDao; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; import cwms.cda.data.dto.TimeSeries; import cwms.cda.formatters.Formats; import fixtures.CwmsDataApiSetupCallback; import fixtures.TestAccounts; import io.restassured.filter.log.LogDetail; +import io.restassured.path.json.JsonPath; import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; +import mil.army.usace.hec.cwms.rating.io.xml.RatingContainerXmlFactory; +import mil.army.usace.hec.cwms.rating.io.xml.RatingSetContainerXmlFactory; +import mil.army.usace.hec.cwms.rating.io.xml.RatingSpecXmlFactory; import org.jooq.DSLContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.sql.SQLException; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; +import hec.data.RatingException; +import hec.data.cwmsRating.io.RatingSetContainer; +import hec.data.cwmsRating.io.RatingSpecContainer; + import static cwms.cda.api.Controllers.*; +import static cwms.cda.security.ApiKeyIdentityProvider.AUTH_HEADER; import static helpers.FloatCloseTo.floatCloseTo; 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; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; @Tag("integration") public class LevelsControllerTestIT extends DataApiTestIT { @@ -79,10 +97,10 @@ void test_location_level() throws Exception { createLocation("level_as_single_value", true, OFFICE); String levelId = "level_as_single_value.Stor.Ave.1Day.Regulating"; ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); - LocationLevel level = new LocationLevel.Builder(levelId, time) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("ac-ft") + .withConstantValue(1.0) .build(); levelList.add(level); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { @@ -137,10 +155,10 @@ void test_retrieve_effective_date() throws Exception { String levelId = "level_with_effect.Flow.Ave.1Day.Regulating"; 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) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("cms") + .withConstantValue(1.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -175,10 +193,10 @@ void test_retrieve_time_window() throws Exception { String levelId = "level_get_all_loc_1.Flow.Ave.1Day.Regulating"; 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) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("cms") + .withConstantValue(1.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -191,10 +209,10 @@ void test_retrieve_time_window() throws Exception { createLocation(locId2, true, OFFICE); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - LocationLevel level = new LocationLevel.Builder(levelId2, time) + LocationLevel level = new ConstantLocationLevel.Builder(levelId2, time) .withOfficeId(OFFICE) - .withConstantValue(2.0) .withLevelUnitsId("ac-ft") + .withConstantValue(2.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -283,10 +301,10 @@ void test_level_as_timeseries() throws Exception { int effectiveDateCount = 10; NavigableMap levels = new TreeMap<>(); for (int i = 0; i < effectiveDateCount; i++) { - LocationLevel level = new LocationLevel.Builder(levelId, time.plusDays(i)) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time.plusDays(i)) .withOfficeId(OFFICE) - .withConstantValue((double) i) .withLevelUnitsId("cfs") + .withConstantValue((double) i) .build(); levelList.add(level); levels.put(level.getLevelDate().toInstant(), level); @@ -330,8 +348,8 @@ void test_level_as_timeseries() throws Exception { 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() + Double constantValue = ((ConstantLocationLevel) levels.floorEntry(tsrec.getDateTime().toInstant()) + .getValue()) .getConstantValue(); assertEquals(constantValue, tsrec.getValue(), 0.0001, "Value check failed at iteration: " + i); } @@ -346,10 +364,10 @@ void test_get_all_location_level() throws Exception { 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) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("ac-ft") + .withConstantValue(1.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -362,10 +380,10 @@ void test_get_all_location_level() throws Exception { createLocation(locId2, true, OFFICE); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - LocationLevel level = new LocationLevel.Builder(levelId2, time) + LocationLevel level = new ConstantLocationLevel.Builder(levelId2, time) .withOfficeId(OFFICE) - .withConstantValue(2.0) .withLevelUnitsId("ac-ft") + .withConstantValue(2.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -507,10 +525,10 @@ void test_get_one_units() throws Exception { createLocation("level_as_single_value", true, OFFICE); String levelId = "level_as_single_value.Stor.Ave.1Day.Regulating"; ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); - LocationLevel level = new LocationLevel.Builder(levelId, time) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("ac-ft") + .withConstantValue(1.0) .build(); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { DSLContext dsl = dslContext(c, OFFICE); @@ -603,10 +621,10 @@ void test_get_all_earliest_time() throws Exception { 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) + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) .withOfficeId(OFFICE) - .withConstantValue(1.0) .withLevelUnitsId("ac-ft") + .withConstantValue(1.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -619,10 +637,10 @@ void test_get_all_earliest_time() throws Exception { createLocation(locId2, true, OFFICE); CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { - LocationLevel level = new LocationLevel.Builder(levelId2, time) + LocationLevel level = new ConstantLocationLevel.Builder(levelId2, time) .withOfficeId(OFFICE) - .withConstantValue(2.0) .withLevelUnitsId("ac-ft") + .withConstantValue(2.0) .build(); levelList.add(level); DSLContext dsl = dslContext(c, OFFICE); @@ -715,8 +733,7 @@ void test_get_all_earliest_time() throws Exception { } @Test - void testRetrievalInvalidLevelName() - { + void testRetrievalInvalidLevelName() { given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSONV2) @@ -733,10 +750,672 @@ void testRetrievalInvalidLevelName() .statusCode(is(HttpServletResponse.SC_BAD_REQUEST)); } + @Test + void testStoreRetrieveVirtualLocationLevels() throws Exception { + // Virtual levels do not include constant or seasonal values + + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + String existingLoc = "LevelsControllerTestIT"; + String virtualLoc = "level_get_all_loc1"; + String levelIdPart2 = ".Stor.Ave.1Day.Regulating"; + String existingSpec = existingLoc + ".Stage;Flow.COE.Production"; + createLocation("virtual_level_value", true, OFFICE); + createLocation(virtualLoc, true, OFFICE); + createLocation(existingLoc, true, OFFICE); + String levelId = "virtual_level_value.Stage.Ave.1Day.Regulating"; + ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); + String levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_1.json"); + String specXml = createRatingSpec(existingLoc); + String setXml = createRatingSet(existingLoc); + String levelIdLocal = virtualLoc + levelIdPart2; + createVirtualLocation(specXml, setXml, time, levelIdLocal, null); + + // Store the virtual level + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Read level with unit + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .queryParam(UNIT, "ft") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("ft")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .queryParam(UNIT, "EN") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("ft")); + + // Read virtual level + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .queryParam(UNIT, "ft3") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("ft3")) + .body("constituents", notNullValue()) + .extract(); + + assertThat(response.path("constituents.size()"), is(2)); + assertThat(response.path("constituents[0].name"), equalTo(virtualLoc + ".Stage.Ave.1Day.Regulating")); + assertThat(response.path("constituents[0].type"), equalTo("LOCATION_LEVEL")); + assertThat(response.path("constituents[0].attribute-id"), equalTo("Stage")); + assertThat(response.path("constituents[1].name"), equalTo(existingSpec)); + assertThat(response.path("constituents[1].type"), equalTo("RATING")); + } + + @Test + void testStoreRetrieveAllVirtualLocationLevels() throws Exception { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + String existingLoc = "LevelsControllerTestIT"; + String levelLoc1 = "level_get_all_loc1"; + String levelLoc2 = "level_get_all_loc2"; + String levelLoc3 = "level_get_all_loc3"; + String virtualLoc = "virtual_level_value"; + String virtualLoc1 = "virtual_level_value_1"; + String virtualLoc2 = "virtual_level_value_2"; + String levelIdStor = ".Stor.Ave.1Day.Regulating"; + String levelIdStage = ".Stage.Ave.1Day.Regulating"; + createLocation(virtualLoc, true, OFFICE); + createLocation(virtualLoc2, true, OFFICE); + createLocation(virtualLoc1, true, OFFICE); + createLocation(levelLoc1, true, OFFICE); + createLocation(levelLoc2, true, OFFICE); + createLocation(levelLoc3, true, OFFICE); + createLocation(existingLoc, true, OFFICE); + String levelId = virtualLoc + levelIdStage; + String level1Id = virtualLoc1 + levelIdStor; + String level2Id = virtualLoc2 + levelIdStor; + ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); + String specXml = createRatingSpec(existingLoc); + String setXml = createRatingSet(existingLoc); + String levelIdLocal = levelLoc1 + levelIdStor; + String levelId2Local = levelLoc2 + levelIdStor; + createVirtualLocation(specXml, setXml, time, levelIdLocal, levelId2Local); + + String levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_1.json"); + // Store the virtual level + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_2.json"); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_3.json"); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Read level with unit + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", level1Id) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m3")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", level2Id) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m3")); + + // retrieve all virtual levels + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(BEGIN, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + JsonPath path = JsonPath.from(response.body().asInputStream()); + List> levels = path.getList("levels"); + boolean foundLevel1 = false; + boolean foundLevel2 = false; + boolean foundLevel3 = false; + boolean foundNormalLevel1 = false; + boolean foundNormalLevel2 = false; + for (Map item : levels) { + if (item.get("location-level-id").equals(levelId)) { + foundLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level1Id)) { + foundLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level2Id)) { + foundLevel3 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelIdLocal)) { + foundNormalLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelId2Local)) { + foundNormalLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } + } + + assertTrue(foundLevel1, "Did not find levelId: " + levelId); + assertTrue(foundLevel2, "Did not find levelId: " + level1Id); + assertTrue(foundLevel3, "Did not find levelId: " + level2Id); + assertTrue(foundNormalLevel1, "Did not find levelId: " + levelIdLocal); + assertTrue(foundNormalLevel2, "Did not find levelId: " + levelId2Local); + } + + @Test + void testStoreRetrieveAllVirtualLocationLevelsPaged() throws Exception { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + String existingLoc = "LevelsControllerTestIT"; + String levelLoc1 = "level_get_all_loc1"; + String levelLoc2 = "level_get_all_loc2"; + String levelLoc3 = "level_get_all_loc3"; + String virtualLoc = "virtual_level_value"; + String virtualLoc1 = "virtual_level_value_1"; + String virtualLoc2 = "virtual_level_value_2"; + String levelIdStor = ".Stor.Ave.1Day.Regulating"; + String levelIdStage = ".Stage.Ave.1Day.Regulating"; + createLocation(virtualLoc, true, OFFICE); + createLocation(virtualLoc2, true, OFFICE); + createLocation(virtualLoc1, true, OFFICE); + createLocation(levelLoc1, true, OFFICE); + createLocation(levelLoc2, true, OFFICE); + createLocation(levelLoc3, true, OFFICE); + createLocation(existingLoc, true, OFFICE); + String levelId = virtualLoc + levelIdStage; + String level1Id = virtualLoc1 + levelIdStor; + String level2Id = virtualLoc2 + levelIdStor; + ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); + String specXml = createRatingSpec(existingLoc); + String setXml = createRatingSet(existingLoc); + String levelIdLocal = levelLoc1 + levelIdStor; + String levelId2Local = levelLoc2 + levelIdStor; + createVirtualLocation(specXml, setXml, time, levelIdLocal, levelId2Local); + + String levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_1.json"); + // Store the virtual level + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_2.json"); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_3.json"); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Read level with unit + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", level1Id) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m3")); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", level2Id) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m3")); + + // retrieve levels with page size of 3 + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(BEGIN, time.toInstant().toString()) + .queryParam(PAGE_SIZE, 3) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + JsonPath path = JsonPath.from(response.body().asInputStream()); + List> levels = path.getList("levels"); + assertEquals(3, levels.size()); + boolean foundLevel1 = false; + boolean foundLevel2 = false; + boolean foundLevel3 = false; + boolean foundNormalLevel1 = false; + boolean foundNormalLevel2 = false; + for (Map item : levels) { + if (item.get("location-level-id").equals(levelId)) { + foundLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level1Id)) { + foundLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level2Id)) { + foundLevel3 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelIdLocal)) { + foundNormalLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelId2Local)) { + foundNormalLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } + } + + String page = path.getString("next-page"); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(BEGIN, time.toInstant().toString()) + .queryParam(PAGE, page) + .when() + .redirects().follow(true) + .redirects().max(2) + .get("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract(); + + path = JsonPath.from(response.body().asInputStream()); + levels = path.getList("levels"); + assertTrue(levels.size() >= 2 && levels.size() <= 3); + for (Map item : levels) { + if (item.get("location-level-id").equals(levelId)) { + foundLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level1Id)) { + foundLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(level2Id)) { + foundLevel3 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelIdLocal)) { + foundNormalLevel1 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } else if (item.get("location-level-id").equals(levelId2Local)) { + foundNormalLevel2 = true; + assertThat(item.get("office-id"), equalTo(user.getOperatingOffice())); + } + } + + assertTrue(foundLevel1, "Did not find levelId: " + levelId); + assertTrue(foundLevel2, "Did not find levelId: " + level1Id); + assertTrue(foundLevel3, "Did not find levelId: " + level2Id); + assertTrue(foundNormalLevel1, "Did not find levelId: " + levelIdLocal); + assertTrue(foundNormalLevel2, "Did not find levelId: " + levelId2Local); + } + + @ParameterizedTest + @ValueSource(strings = {"both", "no_date"}) + void testStoreDeleteVirtualLocationLevel(String deletionMethod) throws Exception { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + String existingLoc = "LevelsControllerTestIT"; + String virtualLoc = "level_get_all_loc1"; + String levelIdPart2 = ".Stor.Ave.1Day.Regulating"; + String existingSpec = existingLoc + ".Stage;Flow.COE.Production"; + createLocation("virtual_level_value", true, OFFICE); + createLocation(virtualLoc, true, OFFICE); + createLocation(existingLoc, true, OFFICE); + String levelId = "virtual_level_value.Stage.Ave.1Day.Regulating"; + ZonedDateTime time = ZonedDateTime.of(2023, 6, 1, 0, 0, 0, 0, ZoneId.of("America/Los_Angeles")); + String levelJson = readResourceFile("cwms/cda/api/virtuallevels/virtual_level_1.json"); + String specXml = createRatingSpec(existingLoc); + String setXml = createRatingSet(existingLoc); + String levelIdLocal = virtualLoc + levelIdPart2; + createVirtualLocation(specXml, setXml, time, levelIdLocal, null); + + // Store the virtual level + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .body(levelJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/levels/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + + //Read level with unit + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("m")); + + // Read virtual level + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam("office", OFFICE) + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .queryParam(UNIT, "ft3") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("level-units-id", equalTo("ft3")) + .body("constituents", notNullValue()) + .extract(); + + assertThat(response.path("constituents.size()"), is(2)); + assertThat(response.path("constituents[0].name"), equalTo(virtualLoc + ".Stage.Ave.1Day.Regulating")); + assertThat(response.path("constituents[0].type"), equalTo("LOCATION_LEVEL")); + assertThat(response.path("constituents[0].attribute-id"), equalTo("Stage")); + assertThat(response.path("constituents[1].name"), equalTo(existingSpec)); + assertThat(response.path("constituents[1].type"), equalTo("RATING")); + + // Delete the level + switch (deletionMethod) { + case "both": + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .queryParam(Controllers.OFFICE, OFFICE) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + break; + case "no_date": + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .header(AUTH_HEADER, user.toHeaderValue()) + .queryParam(Controllers.OFFICE, OFFICE) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)); + break; + default: + fail("Invalid deletion method: " + deletionMethod); + } + + // Read and assert that the level is deleted + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSONV2) + .contentType(Formats.JSONV2) + .queryParam(Controllers.OFFICE, OFFICE) + .queryParam(UNIT, "SI") + .queryParam(EFFECTIVE_DATE, time.toInstant().toString()) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/levels/{level-id}", levelId) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)); + } + @ParameterizedTest @EnumSource(GetAllTestNewAliases.class) - void test_get_all_aliases_new(GetAllTestNewAliases test) - { + void test_get_all_aliases_new(GetAllTestNewAliases test) { given() .log().ifValidationFails(LogDetail.ALL, true) .accept(test._accept) @@ -755,8 +1434,7 @@ void test_get_all_aliases_new(GetAllTestNewAliases test) @ParameterizedTest @EnumSource(GetAllTestLegacy.class) - void test_get_all_aliases_legacy(GetAllTestLegacy test) - { + void test_get_all_aliases_legacy(GetAllTestLegacy test) { given() .log().ifValidationFails(LogDetail.ALL, true) .queryParam(FORMAT, test._format) @@ -775,8 +1453,7 @@ void test_get_all_aliases_legacy(GetAllTestLegacy test) .contentType(is(test._expectedContentType)); } - enum GetAllTestLegacy - { + enum GetAllTestLegacy { JSON(Formats.JSON_LEGACY, Formats.JSON), XML(Formats.XML_LEGACY, Formats.XML), TAB(Formats.TAB_LEGACY, Formats.TAB), @@ -792,8 +1469,7 @@ enum GetAllTestLegacy } } - enum GetAllTestNewAliases - { + enum GetAllTestNewAliases { DEFAULT(Formats.DEFAULT, Formats.JSONV2), JSON(Formats.JSON, Formats.JSONV2), JSONV1(Formats.JSONV1, Formats.JSONV1), @@ -802,10 +1478,62 @@ enum GetAllTestNewAliases final String _accept; final String _expectedContentType; - GetAllTestNewAliases(String accept, String expectedContentType) - { + GetAllTestNewAliases(String accept, String expectedContentType) { _accept = accept; _expectedContentType = expectedContentType; } } + + private String createRatingSpec(String locationName) throws Exception { + String ratingXml = readResourceFile("cwms/cda/api/Zanesville_Stage_Flow_COE_Production.xml"); + ratingXml = ratingXml.replaceAll("Zanesville", locationName); + RatingSetContainer container = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml); + RatingSpecContainer specContainer = container.ratingSpecContainer; + + return RatingSpecXmlFactory.toXml(specContainer, "", 0, true); + } + + private String createRatingSet(String locationName) throws Exception { + String ratingXml = readResourceFile("cwms/cda/api/Zanesville_Stage_Flow_COE_Production.xml"); + ratingXml = ratingXml.replaceAll("Zanesville", locationName); + RatingSetContainer container = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml); + + return RatingContainerXmlFactory.toXml(container, "", 0, true, false); + } + + private void createVirtualLocation(String specXml, String setXml, ZonedDateTime time, String levelId, String levelId2) throws SQLException { + CwmsDataApiSetupCallback.getDatabaseLink().connection(c -> { + DSLContext dsl = dslContext(c, OFFICE); + RatingDao ratingDao = new RatingSetDao(dsl); + try { + ratingDao.create(specXml, false); + } catch (RatingException | IOException e) { + throw new RuntimeException(e); + } + + RatingSetDao ratingSetDao = new RatingSetDao(dsl); + try { + ratingSetDao.create(setXml, false); + } catch (RatingException | IOException e) { + throw new RuntimeException(e); + } + LocationLevel level = new ConstantLocationLevel.Builder(levelId, time) + .withOfficeId(OFFICE) + .withLevelUnitsId("ac-ft") + .withConstantValue(1.0) + .build(); + levelList.add(level); + LocationLevelsDaoImpl dao = new LocationLevelsDaoImpl(dsl); + dao.storeLocationLevel(level); + if (levelId2 != null) { + level = new ConstantLocationLevel.Builder(levelId2, time) + .withOfficeId(OFFICE) + .withLevelUnitsId("ac-ft") + .withConstantValue(10.0) + .build(); + levelList.add(level); + dao.storeLocationLevel(level); + } + }); + } } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java b/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java index 364412ecb..5fb9cf90a 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/LocationControllerTest.java @@ -1,6 +1,5 @@ package cwms.cda.api; -import java.io.IOException; import java.util.HashMap; import javax.servlet.http.HttpServletRequest; @@ -10,7 +9,6 @@ import cwms.cda.api.enums.Nation; import cwms.cda.data.dto.Location; -import cwms.cda.data.dto.LocationLevel; import cwms.cda.formatters.Formats; import fixtures.TestHttpServletResponse; import fixtures.TestServletInputStream; @@ -21,7 +19,6 @@ import io.javalin.plugin.json.JsonMapperKt; import org.junit.jupiter.api.Test; -import static cwms.cda.api.DataApiTestIT.getResourceTemplateStatic; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; @@ -29,10 +26,8 @@ class LocationControllerTest extends ControllerTest { - private static final String OFFICE_ID = "LRL"; - @Test - void testDeserializeLocationXml() throws IOException + void testDeserializeLocationXml() { String xml = loadResourceAsString("cwms/cda/api/location_create.xml"); assertNotNull(xml); @@ -47,7 +42,7 @@ void testDeserializeLocationXml() throws IOException } @Test - void testDeserializeLocationJSON() throws Exception + void testDeserializeLocationJSON() { final String OFFICE = "SPK"; final String json = loadResourceAsString("cwms/cda/api/location_create_spk.json"); @@ -67,7 +62,7 @@ void testDeserializeLocationJSON() throws Exception * Test of getOne method, of class LocationController. */ @Test - public void test_basic_operations() throws Exception { + void test_basic_operations() throws Exception { final String testBody = ""; LocationController instance = new LocationController(new MetricRegistry()); HttpServletRequest request = mock(HttpServletRequest.class); @@ -78,19 +73,16 @@ public void test_basic_operations() throws Exception { when(request.getInputStream()).thenReturn(new TestServletInputStream(testBody)); - final Context context = ContextUtil.init(request,response,"*",new HashMap(), HandlerType.GET,attributes); + final Context context = ContextUtil.init(request,response,"*", new HashMap<>(), HandlerType.GET,attributes); context.attribute("database",getTestConnection()); when(request.getAttribute("database")).thenReturn(getTestConnection()); assertNotNull( context.attribute("database"), "could not get the connection back as an attribute"); - String Location_id = "SimpleNoAlias"; + String locationId = "SimpleNoAlias"; - instance.getOne(context, Location_id); + instance.getOne(context, locationId); assertEquals(200,context.status(), "incorrect status code returned"); - - } - } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/LockControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/LockControllerIT.java index 3bf44c592..8149443db 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/LockControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/LockControllerIT.java @@ -45,7 +45,8 @@ import cwms.cda.data.dao.location.kind.LockDao; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; -import cwms.cda.data.dto.LocationLevel; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; import cwms.cda.data.dto.location.kind.Lock; import cwms.cda.data.dto.location.kind.LockLocationLevelRef; import cwms.cda.formatters.ContentType; @@ -139,7 +140,7 @@ final class LockControllerIT extends DataApiTestIT { } @BeforeAll - public static void setup() throws Exception { + static void setup() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { try { @@ -160,7 +161,7 @@ public static void setup() throws Exception { } @AfterAll - public static void tearDown() throws Exception { + static void tearDown() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { @@ -1010,39 +1011,40 @@ private static PROJECT_OBJ_T buildProject(Location projectLocation) { private List createLocationLevelList(Lock lock) { List retVal = new ArrayList<>(); - LocationLevel lowLowerLevel = new LocationLevel.Builder(lock.getLowWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel lowLowerLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getLowWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getLowWaterLowerPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getLowWaterLowerPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getLowWaterLowerPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getLowWaterLowerPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getLowWaterLowerPoolLocationLevel().getLevelValue()) .build(); retVal.add(lowLowerLevel); - LocationLevel lowUpperLevel = new LocationLevel.Builder(lock.getLowWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel lowUpperLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getLowWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getLowWaterUpperPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getLowWaterUpperPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getLowWaterUpperPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getLowWaterUpperPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getLowWaterUpperPoolLocationLevel().getLevelValue()) .build(); retVal.add(lowUpperLevel); - LocationLevel highLowerLevel = new LocationLevel.Builder(lock.getHighWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel highLowerLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getHighWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterLowerPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getHighWaterLowerPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getHighWaterLowerPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getHighWaterLowerPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getHighWaterLowerPoolLocationLevel().getLevelValue()) .build(); retVal.add(highLowerLevel); - LocationLevel highUpperLevel = new LocationLevel.Builder(lock.getHighWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel highUpperLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getHighWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterUpperPoolLocationLevel().getLevelValue()) + .withOfficeId(lock.getHighWaterUpperPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getHighWaterUpperPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getHighWaterUpperPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getHighWaterUpperPoolLocationLevel().getLevelValue()) .build(); retVal.add(highUpperLevel); - LocationLevel warningBuffer = new LocationLevel.Builder(String.format("%s.Elev-Closure.Inst.0.Warning Buffer", lock.getLocation().getName()), ZonedDateTime.now()) + ConstantLocationLevel warningBuffer = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(String.format("%s.Elev-Closure.Inst.0.Warning Buffer", lock.getLocation().getName()), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterLowerPoolWarningLevel()) .withOfficeId(lock.getLocation().getOfficeId()) - .withSpecifiedLevelId("Warning Buffer") + .withSpecifiedLevelId("Warning Buffer")) + .withConstantValue(lock.getHighWaterLowerPoolWarningLevel()) .build(); retVal.add(warningBuffer); return retVal; diff --git a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java index 4a456fbaa..377339360 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java @@ -68,7 +68,7 @@ static void beforeAll() throws Exception String ratingXml = readResourceFile("cwms/cda/api/Zanesville_Stage_Flow_COE_Production.xml"); ratingXml = ratingXml.replaceAll("Zanesville", EXISTING_LOC); String ratingXml2 = ratingXml.replaceAll("2002-04-09T13:53:01Z", "2016-06-06T00:00:00Z"); - String ratingXml3 = ratingXml.replaceAll("2002-04-09T13:53:01Z", "2025-06-06T00:00:00Z"); + String ratingXml3 = ratingXml.replaceAll("2002-04-09T13:53:01Z", "2085-06-06T00:00:00Z"); RatingSetContainer container = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml); RatingSetContainer container2 = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml2); RatingSetContainer container3 = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml3); @@ -267,7 +267,7 @@ void test_get_one_latest() { effectiveDate = response.path("simple-rating.effective-date"); } assertNotNull(effectiveDate); - assertEquals("2025-06-06T00:00:00Z", effectiveDate); + assertEquals("2016-06-06T00:00:00Z", effectiveDate); // get latest xml response = given() @@ -290,7 +290,7 @@ void test_get_one_latest() { effectiveDate = response.path("simple-rating.effective-date"); } assertNotNull(effectiveDate); - assertEquals("2025-06-06T00:00:00Z", effectiveDate); + assertEquals("2016-06-06T00:00:00Z", effectiveDate); } enum GetOneTest diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/LocationLevelsDaoTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/LocationLevelsDaoTest.java index d06b1cadd..10c8b99e2 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/LocationLevelsDaoTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/LocationLevelsDaoTest.java @@ -35,13 +35,13 @@ import cwms.cda.api.enums.Unit; import cwms.cda.api.enums.UnitSystem; import cwms.cda.data.dto.Location; -import cwms.cda.data.dto.LocationLevel; -import cwms.cda.data.dto.SeasonalValueBean; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalLocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalValueBean; import cwms.cda.formatters.Formats; import cwms.cda.formatters.xml.adapters.ZonedDateTimeAdapter; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; import java.io.IOException; import java.math.BigDecimal; @@ -101,8 +101,8 @@ void testDeleteLocationLevel() throws Exception @Disabled void testUpdate() throws Exception { - LocationLevel updatedLocationLevel = null; - LocationLevel existingLocationLevel = null; + SeasonalLocationLevel updatedLocationLevel = null; + SeasonalLocationLevel existingLocationLevel = null; LocationLevel levelToStore = buildExampleLevel("TEST_LOC6"); LocationsDao locationsDao = new LocationsDaoImpl(getDslContext(getConnection(), OFFICE_ID)); LocationLevelsDao levelsDao = new LocationLevelsDaoImpl(getDslContext(getConnection(), OFFICE_ID)); @@ -113,12 +113,13 @@ void testUpdate() throws Exception String body = getRenamedExampleJSON(); String format = Formats.JSON; - LocationLevel levelFromBody = deserializeLocationLevel(body, Formats.JSON, OFFICE_ID); - existingLocationLevel = levelsDao.retrieveLocationLevel(levelToStore.getLocationLevelId(), UnitSystem.EN.getValue(), levelFromBody.getLevelDate(), OFFICE_ID); + SeasonalLocationLevel levelFromBody = deserializeLocationLevel(body, Formats.JSON, OFFICE_ID); + existingLocationLevel = (SeasonalLocationLevel) levelsDao.retrieveLocationLevel(levelToStore.getLocationLevelId(), UnitSystem.EN.getValue(), levelFromBody.getLevelDate(), OFFICE_ID); existingLocationLevel = updatedClearedFields(body, format, existingLocationLevel); //only store (update) if level does exist updatedLocationLevel = getUpdatedLocationLevel(existingLocationLevel, levelFromBody); - updatedLocationLevel = new LocationLevel.Builder(updatedLocationLevel).withLevelDate(levelFromBody.getLevelDate()).build(); + updatedLocationLevel = ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder(updatedLocationLevel) + .withLevelDate(levelFromBody.getLevelDate())).build(); if (!updatedLocationLevel.getLocationLevelId().equalsIgnoreCase(existingLocationLevel.getLocationLevelId())) //if name changed then delete location with old name { levelsDao.renameLocationLevel(levelToStore.getLocationLevelId(), updatedLocationLevel.getLocationLevelId(), OFFICE_ID); @@ -136,14 +137,14 @@ void testUpdate() throws Exception } } - private LocationLevel deserializeLocationLevel(String body, String format, String office) throws IOException + private SeasonalLocationLevel deserializeLocationLevel(String body, String format, String office) throws IOException { ObjectMapper om = getObjectMapperForFormat(format); - LocationLevel retVal; + SeasonalLocationLevel retVal; try { - retVal = om.readValue(body, LocationLevel.class); - retVal = new LocationLevel.Builder(retVal).withOfficeId(office).build(); + retVal = om.readValue(body, SeasonalLocationLevel.class); + retVal = ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder(retVal).withOfficeId(office)).build(); } catch(Exception e) { @@ -206,21 +207,22 @@ private String getExampleUpdatedJSON() "}"; } - private LocationLevel updatedClearedFields(String body, String format, LocationLevel existingLocation) throws IOException + private SeasonalLocationLevel updatedClearedFields(String body, String format, SeasonalLocationLevel existingLocation) throws IOException { ObjectMapper om = getObjectMapperForFormat(format); JsonNode root = om.readTree(body); - JavaType javaType = om.getTypeFactory().constructType(LocationLevel.class); + JavaType javaType = om.getTypeFactory().constructType(SeasonalLocationLevel.class); BeanDescription beanDescription = om.getSerializationConfig().introspect(javaType); List properties = beanDescription.findProperties(); - LocationLevel retVal = new LocationLevel.Builder(existingLocation).build(); + SeasonalLocationLevel retVal = new SeasonalLocationLevel.Builder(existingLocation).build(); try { for (BeanPropertyDefinition propertyDefinition : properties) { String propertyName = propertyDefinition.getName(); JsonNode propertyValue = root.findValue(propertyName); if (propertyValue != null && "".equals(propertyValue.textValue())) { - retVal = new LocationLevel.Builder(retVal).withProperty(propertyName, null).build(); + retVal = ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder(retVal) + .withProperty(propertyName, null)).build(); } } } @@ -231,64 +233,73 @@ private LocationLevel updatedClearedFields(String body, String format, LocationL return retVal; } - private LocationLevel getUpdatedLocationLevel(LocationLevel existinglocation, LocationLevel updatedLevel) + private SeasonalLocationLevel getUpdatedLocationLevel(SeasonalLocationLevel existinglocation, SeasonalLocationLevel updatedLevel) { - String seasonalTimeSeriesId = (updatedLevel.getSeasonalTimeSeriesId() == null ? existinglocation.getSeasonalTimeSeriesId() : updatedLevel.getSeasonalTimeSeriesId()); - List seasonalValues = (updatedLevel.getSeasonalValues() == null ? existinglocation.getSeasonalValues() : updatedLevel.getSeasonalValues()); - String specifiedLevelId = (updatedLevel.getSpecifiedLevelId() == null ? existinglocation.getSpecifiedLevelId() : updatedLevel.getSpecifiedLevelId()); - String parameterTypeId = (updatedLevel.getParameterTypeId() == null ? existinglocation.getParameterTypeId() : updatedLevel.getParameterTypeId()); - String parameterId = (updatedLevel.getParameterId() == null ? existinglocation.getParameterId() : updatedLevel.getParameterId()); - Double siParameterUnitsConstantValue = (updatedLevel.getConstantValue() == null ? existinglocation.getConstantValue() : updatedLevel.getConstantValue()); - String levelUnitsId = (updatedLevel.getLevelUnitsId() == null ? existinglocation.getLevelUnitsId() : updatedLevel.getLevelUnitsId()); - ZonedDateTime levelDate = (updatedLevel.getLevelDate() == null ? existinglocation.getLevelDate() : updatedLevel.getLevelDate()); - String levelComment = (updatedLevel.getLevelComment() == null ? existinglocation.getLevelComment() : updatedLevel.getLevelComment()); - ZonedDateTime intervalOrigin = (updatedLevel.getIntervalOrigin() == null ? existinglocation.getIntervalOrigin() : updatedLevel.getIntervalOrigin()); - Integer intervalMinutes = (updatedLevel.getIntervalMinutes() == null ? existinglocation.getIntervalMinutes() : updatedLevel.getIntervalMinutes()); - Integer intervalMonths = (updatedLevel.getIntervalMonths() == null ? existinglocation.getIntervalMonths() : updatedLevel.getIntervalMonths()); - String interpolateString = (updatedLevel.getInterpolateString() == null ? existinglocation.getInterpolateString() : updatedLevel.getInterpolateString()); - String durationId = (updatedLevel.getDurationId() == null ? existinglocation.getDurationId() : updatedLevel.getDurationId()); - BigDecimal attributeValue = (updatedLevel.getAttributeValue() == null ? existinglocation.getAttributeValue() : updatedLevel.getAttributeValue()); - String attributeUnitsId = (updatedLevel.getAttributeUnitsId() == null ? existinglocation.getAttributeUnitsId() : updatedLevel.getAttributeUnitsId()); - String attributeParameterTypeId = (updatedLevel.getAttributeParameterTypeId() == null ? existinglocation.getAttributeParameterTypeId() : updatedLevel.getAttributeParameterTypeId()); - String attributeParameterId = (updatedLevel.getAttributeParameterId() == null ? existinglocation.getAttributeParameterId() : updatedLevel.getAttributeParameterId()); - String attributeDurationId = (updatedLevel.getAttributeDurationId() == null ? existinglocation.getAttributeDurationId() : updatedLevel.getAttributeDurationId()); - String attributeComment = (updatedLevel.getAttributeComment() == null ? existinglocation.getAttributeComment() : updatedLevel.getAttributeComment()); - String locationId = (updatedLevel.getLocationLevelId() == null ? existinglocation.getLocationLevelId() : updatedLevel.getLocationLevelId()); - String officeId = (updatedLevel.getOfficeId() == null ? existinglocation.getOfficeId() : updatedLevel.getOfficeId()); - if(existinglocation.getIntervalMonths() != null && existinglocation.getIntervalMonths() > 0) - { - intervalMinutes = null; - } - else if(existinglocation.getIntervalMinutes() != null && existinglocation.getIntervalMinutes() > 0) - { - intervalMonths = null; - } - if(existinglocation.getAttributeValue() == null) - { + String specifiedLevelId = (updatedLevel.getSpecifiedLevelId() == null + ? existinglocation.getSpecifiedLevelId() : updatedLevel.getSpecifiedLevelId()); + String parameterTypeId = (updatedLevel.getParameterTypeId() == null + ? existinglocation.getParameterTypeId() : updatedLevel.getParameterTypeId()); + String parameterId = (updatedLevel.getParameterId() == null + ? existinglocation.getParameterId() : updatedLevel.getParameterId()); + + String levelUnitsId = (updatedLevel.getLevelUnitsId() == null + ? existinglocation.getLevelUnitsId() : updatedLevel.getLevelUnitsId()); + ZonedDateTime levelDate = (updatedLevel.getLevelDate() == null + ? existinglocation.getLevelDate() : updatedLevel.getLevelDate()); + String levelComment = (updatedLevel.getLevelComment() == null + ? existinglocation.getLevelComment() : updatedLevel.getLevelComment()); + + String durationId = (updatedLevel.getDurationId() == null + ? existinglocation.getDurationId() : updatedLevel.getDurationId()); + BigDecimal attributeValue = (updatedLevel.getAttributeValue() == null + ? existinglocation.getAttributeValue() : updatedLevel.getAttributeValue()); + String attributeUnitsId = (updatedLevel.getAttributeUnitsId() == null + ? existinglocation.getAttributeUnitsId() : updatedLevel.getAttributeUnitsId()); + String attributeParameterTypeId = (updatedLevel.getAttributeParameterTypeId() == null + ? existinglocation.getAttributeParameterTypeId() : + updatedLevel.getAttributeParameterTypeId()); + String attributeParameterId = (updatedLevel.getAttributeParameterId() == null + ? existinglocation.getAttributeParameterId() : updatedLevel.getAttributeParameterId()); + String attributeDurationId = (updatedLevel.getAttributeDurationId() == null + ? existinglocation.getAttributeDurationId() : updatedLevel.getAttributeDurationId()); + String attributeComment = (updatedLevel.getAttributeComment() == null + ? existinglocation.getAttributeComment() : updatedLevel.getAttributeComment()); + String locationId = (updatedLevel.getLocationLevelId() == null + ? existinglocation.getLocationLevelId() : updatedLevel.getLocationLevelId()); + String officeId = (updatedLevel.getOfficeId() == null + ? existinglocation.getOfficeId() : updatedLevel.getOfficeId()); + + if (existinglocation.getAttributeValue() == null) { attributeUnitsId = null; } - if(!existinglocation.getSeasonalValues().isEmpty()) - { - siParameterUnitsConstantValue = null; - seasonalTimeSeriesId = null; - } - else if(existinglocation.getSeasonalTimeSeriesId() != null && !existinglocation.getSeasonalTimeSeriesId().isEmpty()) - { - siParameterUnitsConstantValue = null; - seasonalValues = null; + + ZonedDateTime intervalOrigin = (updatedLevel.getIntervalOrigin() == null + ? existinglocation.getIntervalOrigin() : updatedLevel.getIntervalOrigin()); + Integer intervalMinutes = (updatedLevel.getIntervalMinutes() == null + ? existinglocation.getIntervalMinutes() : updatedLevel.getIntervalMinutes()); + Integer intervalMonths = (updatedLevel.getIntervalMonths() == null + ? existinglocation.getIntervalMonths() : updatedLevel.getIntervalMonths()); + String interpolateString = (updatedLevel.getInterpolateString() == null + ? existinglocation.getInterpolateString() : updatedLevel.getInterpolateString()); + + List seasonalValues = (updatedLevel.getSeasonalValues() == null + ? existinglocation.getSeasonalValues() : updatedLevel.getSeasonalValues()); + + if (existinglocation.getIntervalMonths() != null && existinglocation.getIntervalMonths() > 0) { + intervalMinutes = null; + } else if (existinglocation.getIntervalMinutes() != null + && existinglocation.getIntervalMinutes() > 0) { + intervalMonths = null; } - return new LocationLevel.Builder(locationId, levelDate) + + return ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder(locationId, levelDate) .withSeasonalValues(seasonalValues) - .withSeasonalTimeSeriesId(seasonalTimeSeriesId) .withSpecifiedLevelId(specifiedLevelId) .withParameterTypeId(parameterTypeId) .withParameterId(parameterId) - .withConstantValue(siParameterUnitsConstantValue) .withLevelUnitsId(levelUnitsId) .withLevelComment(levelComment) - .withIntervalOrigin(intervalOrigin) - .withIntervalMinutes(intervalMinutes) - .withIntervalMonths(intervalMonths) + .withOfficeId(officeId) .withInterpolateString(interpolateString) .withDurationId(durationId) .withAttributeValue(attributeValue) @@ -296,8 +307,10 @@ else if(existinglocation.getSeasonalTimeSeriesId() != null && !existinglocation. .withAttributeParameterTypeId(attributeParameterTypeId) .withAttributeParameterId(attributeParameterId) .withAttributeDurationId(attributeDurationId) - .withAttributeComment(attributeComment) - .withOfficeId(officeId) + .withAttributeComment(attributeComment)) + .withIntervalOrigin(intervalOrigin) + .withIntervalMinutes(intervalMinutes) + .withIntervalMonths(intervalMonths) .build(); } @@ -324,14 +337,13 @@ LocationLevel buildExampleLevel(String locationName) throws Exception seasonalValues.add(0, seasonalVal); ZonedDateTimeAdapter zonedDateTimeAdapter = new ZonedDateTimeAdapter(); ZonedDateTime unmarshalledDateTime = zonedDateTimeAdapter.unmarshal(dateString); - LocationLevel retval = new LocationLevel.Builder(locationName + ".Elev.Inst.0.Bottom of Inlet", unmarshalledDateTime) + return ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder(locationName + ".Elev.Inst.0.Bottom of Inlet", unmarshalledDateTime) .withOfficeId(OFFICE_ID) .withLevelComment("For testing") - .withLevelUnitsId(Unit.FEET.getValue()) + .withLevelUnitsId(Unit.FEET.getValue())) .withSeasonalValues(seasonalValues) .withIntervalMonths(1) .build(); - return retval; } private Location buildTestLocation(String name) { diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/LockDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/LockDaoIT.java index fee255738..7ed9c75c3 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/LockDaoIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/LockDaoIT.java @@ -36,7 +36,8 @@ import cwms.cda.data.dao.LocationsDaoImpl; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; -import cwms.cda.data.dto.LocationLevel; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; import cwms.cda.data.dto.LookupType; import cwms.cda.data.dto.location.kind.Lock; import cwms.cda.data.dto.location.kind.LockLocationLevelRef; @@ -72,7 +73,7 @@ final class LockDaoIT extends ProjectStructureIT { private List locationLevelsToCleanup = new ArrayList<>(); @BeforeAll - public void setup() throws Exception { + void setup() throws Exception { setupProject(); CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { @@ -90,7 +91,7 @@ public void setup() throws Exception { } @AfterAll - public void tearDown() throws Exception { + void tearDown() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); @@ -330,39 +331,39 @@ private Lock storeLocLevelsAndBuildStorableLock(Lock lock) throws SQLException { private List createLocationLevelList(Lock lock) { List retVal = new ArrayList<>(); - LocationLevel lowLowerLevel = new LocationLevel.Builder(lock.getLowWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel lowLowerLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getLowWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getLowWaterLowerPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getLowWaterLowerPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getLowWaterLowerPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getLowWaterLowerPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getLowWaterLowerPoolLocationLevel().getLevelValue()) .build(); retVal.add(lowLowerLevel); - LocationLevel lowUpperLevel = new LocationLevel.Builder(lock.getLowWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel lowUpperLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getLowWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getLowWaterUpperPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getLowWaterUpperPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getLowWaterUpperPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getLowWaterUpperPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getLowWaterUpperPoolLocationLevel().getLevelValue()) .build(); retVal.add(lowUpperLevel); - LocationLevel highLowerLevel = new LocationLevel.Builder(lock.getHighWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel highLowerLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getHighWaterLowerPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterLowerPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getHighWaterLowerPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getHighWaterLowerPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getHighWaterLowerPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getHighWaterLowerPoolLocationLevel().getLevelValue()) .build(); retVal.add(highLowerLevel); - LocationLevel highUpperLevel = new LocationLevel.Builder(lock.getHighWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) + ConstantLocationLevel highUpperLevel = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(lock.getHighWaterUpperPoolLocationLevel().getLevelId(), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterUpperPoolLocationLevel().getLevelValue()) .withOfficeId(lock.getHighWaterUpperPoolLocationLevel().getOfficeId()) - .withSpecifiedLevelId(lock.getHighWaterUpperPoolLocationLevel().getSpecifiedLevelId()) + .withSpecifiedLevelId(lock.getHighWaterUpperPoolLocationLevel().getSpecifiedLevelId())) + .withConstantValue(lock.getHighWaterUpperPoolLocationLevel().getLevelValue()) .build(); retVal.add(highUpperLevel); - LocationLevel warningBuffer = new LocationLevel.Builder(String.format("%s.Elev-Closure.Inst.0.Warning Buffer", lock.getLocation().getName()), ZonedDateTime.now()) + ConstantLocationLevel warningBuffer = ((ConstantLocationLevel.Builder) new ConstantLocationLevel.Builder(String.format("%s.Elev-Closure.Inst.0.Warning Buffer", lock.getLocation().getName()), ZonedDateTime.now()) .withLevelUnitsId(lock.getElevationUnits()) - .withConstantValue(lock.getHighWaterLowerPoolWarningLevel()) .withOfficeId(lock.getLocation().getOfficeId()) - .withSpecifiedLevelId("Warning Buffer") + .withSpecifiedLevelId("Warning Buffer")) + .withConstantValue(lock.getHighWaterLowerPoolWarningLevel()) .build(); retVal.add(warningBuffer); return retVal; diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/LocationLevelTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/LocationLevelTest.java index 92e9d8555..d825eac2f 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/LocationLevelTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/LocationLevelTest.java @@ -1,94 +1,191 @@ package cwms.cda.data.dto; -import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.ArrayList; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import cwms.cda.api.enums.Nation; -import cwms.cda.api.errors.ExclusiveFieldsException; import cwms.cda.api.errors.RequiredFieldException; +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalLocationLevel; +import cwms.cda.data.dto.locationlevel.SeasonalValueBean; +import cwms.cda.data.dto.locationlevel.TimeSeriesLocationLevel; +import cwms.cda.data.dto.locationlevel.VirtualLocationLevel; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; import cwms.cda.formatters.json.JsonV2; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -class LocationLevelTest -{ +class LocationLevelTest { + @Test + void test_serialization_formats_TimeSeries() { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + String tsId = "Test.Elev.Ave.1Day.Regulating"; + final TimeSeriesLocationLevel level = new TimeSeriesLocationLevel.Builder("Test", zdt, tsId).build(); + + ContentType contentType = Formats.parseHeader(Formats.JSONV2, LocationLevel.class); + String jsonStr = Formats.format(contentType, level); + // If JSONv2 isn't setup correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} + assertTrue(jsonStr.contains("2021")); + } @Test - void test_exlusived_fields_monitored() - { - final LocationLevel noParameter = new LocationLevel.Builder("Test",ZonedDateTime.now()) - .withOfficeId("SPK") - .build(); - - assertThrows(RequiredFieldException.class, () -> { - noParameter.validate(); - }); + void test_serialization_formats_Constant() { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + final ConstantLocationLevel level = new ConstantLocationLevel.Builder("Test", zdt).build(); + ContentType contentType = Formats.parseHeader(Formats.JSONV2, LocationLevel.class); + String jsonStr = Formats.format(contentType, level); - final LocationLevel constAndSeasonalId = new LocationLevel.Builder("Test",ZonedDateTime.now()) - .withOfficeId("SPK") - .withConstantValue(5.0) - .withSeasonalTimeSeriesId("The test timeseries") - .build(); + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} - assertThrows(ExclusiveFieldsException.class, () -> { - constAndSeasonalId.validate(); - }); + assertTrue(jsonStr.contains("2021")); + } + @Test + void test_serialization_formats_Seasonal() { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + final SeasonalLocationLevel level = ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder("Test", zdt) + .withSeasonalValue(new SeasonalValueBean.Builder().withValue(34.9).build()) + .withIntervalMinutes(23) + .withOfficeId("SPK")) + .build(); - // we don't need actual values for this test, just the system to think there might be - final LocationLevel constAndValBean = new LocationLevel.Builder("Test",ZonedDateTime.now()) - .withOfficeId("SPK") - .withConstantValue(5.0) - .withSeasonalValues(new ArrayList<>()) - .build(); - assertThrows(ExclusiveFieldsException.class, () -> { - constAndSeasonalId.validate(); - }); + ContentType contentType = Formats.parseHeader(Formats.JSONV2, LocationLevel.class); + String jsonStr = Formats.format(contentType, level); + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} + assertTrue(jsonStr.contains("2021")); } @Test - void test_serialization_formats() - { + void test_serialization_formats_Virtual() { ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); - final LocationLevel level = new LocationLevel.Builder("Test", zdt).build(); + final VirtualLocationLevel level = new VirtualLocationLevel.Builder("Test", zdt).build(); ContentType contentType = Formats.parseHeader(Formats.JSONV2, LocationLevel.class); String jsonStr = Formats.format(contentType, level); - // If JSONv2 isn't setup correctly it will serialize the level like: -// {"location-level-id":"Test","level-date":1624287600.000000000} + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} + + assertTrue(jsonStr.contains("2021")); + } + + @Test + void test_serialization_om_TimeSeries() throws JsonProcessingException { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + String tsId = "Test.Elev.Ave.1Day.Regulating"; + final TimeSeriesLocationLevel level = new TimeSeriesLocationLevel.Builder("Test", zdt, tsId).build(); + + ObjectMapper om = JsonV2.buildObjectMapper(); + String jsonStr = om.writeValueAsString(level); + + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} + + assertTrue(jsonStr.contains("2021")); + } + + @Test + void test_serialization_om_Seasonal() throws JsonProcessingException { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + final SeasonalLocationLevel level = ((SeasonalLocationLevel.Builder) new SeasonalLocationLevel.Builder("Test", zdt) + .withSeasonalValue(new SeasonalValueBean.Builder().withValue(21.0).build()) + .withIntervalMonths(12) + .withOfficeId("SPK")) + .build(); + + ObjectMapper om = JsonV2.buildObjectMapper(); + String jsonStr = om.writeValueAsString(level); + + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} + + assertTrue(jsonStr.contains("2021")); + } + + @Test + void test_serialization_om_Constant() throws JsonProcessingException { + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + final ConstantLocationLevel level = new ConstantLocationLevel.Builder("Test", zdt).build(); + + ObjectMapper om = JsonV2.buildObjectMapper(); + String jsonStr = om.writeValueAsString(level); + + // If JSONv2 isn't annotated correctly it will serialize the level like: + // {"location-level-id":"Test","level-date":1624287600.000000000} assertTrue(jsonStr.contains("2021")); } @Test - void test_serialization_om() throws JsonProcessingException { + void test_serialization_om_Virtual() throws JsonProcessingException { ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); - final LocationLevel level = new LocationLevel.Builder("Test", zdt).build(); + final VirtualLocationLevel level = new VirtualLocationLevel.Builder("Test", zdt).build(); ObjectMapper om = JsonV2.buildObjectMapper(); String jsonStr = om.writeValueAsString(level); // If JSONv2 isn't annotated correctly it will serialize the level like: -// {"location-level-id":"Test","level-date":1624287600.000000000} + // {"location-level-id":"Test","level-date":1624287600.000000000} assertTrue(jsonStr.contains("2021")); } + @Test + void test_mutual_exclusivity_seasonal() { + assertThrows(RequiredFieldException.class, () -> new SeasonalLocationLevel.Builder("Test", ZonedDateTime.now()).build()); + assertThrows(RequiredFieldException.class, () -> new SeasonalLocationLevel.Builder("Test", ZonedDateTime.now()) + .withIntervalMinutes(25).withIntervalMonths(12).build()); + } + + @Test + void test_update_level() { + ConstantLocationLevel existingLevel = new ConstantLocationLevel.Builder("Test", ZonedDateTime.now()).withConstantValue(12345.65).build(); + + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + + ConstantLocationLevel updatedLevel = new ConstantLocationLevel.Builder("Test", ZonedDateTime.now()).withConstantValue(1899.45).build(); + + LocationLevel updated = LocationLevel.getUpdatedLocationLevel(existingLevel, updatedLevel, zdt); + assertNotNull(updated); + assertInstanceOf(ConstantLocationLevel.class, updated); + ConstantLocationLevel constantLevel = (ConstantLocationLevel) updated; + assertEquals(1899.45, constantLevel.getConstantValue()); + assertEquals(zdt, constantLevel.getLevelDate()); + } + + @Test + void test_update_level_virtual() { + VirtualLocationLevel existingLevel = new VirtualLocationLevel.Builder("Test", ZonedDateTime.now()).withConstituentConnections("L1=L2").build(); + + ZonedDateTime zdt = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); + + VirtualLocationLevel updatedLevel = new VirtualLocationLevel.Builder("Test", ZonedDateTime.now()).withConstituentConnections("L2=L3").build(); + + LocationLevel updated = LocationLevel.getUpdatedLocationLevel(existingLevel, updatedLevel, zdt); + + assertNotNull(updated); + assertInstanceOf(VirtualLocationLevel.class, updated); + VirtualLocationLevel virtualLevel = (VirtualLocationLevel) updated; + assertEquals("L2=L3", virtualLevel.getConstituentConnections()); + assertEquals(zdt, virtualLevel.getLevelDate()); + } } diff --git a/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java b/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java index 61698b1b1..a176d2ce0 100644 --- a/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java @@ -10,16 +10,14 @@ import cwms.cda.data.dto.Clobs; import cwms.cda.data.dto.County; import cwms.cda.data.dto.CwmsDTOBase; -import cwms.cda.data.dto.LocationLevels; +import cwms.cda.data.dto.locationlevel.LocationLevels; import cwms.cda.data.dto.Office; import cwms.cda.data.dto.State; import cwms.cda.data.dto.basinconnectivity.Basin; import cwms.cda.data.dto.project.LockRevokerRights; import cwms.cda.data.dto.project.Project; import cwms.cda.formatters.json.JsonV2; -import cwms.cda.formatters.xml.XMLv2; import cwms.cda.formatters.xml.XMLv2Office; -import io.swagger.v3.oas.annotations.Parameter; import java.util.Map; import org.junit.jupiter.api.Test; @@ -153,7 +151,7 @@ void testParseHeader() { @ParameterizedTest @EnumSource(ParseQueryOrParamTest.class) - void test_header_or_query_parm(ParseQueryOrParamTest test) throws Exception { + void test_header_or_query_parm(ParseQueryOrParamTest test) { ContentType ct = Formats.parseQueryOrHeaderParam(test.header, test.query, test.dto); System.out.println(ct.toString()); assertTrue(ContentType.equivalent(ct.toString(), test.type), "In correct content type returned."); diff --git a/cwms-data-api/src/test/java/cwms/cda/formatters/JsonV2Test.java b/cwms-data-api/src/test/java/cwms/cda/formatters/JsonV2Test.java index 498e7c040..54625be1e 100644 --- a/cwms-data-api/src/test/java/cwms/cda/formatters/JsonV2Test.java +++ b/cwms-data-api/src/test/java/cwms/cda/formatters/JsonV2Test.java @@ -4,10 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.ZonedDateTime; + +import cwms.cda.data.dto.locationlevel.ConstantLocationLevel; import org.junit.jupiter.api.Test; -import cwms.cda.data.dto.LocationLevel; -import cwms.cda.data.dto.LocationLevels; +import cwms.cda.data.dto.locationlevel.LocationLevel; +import cwms.cda.data.dto.locationlevel.LocationLevels; import cwms.cda.formatters.json.JsonV2; class JsonV2Test extends TimeSeriesTestBase { @@ -42,7 +44,7 @@ void canSerializeLocationLevel(){ private LocationLevel buildLevel(String crazyName) { ZonedDateTime efDate = ZonedDateTime.parse("2021-06-21T08:00:00-07:00[PST8PDT]"); - return new LocationLevel.Builder(crazyName, efDate).build(); + return new ConstantLocationLevel.Builder(crazyName, efDate).build(); } @Test diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_1.json b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_1.json new file mode 100644 index 000000000..1cddc2d4e --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_1.json @@ -0,0 +1,28 @@ +{ + "office-id" : "SPK", + "location-level-id" : "virtual_level_value.Stage.Ave.1Day.Regulating", + "specified-level-id" : "Total", + "parameter-id" : "Stage", + "attribute-value" : 12.5, + "attribute-units-id" : "ft", + "attribute-parameter-id" : "Stage", + "attribute-parameter-type-id" : "Inst", + "level-units-id" : "ft", + "level-date" : "2023-06-01T00:00:00-07:00", + "expiration-date" : 1685689200000, + "constituents" : [ + { + "type": "LOCATION_LEVEL", + "abbr": "L1", + "name": "level_get_all_loc1.Stage.Ave.1Day.Regulating", + "attribute-id": "Stage", + "attribute-value": 16.5 + }, + { + "type": "RATING", + "abbr": "R1", + "name": "LevelsControllerTestIT.Stage;Flow.COE.Production" + } + ], + "constituent-connections": "L1=R1I1" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_2.json b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_2.json new file mode 100644 index 000000000..ba202ea61 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_2.json @@ -0,0 +1,22 @@ +{ + "office-id" : "SPK", + "location-level-id" : "virtual_level_value_1.Stor.Ave.1Day.Regulating", + "level-units-id" : "ac-ft", + "level-date" : "2023-06-01T00:00:00-07:00", + "expiration-date" : 1685689200000, + "constituents" : [ + { + "type": "LOCATION_LEVEL", + "abbr": "L1", + "name": "level_get_all_loc2.Stage.Ave.1Day.Regulating", + "attribute-id": "Stage", + "attribute-value": 17.65 + }, + { + "type": "RATING", + "abbr": "R1", + "name": "LevelsControllerTestIT.Stage;Flow.COE.Production" + } + ], + "constituent-connections": "L1=R1I1" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_3.json b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_3.json new file mode 100644 index 000000000..6d20112d6 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/virtuallevels/virtual_level_3.json @@ -0,0 +1,22 @@ +{ + "office-id" : "SPK", + "location-level-id" : "virtual_level_value_2.Stor.Ave.1Day.Regulating", + "level-units-id" : "ac-ft", + "level-date" : "2023-06-01T00:00:00-07:00", + "expiration-date" : 1685689200000, + "constituents" : [ + { + "type": "LOCATION_LEVEL", + "abbr": "L1", + "name": "level_get_all_loc3.Stage.Ave.1Day.Regulating", + "attribute-id": "Stage", + "attribute-value": 8.99 + }, + { + "type": "RATING", + "abbr": "R1", + "name": "LevelsControllerTestIT.Stage;Flow.COE.Production" + } + ], + "constituent-connections": "L1=R1I1" +} \ No newline at end of file