From aafe874c690328d9810ba7b15ed74b62779c38f6 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:38:43 -0800 Subject: [PATCH] WIP changes. --- .../src/main/java/cwms/cda/ApiServlet.java | 4 + .../cda/api/TextTimeSeriesController.java | 236 ++++++++++ .../cwms/cda/data/dao/TextTimeSeriesDao.java | 406 ++++++++++++++++++ .../cwms/cda/data/dto/TextTimeSeries.java | 37 ++ .../RegularTextTimeSeriesRow.java | 127 ++++++ .../timeSeriesText/StandardTextCatalog.java | 125 ++++++ .../dto/timeSeriesText/StandardTextId.java | 105 +++++ .../StandardTextIdComparator.java | 56 +++ .../dto/timeSeriesText/StandardTextValue.java | 119 +++++ .../dto/timeSeriesText/TextTimeSeries.java | 30 ++ .../StandardTextCatalogTest.java | 80 ++++ .../StandardTextIdComparatorTest.java | 77 ++++ .../timeSeriesText/StandardTextIdTest.java | 40 ++ .../timeSeriesText/StandardTextValueTest.java | 46 ++ 14 files changed, 1488 insertions(+) create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesController.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/TextTimeSeriesDao.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/TextTimeSeries.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/RegularTextTimeSeriesRow.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalog.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextId.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparator.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextValue.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/TextTimeSeries.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalogTest.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparatorTest.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdTest.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextValueTest.java diff --git a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java index b9b1ed807..0d94780a8 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -50,6 +50,7 @@ import cwms.cda.api.RatingTemplateController; import cwms.cda.api.SpecifiedLevelController; import cwms.cda.api.StateController; +import cwms.cda.api.TextTimeSeriesController; import cwms.cda.api.TimeSeriesCategoryController; import cwms.cda.api.TimeSeriesController; import cwms.cda.api.TimeSeriesGroupController; @@ -130,6 +131,7 @@ "/auth/*", "/swagger-docs", "/timeseries/*", + "/text-timeseries/*", "/offices/*", "/states/*", "/counties/*", @@ -395,6 +397,8 @@ protected void configureRoutes() { new PoolController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache("/specified-levels/{specified-level-id}", new SpecifiedLevelController(metrics), requiredRoles,5, TimeUnit.MINUTES); + cdaCrudCache("/text-timeseries/{ts-id}", + new TextTimeSeriesController(metrics), requiredRoles,5, TimeUnit.MINUTES); } /** diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesController.java b/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesController.java new file mode 100644 index 000000000..c892154f6 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/TextTimeSeriesController.java @@ -0,0 +1,236 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.api; + +import static cwms.cda.api.Controllers.CREATE; +import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; +import static cwms.cda.api.Controllers.GET_ALL; +import static cwms.cda.api.Controllers.NOT_SUPPORTED_YET; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.STATUS_200; +import static cwms.cda.api.Controllers.TEMPLATE_ID_MASK; +import static cwms.cda.api.Controllers.UPDATE; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cwms.cda.api.errors.CdaError; +import cwms.cda.data.dao.TextTimeSeriesDao; +import cwms.cda.data.dto.TextTimeSeries; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.json.JsonV2; + +import io.javalin.apibuilder.CrudHandler; +import io.javalin.core.util.Header; +import io.javalin.http.Context; +import io.javalin.plugin.openapi.annotations.HttpMethod; +import io.javalin.plugin.openapi.annotations.OpenApi; +import io.javalin.plugin.openapi.annotations.OpenApiContent; +import io.javalin.plugin.openapi.annotations.OpenApiParam; +import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; +import io.javalin.plugin.openapi.annotations.OpenApiResponse; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; + + +public class TextTimeSeriesController implements CrudHandler { + private static final Logger logger = Logger.getLogger(TextTimeSeriesController.class.getName()); + private static final String TAG = "Text-TimeSeries"; + public static final String TS_ID = "ts-id"; + public static final String TEXT_TIMESERIES_ID = "text-timeseries-id"; + private final MetricRegistry metrics; + + + + public TextTimeSeriesController(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + } + + @NotNull + protected TextTimeSeriesDao getDao(DSLContext dsl) { + return new TextTimeSeriesDao(dsl); + } + + + private Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + + @OpenApi( + queryParams = { + @OpenApiParam(name = OFFICE, description = "Specifies the owning office of " + + "the Text TimeSeries whose data is to be included in the response." + + " If this field is not specified, matching rating information from " + + "all offices shall be returned."), + @OpenApiParam(name = TEMPLATE_ID_MASK, description = "Mask that specifies " + + "the IDs to be included in the response. If this field is not " + + "specified, all text timeseries shall be returned."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, + content = { + @OpenApiContent(type = Formats.JSONV2, from = TextTimeSeries.class) + } + )}, + tags = {TAG} + ) + @Override + public void getAll(Context ctx) { + String office = ctx.queryParam(OFFICE); + String templateIdMask = ctx.queryParam(TEMPLATE_ID_MASK); + + String formatHeader = ctx.header(Header.ACCEPT); + ContentType contentType = Formats.parseHeaderAndQueryParm(formatHeader, ""); + try (Timer.Context timeContext = markAndTime(GET_ALL); + DSLContext dsl = getDslContext(ctx)) { + TextTimeSeriesDao dao = getDao(dsl); + List textTimeSeries = null; // dao.get(office, templateIdMask); + + ctx.contentType(contentType.toString()); + + String result = Formats.format(contentType, textTimeSeries, TextTimeSeries.class); + ctx.result(result); + + ctx.status(HttpServletResponse.SC_OK); + } catch (Exception ex) { + CdaError re = + new CdaError("Failed to process request: " + ex.getLocalizedMessage()); + logger.log(Level.SEVERE, re.toString(), ex); + ctx.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).json(re); + } + + } + + @OpenApi(ignore = true) + @Override + public void getOne(Context ctx, String templateId) { + throw new UnsupportedOperationException(NOT_SUPPORTED_YET); //To change body of + // generated methods, choose Tools | Specs. + } + + @OpenApi( + description = "Create new TextTimeSeries", + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = TextTimeSeries.class, type = Formats.JSONV2) + }, + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided ID already exists. Default: true") + }, + method = HttpMethod.POST, + tags = {TAG} + ) + @Override + public void create(Context ctx) { + try (Timer.Context ignored = markAndTime(CREATE); + DSLContext dsl = getDslContext(ctx)) { + String reqContentType = ctx.req.getContentType(); + String formatHeader = reqContentType != null ? reqContentType : Formats.JSONV2; + String body = ctx.body(); + TextTimeSeries deserialize = deserialize(body, formatHeader); + TextTimeSeriesDao dao = getDao(dsl); + boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true); +// dao.create(deserialize, failIfExists); + ctx.status(HttpServletResponse.SC_CREATED); + } catch (JsonProcessingException ex) { + CdaError re = new CdaError("Failed to process create request"); + logger.log(Level.SEVERE, re.toString(), ex); + ctx.status(HttpServletResponse.SC_INTERNAL_SERVER_ERROR).json(re); + } + } + + @OpenApi( + description = "Renames the requested specified level id", + pathParams = { + @OpenApiParam(name = TS_ID, description = "The specified level id to be renamed"), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the " + + "owning office of the text timeseries to be renamed"), + @OpenApiParam(name = TEXT_TIMESERIES_ID, description = "The new text timeserie id.") + }, + method = HttpMethod.PATCH, + tags = {TAG} + ) + @Override + public void update(Context ctx, @NotNull String oldTextTimeSeriesId) { + try (Timer.Context ignored = markAndTime(UPDATE); + DSLContext dsl = getDslContext(ctx)) { + TextTimeSeriesDao dao = getDao(dsl); + String newTextTimeSeriesId = ctx.queryParam(TEXT_TIMESERIES_ID); + String office = ctx.queryParam(OFFICE); + // dao.update(oldTextTimeSeriesId, newTextTimeSeriesId, office); + ctx.status(HttpServletResponse.SC_NO_CONTENT); + } + } + + + + @OpenApi( + description = "Deletes requested text timeseries id", + pathParams = { + @OpenApiParam(name = TS_ID, description = "The text timeseries id to be deleted"), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the " + + "owning office of the timeseries identifier to be deleted"), + }, + method = HttpMethod.DELETE, + tags = {TAG} + ) + @Override + public void delete(Context ctx, String textTimeSeriesId) { + try (Timer.Context ignored = markAndTime(UPDATE); + DSLContext dsl = getDslContext(ctx)) { + TextTimeSeriesDao dao = getDao(dsl); + String office = ctx.queryParam(OFFICE); + // dao.delete(textTimeSeriesId, office); + ctx.status(HttpServletResponse.SC_NO_CONTENT); + } + } + + private static TextTimeSeries deserialize(String body, String format) throws JsonProcessingException { + TextTimeSeries retval; + if (ContentType.equivalent(Formats.JSONV2, format)) { + ObjectMapper om = JsonV2.buildObjectMapper(); + retval = om.readValue(body, TextTimeSeries.class); + } else { + throw new IllegalArgumentException("Unsupported format: " + format); + } + return retval; + } + +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/TextTimeSeriesDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/TextTimeSeriesDao.java new file mode 100644 index 000000000..d6bd44fd2 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/TextTimeSeriesDao.java @@ -0,0 +1,406 @@ +package cwms.cda.data.dao; + +import com.google.common.flogger.FluentLogger; +import cwms.cda.api.errors.NotFoundException; +import cwms.cda.data.dto.timeSeriesText.RegularTextTimeSeriesRow; +import cwms.cda.data.dto.timeSeriesText.StandardTextCatalog; +import cwms.cda.data.dto.timeSeriesText.StandardTextValue; +import hec.data.ITimeSeriesDescription; + + +import hec.data.timeSeriesText.StandardTextId; +import hec.data.timeSeriesText.TextTimeSeries; +import hec.db.DbConnection; +import hec.db.cwms.CwmsTimeSeriesTextDao; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import org.jooq.DSLContext; +import org.jooq.exception.NoDataFoundException; +import org.jooq.impl.DefaultBinding; +import usace.cwms.db.dao.ifc.text.CwmsDbText; +import usace.cwms.db.dao.util.OracleTypeMap; +import usace.cwms.db.dao.util.services.CwmsDbServiceLookup; +import usace.cwms.db.jooq.codegen.packages.CWMS_TEXT_PACKAGE; +import wcds.dbi.oracle.CwmsDaoServiceLookup; + +// based on https://bitbucket.hecdev.net/projects/CWMS/repos/hec-cwms-data-access/browse/hec-db-jdbc/src/main/java/wcds/dbi/oracle/cwms/CwmsTimeSeriesTextJdbcDao.java +public final class TextTimeSeriesDao extends JooqDao { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault(); + private final SimpleDateFormat _dateTimeFormatter = new SimpleDateFormat("dd-MMM-yyyy " + + "HH:mm:ss"); + + public static final String OFFICE_ID = "OFFICE_ID"; + private static final String TEXT = "TEXT"; + private static final String TEXT_ID = "TEXT_ID"; + private static final int TEXT_DOES_NOT_EXIST_ERROR_CODE = 20034; + private static final int TEXT_ID_DOES_NOT_EXIST_ERROR_CODE = 20001; + private static final String ATTRIBUTE = "ATTRIBUTE"; + private static final String STD_TEXT_ID = "STD_TEXT_ID"; + private static final String DATA_ENTRY_DATE = "DATA_ENTRY_DATE"; + private static final String VERSION_DATE = "VERSION_DATE"; + private static final String DATE_TIME = "DATE_TIME"; + private static final String STD_TEXT = "STD_TEXT"; + + private static List timeSeriesStdTextColumnsList; + private static List stdTextCatalogColumnsList; + private static List timeSeriesTextColumnsList; + + static { + String[] array = new String[]{DATE_TIME, VERSION_DATE, DATA_ENTRY_DATE, TEXT_ID, + ATTRIBUTE, TEXT}; + Arrays.sort(array); + timeSeriesTextColumnsList = Arrays.asList(array); + } + + static { + String[] array = new String[]{OFFICE_ID, STD_TEXT_ID, STD_TEXT}; + Arrays.sort(array); + stdTextCatalogColumnsList = Arrays.asList(array); + } + + static { + String[] array = new String[]{DATE_TIME, VERSION_DATE, DATA_ENTRY_DATE, STD_TEXT_ID, + ATTRIBUTE, STD_TEXT}; + Arrays.sort(array); + timeSeriesStdTextColumnsList = Arrays.asList(array); + } + + + public TextTimeSeriesDao(DSLContext dsl) { + super(dsl); + } + + + + + public StandardTextCatalog getCatalog(String pOfficeIdMask, String pStdTextIdMask) throws SQLException { + + CwmsTimeSeriesTextDao dbText = CwmsDaoServiceLookup.getDao(CwmsTimeSeriesTextDao.class, new DbConnection(DbConnection.DB_ORACLE)); + return connectionResult(dsl, c -> { + hec.data.timeSeriesText.StandardTextCatalog dataCatalog = dbText.retreiveStandardTextCatalog(c, pOfficeIdMask, pStdTextIdMask); + return new StandardTextCatalog.Builder().from(dataCatalog).build(); + }); + + } + + public StandardTextCatalog getCatalogNotUsed(String pOfficeIdMask, String pStdTextIdMask) throws SQLException { + + try (ResultSet rs = CWMS_TEXT_PACKAGE.call_CAT_STD_TEXT_F(dsl.configuration(), + pStdTextIdMask, pOfficeIdMask).intoResultSet()) { + + OracleTypeMap.checkMetaData(rs.getMetaData(), stdTextCatalogColumnsList, "Standard " + + "Text Catalog"); + StandardTextCatalog.Builder builder = new StandardTextCatalog.Builder(); + while (rs.next()) { + cwms.cda.data.dto.timeSeriesText.StandardTextId id = new cwms.cda.data.dto.timeSeriesText.StandardTextId.Builder() + .withOfficeId(rs.getString(OFFICE_ID)) + .withId(rs.getString(STD_TEXT_ID)) + .build(); + StandardTextValue standardTextValue = new StandardTextValue.Builder() + .withId(id) + .withStandardText(rs.getString(STD_TEXT)) + .build(); + builder.withStandardTextValue(standardTextValue); + } + return builder.build(); + } + + } + + + + + + + + + + + public Timestamp createTimestamp(Date date) { + Timestamp retval = null; + if (date != null) { + long time = date.getTime(); + retval = createTimestamp(time); + } + return retval; + } + + public Timestamp createTimestamp(long date) { + + if (logger.atFinest().isEnabled()) { + TimeZone defaultTimeZone = DEFAULT_TIME_ZONE; + String defaultTimeZoneDisplayName = + " " + defaultTimeZone.getDisplayName(defaultTimeZone.inDaylightTime(new Date(date)), TimeZone.SHORT); + TimeZone gmtTimeZone = OracleTypeMap.GMT_TIME_ZONE; + Date convertedDate = new Date(date); + String utcTimeZoneDisplayName = + " " + gmtTimeZone.getDisplayName(gmtTimeZone.inDaylightTime(convertedDate), + TimeZone.SHORT); + logger.atFinest().log("Storing date: " + _dateTimeFormatter.format(date) + defaultTimeZoneDisplayName + + " converted to UTC date: " + _dateTimeFormatter.format(convertedDate) + utcTimeZoneDisplayName); + } + return new Timestamp(date); + } + + public String createTimeZoneId(TimeZone timeZone) { + String retval = null; + if (timeZone != null) { + retval = timeZone.getID(); + } + return retval; + } + + + public ResultSet retrieveTsTextF(String pTsid, String textMask, + Date startTime, Date endTime, Date versionDate, + TimeZone timeZone, boolean maxVersion, + Long minAttribute, Long maxAttribute, String officeId) throws SQLException { + Timestamp pStartTime = createTimestamp(startTime); + Timestamp pEndTime = createTimestamp(endTime); + Timestamp pVersionDate = createTimestamp(versionDate); + String pTimeZone = createTimeZoneId(timeZone); + String pMaxVersion = OracleTypeMap.formatBool(maxVersion); + return CWMS_TEXT_PACKAGE.call_RETRIEVE_TS_TEXT_F(dsl.configuration(), + pTsid, textMask, + pStartTime, + pEndTime, + pVersionDate, + pTimeZone, + pMaxVersion, minAttribute, maxAttribute, officeId).intoResultSet(); + } + + public TextTimeSeries retrieveTimeSeriesText( + ITimeSeriesDescription timeSeriesDescription, String textMask, Date startTime, + Date endTime, Date versionDate, + boolean maxVersion, Long minAttribute, Long maxAttribute) throws IOException { + try { + String pTsid = timeSeriesDescription.toString(); + TimeZone timeZone = OracleTypeMap.GMT_TIME_ZONE; + String officeId = timeSeriesDescription.getOfficeId(); + + try (ResultSet retrieveTsTextF = retrieveTsTextF(pTsid, textMask, + startTime, endTime, versionDate, timeZone, maxVersion, minAttribute, + maxAttribute, + officeId)) { + return parseTimeSeriesTextResultSet(timeSeriesDescription, retrieveTsTextF); + } + } catch (SQLException e) { + if (e.getErrorCode() == TEXT_DOES_NOT_EXIST_ERROR_CODE || e.getErrorCode() == TEXT_ID_DOES_NOT_EXIST_ERROR_CODE) { + throw new NoDataFoundException(); + } else { + throw new IOException(e); + } + } + } + + private TextTimeSeries parseTimeSeriesTextResultSet(ITimeSeriesDescription timeSeriesDescription, ResultSet rs) throws SQLException { + OracleTypeMap.checkMetaData(rs.getMetaData(), timeSeriesTextColumnsList, "Text Time " + + "Series"); + + TextTimeSeries retval = new TextTimeSeries<>(timeSeriesDescription); + while (rs.next()) { + RegularTextTimeSeriesRow.Builder builder = new RegularTextTimeSeriesRow.Builder(); + + Timestamp tsDateTime = rs.getTimestamp(DATE_TIME, + OracleTypeMap.getInstance().getGmtCalendar()); + if (!rs.wasNull()) { + Date dateTime = new Date(tsDateTime.getTime()); + builder.withDateTime(dateTime); + } + + Timestamp tsVersionDate = rs.getTimestamp(VERSION_DATE, + OracleTypeMap.getInstance().getGmtCalendar()); + if (!rs.wasNull()) { + Date versionDate = new Date(tsVersionDate.getTime()); + builder.withVersionDate(versionDate); + } + Timestamp tsDataEntryDate = rs.getTimestamp(DATA_ENTRY_DATE, + OracleTypeMap.getInstance().getGmtCalendar()); + if (!rs.wasNull()) { + Date dataEntryDate = new Date(tsDataEntryDate.getTime()); + builder.withDataEntryDate(dataEntryDate); + } + String textId = rs.getString(TEXT_ID); + if (!rs.wasNull()) { + builder.withTextId(textId); + } + Number attribute = rs.getLong(ATTRIBUTE); + if (!rs.wasNull()) { + builder.withAttribute(attribute.longValue()); + } + String clobString = rs.getString(TEXT); + if (!rs.wasNull()) { + builder.withTextValue(clobString); + } + retval.add(builder.build()); + } + return retval; + } + + + + + + + + protected void setThreadTimeZone(String timeZone) { + if (timeZone == null) { + timeZone = System.getProperty("cwms.default.timezone"); + if (timeZone == null) { + timeZone = "UTC"; + } + } + TimeZone calTimeZone = TimeZone.getTimeZone(timeZone); + DefaultBinding.THREAD_LOCAL.set(Calendar.getInstance(calTimeZone)); + } + + + protected void setThreadTimeZone(TimeZone timeZone) { + if (timeZone == null) { + timeZone = TimeZone.getTimeZone("UTC"); + } + String defaultTimeZone = System.getProperty("cwms.default.timezone"); + if (defaultTimeZone != null) { + timeZone = TimeZone.getTimeZone(defaultTimeZone); + } + DefaultBinding.THREAD_LOCAL.set(Calendar.getInstance(timeZone)); + } + + + public void deleteStandardText(StandardTextId standardTextId, DeleteAction deleteAction) { + String stdTextId = standardTextId.getStandardTextId(); + String deleteActionString = deleteAction.toString(); + String officeId = standardTextId.getOfficeId(); + + connection(dsl, c -> { + CwmsDbText dbText = CwmsDbServiceLookup.buildCwmsDb(CwmsDbText.class, c); + try { + dbText.deleteStdText(c, stdTextId, deleteActionString, officeId); + } catch (SQLException e) { + throw new IOException(e); + } + }); + } + + + public StandardTextValue retrieveStandardText(StandardTextId standardTextId) throws IOException { + + String stdTextId = standardTextId.getStandardTextId(); + String officeId = standardTextId.getOfficeId(); + + return connectionResult(dsl, c -> { + CwmsDbText dbText = CwmsDbServiceLookup.buildCwmsDb(CwmsDbText.class, c); + + try { + String stdTextClob = dbText.retrieveStdTextF(c, stdTextId, officeId); + + cwms.cda.data.dto.timeSeriesText.StandardTextId id = new cwms.cda.data.dto.timeSeriesText.StandardTextId.Builder() + .from(standardTextId) + .build(); + return new StandardTextValue.Builder() + .withId(id) + .withStandardText(stdTextClob) + .build(); + } catch (SQLException e) { + if (e.getErrorCode() == TEXT_DOES_NOT_EXIST_ERROR_CODE) { + throw new NotFoundException(e); + } else { + throw new IOException(e); + } + } + + }); + + + } + + + public void storeStandardText(StandardTextValue standardTextValue, + boolean failIfExists) { + + cwms.cda.data.dto.timeSeriesText.StandardTextId standardTextId = standardTextValue.getId(); + String stdTextId = standardTextId.getId(); + String stdText = standardTextValue.getStandardText(); + String officeId = standardTextId.getOfficeId(); + + connection(dsl, c -> { + CwmsDbText dbText = CwmsDbServiceLookup.buildCwmsDb(CwmsDbText.class, c); + + try { + dbText.storeStdText(c, stdTextId, stdText, failIfExists, officeId); + } catch (SQLException e) { + throw new IOException(e); + } + }); + } + + + + + + public void deleteTimeSeriesStandardText( + ITimeSeriesDescription timeSeriesDescription, + StandardTextId standardTextId, Date startTime, + Date endTime, Date versionDate, boolean maxVersion, + Long minAttribute, Long maxAttribute) { + + String tsid = timeSeriesDescription.toString(); + String stdTextIdMask = standardTextId.getStandardTextId(); + TimeZone timeZone = OracleTypeMap.GMT_TIME_ZONE; + String officeId = timeSeriesDescription.getOfficeId(); + + connection(dsl, c -> { + try { + CwmsDbText dbText = CwmsDbServiceLookup.buildCwmsDb(CwmsDbText.class, c); + dbText.deleteTsStdText(c, tsid, stdTextIdMask, startTime, + endTime, versionDate, timeZone, maxVersion, minAttribute, + maxAttribute, officeId); + } catch (SQLException e) { + throw new IOException(e); + } + }); + } + + +// public TextTimeSeries retrieveTimeSeriesStandardText( +// ITimeSeriesDescription timeSeriesDescription, StandardTextId standardTextId, Date startTime, Date endTime, +// Date versionDate, boolean maxVersion, boolean retrieveText, Long minAttribute, Long maxAttribute) +// { +// +// String tsid = timeSeriesDescription.toString(); +// String stdTextIdMask = null; +// if (standardTextId != null) { +// stdTextIdMask = standardTextId.getStandardTextId(); +// } +// TimeZone timeZone = OracleTypeMap.GMT_TIME_ZONE; +// String officeId = timeSeriesDescription.getOfficeId(); +// +// return connectionResult(dsl, c -> { +// CwmsDbText dbText = CwmsDbServiceLookup.buildCwmsDb(CwmsDbText.class, c); +// +// try { +// String stdTextClob = dbText.retrieveTsStdTextF(c, tsid, +// stdTextIdMask, startTime, endTime, versionDate, timeZone, maxVersion, +// retrieveText, minAttribute, maxAttribute, officeId); +// +// return parseTimeSeriesStandardTextResultSet(timeSeriesDescription, retrieveTsStdTextF); +// +// } catch (SQLException e) { +// if (e.getErrorCode() == TEXT_DOES_NOT_EXIST_ERROR_CODE || e.getErrorCode() == TEXT_ID_DOES_NOT_EXIST_ERROR_CODE) { +// throw new NotFoundException(e); +// } else { +// throw new IOException(e); +// } +// } +// }); + +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/TextTimeSeries.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/TextTimeSeries.java new file mode 100644 index 000000000..73b236ef2 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/TextTimeSeries.java @@ -0,0 +1,37 @@ +package cwms.cda.data.dto; + +import cwms.cda.api.errors.FieldException; + +public class TextTimeSeries extends CwmsDTOPaginated { + private String description; + private String id; + private String officeId; + + + public TextTimeSeries() { + super(null); + id = null; + description = null; + } + + public TextTimeSeries(String id, String officeId, String description) { + super(officeId); + this.id = id; + this.description = description; + } + + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + + + + @Override + public void validate() throws FieldException { + + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/RegularTextTimeSeriesRow.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/RegularTextTimeSeriesRow.java new file mode 100644 index 000000000..9b7e5db1a --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/RegularTextTimeSeriesRow.java @@ -0,0 +1,127 @@ +package cwms.cda.data.dto.timeSeriesText; + +import com.fasterxml.jackson.annotation.JsonInclude; +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 hec.data.timeSeriesText.DateDateKey; +import hec.data.timeSeriesText.TextTimeSeriesRow; +import java.util.Date; + +@JsonDeserialize(builder = RegularTextTimeSeriesRow.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +public class RegularTextTimeSeriesRow implements TextTimeSeriesRow { + private final Date _dateTime; + private final Date _versionDate; + private final Date _dataEntryDate; + private final String _textId; + private final Long _attribute; + private final String _textValue; + private final boolean _newData; + + private RegularTextTimeSeriesRow(Builder builder) { + _dateTime = builder._dateTime; + _versionDate = builder._versionDate; + _dataEntryDate = builder._dataEntryDate; + _textId = builder._textId; + _attribute = builder._attribute; + _textValue = builder._textValue; + _newData = builder._newData; + } + + @Override + public RegularTextTimeSeriesRow copy() { + return new RegularTextTimeSeriesRow.Builder().from(this).build(); + } + + @Override + public Date getDateTime() { + return null; + } + + @Override + public Date getDataEntryDate() { + return null; + } + + @Override + public DateDateKey getDateDateKey() { + return null; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + private Date _dateTime; + private Date _versionDate; + private Date _dataEntryDate; + private String _textId; + private Long _attribute; + private String _textValue; + private boolean _newData; + + public Builder() { + } + + public Builder withDateTime(Date dateTime) { + _dateTime = dateTime; + return this; + } + + public Builder withVersionDate(Date versionDate) { + _versionDate = versionDate; + return this; + } + + public Builder withDataEntryDate(Date dataEntryDate) { + _dataEntryDate = dataEntryDate; + return this; + } + + public Builder withTextId(String textId) { + _textId = textId; + return this; + } + + public Builder withAttribute(Long attribute) { + _attribute = attribute; + return this; + } + + public Builder withTextValue(String textValue) { + _textValue = textValue; + return this; + } + + public Builder withNewData(boolean newData) { + _newData = newData; + return this; + } + + public RegularTextTimeSeriesRow build() { + return new RegularTextTimeSeriesRow(this); + } + + public Builder from(RegularTextTimeSeriesRow regularTextTimeSeriesRow) { + if(regularTextTimeSeriesRow == null){ + return withDateTime(null) + .withVersionDate(null) + .withDataEntryDate(null) + .withTextId(null) + .withAttribute(null) + .withTextValue(null) + .withNewData(false); + } else { + return withDateTime(regularTextTimeSeriesRow._dateTime) + .withVersionDate(regularTextTimeSeriesRow._versionDate) + .withDataEntryDate(regularTextTimeSeriesRow._dataEntryDate) + .withTextId(regularTextTimeSeriesRow._textId) + .withAttribute(regularTextTimeSeriesRow._attribute) + .withTextValue(regularTextTimeSeriesRow._textValue) + .withNewData(regularTextTimeSeriesRow._newData); + } + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalog.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalog.java new file mode 100644 index 000000000..021d2c5d6 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalog.java @@ -0,0 +1,125 @@ +package cwms.cda.data.dto.timeSeriesText; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +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.api.errors.FieldException; +import cwms.cda.data.dto.CwmsDTO; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NavigableMap; + +@JsonDeserialize(builder = StandardTextCatalog.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@JsonIgnoreProperties("office-id") // This does work to block office-id from the super! +public class StandardTextCatalog extends CwmsDTO { + + private final NavigableMap values; + + + private StandardTextCatalog(Builder builder) { + super(builder == null? null : getOffice(builder.values)); + + if(builder.values != null){ + values = new java.util.TreeMap<>(new StandardTextIdComparator()); + for(StandardTextValue value : builder.values){ + values.put(value.getId(), value); + } + } else { + values = null; + } + + } + + private static String getOffice(Collection values) { + String retval = null; + if (values != null) { + for (StandardTextValue value : values) { + if (value != null) { + cwms.cda.data.dto.timeSeriesText.StandardTextId id = value.getId(); + if (id != null && id.getOfficeId() != null){ + retval = id.getOfficeId(); + break; + } + } + } + } + return retval; + } + + public Collection getValues(){ + if(values == null){ + return null; + } + return values.values(); + } + + + @Override + public void validate() throws FieldException { + + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + private List values = new ArrayList<>(); + + + public Builder() { + } + + public Builder withStandardTextValue(StandardTextValue standardTextValue) { + + if (values == null) { + values = new ArrayList<>(); + } + values.add(standardTextValue); + return this; + } + + public Builder withValues(List newValues) { + + if (newValues != null) { + values = new ArrayList<>(); + values.addAll(newValues); + } else { + values = null; + } + + return this; + } + + public StandardTextCatalog build() { + return new StandardTextCatalog(this); + } + + public Builder from(hec.data.timeSeriesText.StandardTextCatalog dataCatalog) { + List newValues = null; + + if (dataCatalog != null) { + Collection standardTextValues = + dataCatalog.getStandardTextValues(); + if (standardTextValues != null) { + newValues = new ArrayList<>(); + for (hec.data.timeSeriesText.StandardTextValue standardTextValue : + standardTextValues) { + StandardTextValue newStandardTextValue = + new StandardTextValue.Builder().from(standardTextValue).build(); + withStandardTextValue(newStandardTextValue); + } + } + } + + return withValues(newValues); + } + } + + +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextId.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextId.java new file mode 100644 index 000000000..fc7037867 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextId.java @@ -0,0 +1,105 @@ +package cwms.cda.data.dto.timeSeriesText; + +import com.fasterxml.jackson.annotation.JsonInclude; +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.api.errors.FieldException; +import cwms.cda.data.dto.CwmsDTO; + +/** + * Almost an exact copy of hec.data.timeSeriesText.StandardTextId. Its duplicated here so that + * CDA can + * control serialization. + */ + +@JsonDeserialize(builder = StandardTextId.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +public class StandardTextId extends CwmsDTO { + + private final String id; + + private StandardTextId(Builder builder) { + super(builder.officeId); + + this.id = builder.id; + } + + public String getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StandardTextId that = (StandardTextId) o; + + return getId() != null ? getId().equals(that.getId()) : that.getId() == null; + } + + @Override + public int hashCode() { + return getId() != null ? getId().hashCode() : 0; + } + + @Override + public void validate() throws FieldException { + + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + + public String officeId; + private String id; + + public Builder() { + } + + public Builder withOfficeId(String officeId) { + this.officeId = officeId; + return this; + } + + public Builder withId(String id){ + this.id = id; + return this; + } + + public Builder from(StandardTextId from){ + String newOfficeId = null; + String newStandardTextId = null; + + if(from != null){ + newOfficeId = from.getOfficeId(); + newStandardTextId = from.getId(); + } + return withOfficeId(newOfficeId) + .withId(newStandardTextId); + } + + // simplify building from a hec.data object. + public Builder from(hec.data.timeSeriesText.StandardTextId from){ + String newOfficeId = null; + String newStandardTextId = null; + + if(from != null){ + newOfficeId = from.getOfficeId(); + newStandardTextId = from.getStandardTextId(); + } + return withOfficeId(newOfficeId) + .withId(newStandardTextId); + } + + + public StandardTextId build(){ + return new StandardTextId(this); + } + + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparator.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparator.java new file mode 100644 index 000000000..76a31f6eb --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparator.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021. Hydrologic Engineering Center (HEC). + * United States Army Corps of Engineers + * All Rights Reserved. HEC PROPRIETARY/CONFIDENTIAL. + * Source may not be released without written approval from HEC + * + */ + +package cwms.cda.data.dto.timeSeriesText; + +import java.util.Comparator; + +public class StandardTextIdComparator implements Comparator { + public StandardTextIdComparator() { + + } + + @Override + public int compare(StandardTextId o1, StandardTextId o2) { + String standardTextId1 = null; + String standardTextId2 = null; + String officeId1 = null; + String officeId2 = null; + + if (o1 != null) { + standardTextId1 = o1.getId(); + officeId1 = o1.getOfficeId(); + } + if (o2 != null) { + standardTextId2 = o2.getId(); + officeId2 = o2.getOfficeId(); + } + + // compare officeId first and then standardTextId + int compare = compareStrings(officeId1, officeId2); + if (compare == 0) { + compare = compareStrings(standardTextId1, standardTextId2); + } + return compare; + } + + private int compareStrings(String s1, String s2) { + if (s1 == null && s2 == null) { + return 0; + } + if (s1 == null) { + return -1; + } + if (s2 == null) { + return 1; + } + int compare = String.CASE_INSENSITIVE_ORDER.compare(s1, s2); + return compare; + } + +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextValue.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextValue.java new file mode 100644 index 000000000..e2a38fa8d --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/StandardTextValue.java @@ -0,0 +1,119 @@ +package cwms.cda.data.dto.timeSeriesText; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.api.errors.FieldException; +import cwms.cda.data.dto.CwmsDTO; +import org.jetbrains.annotations.NotNull; + +@JsonDeserialize(builder = StandardTextValue.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@JsonIgnoreProperties("office-id") // This does work to block office-id from the super! +public class StandardTextValue extends CwmsDTO { + + private final StandardTextId id; + + private final String standardText; + + private StandardTextValue(@NotNull Builder builder) { + super(builder.id == null? null : builder.id.getOfficeId()); + this.id = builder.id; + this.standardText = builder.standardText; + } + + public String getStandardText() { + return standardText; + } + + + public StandardTextId getId() { + return id; + } + + @Override + public void validate() throws FieldException { + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StandardTextValue that = (StandardTextValue) o; + + if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false; + return getStandardText() != null ? getStandardText().equals(that.getStandardText()) : that.getStandardText() == null; + } + + @Override + public int hashCode() { + int result = getId() != null ? getId().hashCode() : 0; + result = 31 * result + (getStandardText() != null ? getStandardText().hashCode() : 0); + return result; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + private StandardTextId id; + private String standardText; + + public Builder() { + + } + + public Builder withStandardText(String text) { + standardText = text; + return this; + } + + @JsonProperty("id") // If we don't have this then Jackson gets confused whether to call this or the method that takes hec.data + public Builder withId(StandardTextId id){ + this.id = id; + return this; + } + + public Builder withId(hec.data.timeSeriesText.StandardTextId newId){ + return withId(new StandardTextId.Builder().from(newId).build()); + } + + public Builder from(StandardTextValue input) { + StandardTextId newId = null; + String newStandardText = null; + + if (input != null) { + newId = input.getId(); + newStandardText = input.getStandardText(); + } + return withId(newId) + .withStandardText(newStandardText); + } + + // Simplify building from hec.data object. + public Builder from(hec.data.timeSeriesText.StandardTextValue value) { + StandardTextId newId = null; + String newStandardText = null; + + if (value != null) { + hec.data.timeSeriesText.StandardTextId standardTextId = value.getStandardTextId(); + newId = new StandardTextId.Builder().from(standardTextId).build(); + newStandardText = value.getStandardText(); + } + + return withId(newId) + .withStandardText(newStandardText); + } + + + public StandardTextValue build() { + return new StandardTextValue(this); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/TextTimeSeries.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/TextTimeSeries.java new file mode 100644 index 000000000..db220bdca --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/timeSeriesText/TextTimeSeries.java @@ -0,0 +1,30 @@ +package cwms.cda.data.dto.timeSeriesText; + +import com.fasterxml.jackson.annotation.JsonInclude; +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 hec.data.timeSeriesText.TextTimeSeriesRow; + +@JsonDeserialize(builder = TextTimeSeries.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +public class TextTimeSeries{ + + + private TextTimeSeries(Builder builder) { + + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + public Builder() { + } + + public TextTimeSeries build(){ + return new TextTimeSeries(this); + } + } +} diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalogTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalogTest.java new file mode 100644 index 000000000..e79cc06b3 --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextCatalogTest.java @@ -0,0 +1,80 @@ +package cwms.cda.data.dto.timeSeriesText; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cwms.cda.formatters.json.JsonV2; +import org.junit.jupiter.api.Test; + +class StandardTextCatalogTest { + + @Test + void testSerialize() throws JsonProcessingException { + StandardTextCatalog.Builder builder = new StandardTextCatalog.Builder(); + builder.withStandardTextValue(buildSTV("theId", "theOffice", "theText")); + builder.withStandardTextValue(buildSTV("anotherId", "theOffice", "moreText")); + + StandardTextCatalog catalog = builder.build(); + assertNotNull(catalog); + + ObjectMapper mapper = JsonV2.buildObjectMapper(); + String json = mapper.writeValueAsString(catalog); + assertNotNull(json); +// System.out.println(json); + + assertTrue(json.contains("theOffice")); + assertTrue(json.contains("theId")); + assertTrue(json.contains("anotherId")); + assertTrue(json.contains("theText")); + assertTrue(json.contains("moreText")); + } + + private static StandardTextValue buildSTV(String theId, String theOffice, String theText) { + return new StandardTextValue.Builder() + .withId(buildID(theId, theOffice)) + .withStandardText(theText).build(); + } + + private static StandardTextId buildID(String theId, String theOffice) { + return new StandardTextId.Builder() + .withId(theId) + .withOfficeId(theOffice).build(); + } + + @Test + void testRoundtrip() throws JsonProcessingException { + StandardTextCatalog.Builder builder = new StandardTextCatalog.Builder(); + builder.withStandardTextValue(buildSTV("theId", "theOffice", "theText")); + builder.withStandardTextValue(buildSTV("anotherId", "theOffice", "moreText")); + + StandardTextCatalog catalog = builder.build(); + assertNotNull(catalog); + + ObjectMapper mapper = JsonV2.buildObjectMapper(); + String json = mapper.writeValueAsString(catalog); + assertNotNull(json); + + StandardTextCatalog catalog2 = mapper.readValue(json, StandardTextCatalog.class); + + assertNotNull(catalog2); + assertCatalogEquals(catalog, catalog2); + + } + + private static void assertCatalogEquals(StandardTextCatalog catalog, StandardTextCatalog catalog2) { + assertEquals(catalog.getOfficeId(), catalog2.getOfficeId()); + + assertEquals(catalog.getValues().size(), catalog2.getValues().size()); + for (StandardTextValue value : catalog.getValues()) { + assertTrue(catalog2.getValues().contains(value)); + } + + for (StandardTextValue value : catalog2.getValues()) { + assertTrue(catalog.getValues().contains(value)); + } + } + +} \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparatorTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparatorTest.java new file mode 100644 index 000000000..0fb18d64e --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdComparatorTest.java @@ -0,0 +1,77 @@ +package cwms.cda.data.dto.timeSeriesText; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class StandardTextIdComparatorTest { + + @Test + void testCompareSelf(){ + StandardTextId id = new StandardTextId.Builder() + .withId("theId") + .withOfficeId("theOffice").build(); + + StandardTextIdComparator comparator = new StandardTextIdComparator(); + assertEquals(0, comparator.compare(id, id)); + } + + @Test + void testCompareDiffOffice(){ + StandardTextId.Builder builder = new StandardTextId.Builder() + .withId("theId"); + + StandardTextId idA = builder.withOfficeId("OfficeA").build(); + StandardTextId idB = builder.withOfficeId("OfficeB").build(); + + StandardTextIdComparator comparator = new StandardTextIdComparator(); + assertEquals(-1, comparator.compare(idA, idB)); + assertEquals(1, comparator.compare(idB, idA)); + } + + @Test + void testCompareNullOffices(){ + StandardTextId.Builder builder = new StandardTextId.Builder() + .withId("theId"); + + StandardTextId idA = builder.withOfficeId(null).build(); + StandardTextId idB = builder.withOfficeId(null).build(); + StandardTextId idC = builder.withOfficeId("OfficeA").build(); + + StandardTextIdComparator comparator = new StandardTextIdComparator(); + assertEquals(0, comparator.compare(idA, idB)); + assertEquals(0, comparator.compare(idB, idA)); + assertEquals(-1, comparator.compare(idA, idC)); // nulls come first + assertEquals(1, comparator.compare(idC, idA)); + } + + @Test + void testCompareEmptyOffices(){ + StandardTextId.Builder builder = new StandardTextId.Builder() + .withId("theId"); + + StandardTextId idA = builder.withOfficeId("").build(); + StandardTextId idB = builder.withOfficeId("").build(); + StandardTextId idC = builder.withOfficeId("OfficeA").build(); + + StandardTextIdComparator comparator = new StandardTextIdComparator(); + assertEquals(0, comparator.compare(idA, idB)); + assertEquals(0, comparator.compare(idB, idA)); + assertTrue(0> comparator.compare(idA, idC)); + assertTrue(0< comparator.compare(idC, idA)); + } + + @Test + void testCompareDiffID(){ + StandardTextId.Builder builder = new StandardTextId.Builder() + .withOfficeId("SWT"); + + StandardTextId idA = builder.withId("A").build(); + StandardTextId idB = builder.withId("B").build(); + + StandardTextIdComparator comparator = new StandardTextIdComparator(); + assertEquals(-1, comparator.compare(idA, idB)); + assertEquals(1, comparator.compare(idB, idA)); + } + +} \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdTest.java new file mode 100644 index 000000000..dc840f4c3 --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextIdTest.java @@ -0,0 +1,40 @@ +package cwms.cda.data.dto.timeSeriesText; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cwms.cda.formatters.json.JsonV2; +import org.junit.jupiter.api.Test; + +class StandardTextIdTest { + + + @Test + void testSerialization() throws JsonProcessingException + { + + StandardTextId id = new StandardTextId.Builder() + .withId("theId") + .withOfficeId("theOffice").build(); + + + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + String json = objectMapper.writeValueAsString(id); + assertNotNull(json); +// System.out.println(json); + } + + @Test + void testDeserialization() throws JsonProcessingException + { + String input = "{\"office-id\":\"theOffice\",\"id\":\"theId\"}"; + + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + StandardTextId id = objectMapper.readValue(input, StandardTextId.class); + assertNotNull(id); + assertEquals("theOffice", id.getOfficeId()); + assertEquals("theId", id.getId()); + } + +} \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextValueTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextValueTest.java new file mode 100644 index 000000000..189d2ff8f --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/timeSeriesText/StandardTextValueTest.java @@ -0,0 +1,46 @@ +package cwms.cda.data.dto.timeSeriesText; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cwms.cda.formatters.json.JsonV2; +import org.junit.jupiter.api.Test; + +class StandardTextValueTest { + + @Test + void testSerialization() throws JsonProcessingException + { + + StandardTextValue standardTextValue = new StandardTextValue.Builder() + .withId(new StandardTextId.Builder() + .withId("theId") + .withOfficeId("theOffice").build()) + .withStandardText("textValue").build(); + + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + String json = objectMapper.writeValueAsString(standardTextValue); + assertNotNull(json); + System.out.println(json); + } + + @Test + void testDeserialization() throws JsonProcessingException + { + String input = "{\"id\":{\"office-id\":\"theOffice\",\"id\":\"theId\"},\"standard-text\":\"textValue\"}"; + + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + StandardTextValue standardTextValue = objectMapper.readValue(input, StandardTextValue.class); + assertNotNull(standardTextValue); + StandardTextId id = standardTextValue.getId(); + assertNotNull(id); + assertEquals("theOffice", id.getOfficeId()); + assertEquals("theId", id.getId()); + assertEquals("textValue", standardTextValue.getStandardText()); + + + } + +} \ No newline at end of file