diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java index 398f14eb..46e35c37 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java @@ -32,4 +32,9 @@ public DbException(String message, Exception cause) { super(message, cause); } + + public DbException(String message) + { + super(message); + } } diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/OpenDcsResource.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/OpenDcsResource.java index d5a931a9..eeebd1c2 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/OpenDcsResource.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/OpenDcsResource.java @@ -22,7 +22,10 @@ import javax.ws.rs.core.Context; import decodes.cwms.CwmsDatabaseProvider; +import decodes.db.Database; import decodes.db.DatabaseException; +import decodes.db.DatabaseIO; +import decodes.tsdb.TimeSeriesDb; import decodes.util.DecodesSettings; import opendcs.opentsdb.OpenTsdbProvider; import org.opendcs.database.DatabaseService; @@ -81,7 +84,18 @@ final OpenDcsDatabase createDb() { throw new IllegalStateException("Error connecting to the database via JNDI", ex); } -// throw new IllegalStateException("Error connecting to the database via JNDI", e); } } + + DatabaseIO getLegacyDatabase() + { + return createDb().getLegacyDatabase(Database.class).map(Database::getDbIo) + .orElseThrow(() -> new UnsupportedOperationException("Endpoint is unsupported by the OpenDCS REST API.")); + } + + TimeSeriesDb getLegacyTimeseriesDB() + { + return createDb().getLegacyDatabase(TimeSeriesDb.class) + .orElseThrow(() -> new UnsupportedOperationException("Endpoint is unsupported by the OpenDCS REST API.")); + } } diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/TimeSeriesResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/TimeSeriesResources.java index 9cb99589..ebd942b7 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/TimeSeriesResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/TimeSeriesResources.java @@ -15,11 +15,12 @@ package org.opendcs.odcsapi.res; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import java.util.logging.Logger; import javax.annotation.security.RolesAllowed; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -32,17 +33,33 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import decodes.cwms.CwmsTsId; +import decodes.hdb.HdbTsId; +import decodes.sql.DbKey; +import decodes.tsdb.BadTimeSeriesException; +import decodes.tsdb.CTimeSeries; +import decodes.tsdb.DbIoException; +import decodes.tsdb.NoSuchObjectException; +import decodes.tsdb.TimeSeriesDb; +import decodes.tsdb.TimeSeriesIdentifier; +import decodes.tsdb.TsGroup; +import ilex.var.TimedVariable; +import opendcs.dai.IntervalDAI; +import opendcs.dai.TimeSeriesDAI; +import opendcs.dai.TsGroupDAI; +import opendcs.opentsdb.Interval; import org.opendcs.odcsapi.beans.ApiInterval; +import org.opendcs.odcsapi.beans.ApiTimeSeriesData; +import org.opendcs.odcsapi.beans.ApiTimeSeriesIdentifier; +import org.opendcs.odcsapi.beans.ApiTimeSeriesSpec; +import org.opendcs.odcsapi.beans.ApiTimeSeriesValue; import org.opendcs.odcsapi.beans.ApiTsGroup; +import org.opendcs.odcsapi.beans.ApiTsGroupRef; import org.opendcs.odcsapi.dao.ApiRefListDAO; -import org.opendcs.odcsapi.dao.ApiTsDAO; import org.opendcs.odcsapi.dao.DbException; -import org.opendcs.odcsapi.errorhandling.ErrorCodes; import org.opendcs.odcsapi.errorhandling.WebAppException; import org.opendcs.odcsapi.hydrojson.DbInterface; import org.opendcs.odcsapi.sec.AuthorizationCheck; -import org.opendcs.odcsapi.util.ApiConstants; -import org.opendcs.odcsapi.util.ApiHttpUtil; import ilex.util.IDateFormat; @@ -52,7 +69,7 @@ * */ @Path("/") -public class TimeSeriesResources +public class TimeSeriesResources extends OpenDcsResource { @Context HttpHeaders httpHeaders; @@ -62,12 +79,63 @@ public class TimeSeriesResources @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response getTimeSeriesRefs(@QueryParam("active") Boolean activeOnly) throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getTimeSeriesRefs"); - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + TimeSeriesDb tsdb = getLegacyTimeseriesDB(); + try (TimeSeriesDAI dai = tsdb.makeTimeSeriesDAO()) + { + return Response.status(HttpServletResponse.SC_OK) + .entity(map(dai.listTimeSeries(), activeOnly != null && activeOnly)) + .build(); + } + catch (DbIoException ex) { - return ApiHttpUtil.createResponse(dao.getTsRefs(activeOnly != null && activeOnly)); + throw new DbException("Unable to retrieve time series", ex); + } + } + + // TODO: Add active support to OpenDCS DAIs. + // This method only supports active checking for the CWMS DB implementation. + static ArrayList map(ArrayList identifiers, boolean activeOnly) + { + ArrayList ret = new ArrayList<>(); + for(TimeSeriesIdentifier id : identifiers) + { + if (activeOnly && id instanceof CwmsTsId) + { + CwmsTsId ctsid = (CwmsTsId)id; + if (ctsid.isActive()) + { + ApiTimeSeriesIdentifier apiId = new ApiTimeSeriesIdentifier(); + if (id.getKey() != null) + { + apiId.setKey(id.getKey().getValue()); + } + else + { + apiId.setKey(DbKey.NullKey.getValue()); + } + apiId.setActive(ctsid.isActive()); + apiId.setDescription(id.getDescription()); + apiId.setStorageUnits(id.getStorageUnits()); + apiId.setUniqueString(id.getUniqueString()); + ret.add(apiId); + } + } else { + ApiTimeSeriesIdentifier apiId = new ApiTimeSeriesIdentifier(); + if (id.getKey() != null) + { + apiId.setKey(id.getKey().getValue()); + } + else + { + apiId.setKey(DbKey.NullKey.getValue()); + } + apiId.setDescription(id.getDescription()); + apiId.setStorageUnits(id.getStorageUnits()); + apiId.setUniqueString(id.getUniqueString()); + ret.add(apiId); + } } + return ret; } @GET @@ -77,73 +145,214 @@ public Response getTimeSeriesRefs(@QueryParam("active") Boolean activeOnly) thro public Response getTimeSeriesSpec(@QueryParam("key") Long tsKey) throws WebAppException, DbException { if (tsKey == null) - throw new WebAppException(ErrorCodes.MISSING_ID, - "Missing required tskey parameter."); - - Logger.getLogger(ApiConstants.loggerName).fine("getTimeSeriesSpec"); - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) { - return ApiHttpUtil.createResponse(dao.getSpec(tsKey)); + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Missing required tskey parameter."); + } + + try (TimeSeriesDAI dai = getLegacyTimeseriesDB().makeTimeSeriesDAO()) + { + TimeSeriesIdentifier identifier = dai.getTimeSeriesIdentifier(DbKey.createDbKey(tsKey)); + ApiTimeSeriesSpec spec = specMap(identifier); + return Response.status(HttpServletResponse.SC_OK) + .entity(spec).build(); + } + catch (NoSuchObjectException e) + { + return Response.status(HttpServletResponse.SC_NOT_FOUND) + .entity("Time series with key=" + tsKey + " not found").build(); + } + catch (DbIoException ex) + { + throw new DbException("Unable to retrieve time series spec", ex); } } + static ApiTimeSeriesSpec specMap(TimeSeriesIdentifier id) + { + ApiTimeSeriesSpec ret = new ApiTimeSeriesSpec(); + ApiTimeSeriesIdentifier tsId = map(id); + ret.setTsid(tsId); + if (id instanceof CwmsTsId) + { + CwmsTsId ctsid = (CwmsTsId)id; + ret.setActive(ctsid.isActive()); + ret.setInterval(ctsid.getInterval()); + ret.setDuration(ctsid.getDuration()); + ret.setVersion((ctsid).getVersion()); + if (ctsid.getDataTypeId() != null) + { + ret.setDatatypeId(ctsid.getDataTypeId().getValue()); + } + else + { + ret.setDatatypeId(DbKey.NullKey.getValue()); + } + if (ctsid.getSite() != null && ctsid.getSite().getId() != null) + { + ret.setSiteId(ctsid.getSite().getId().getValue()); + } + else + { + ret.setSiteId(DbKey.NullKey.getValue()); + } + if (ctsid.getSubLoc() != null) + { + ret.setLocation(ctsid.getBaseLoc() + "-" + ctsid.getSubLoc()); + } + else + { + ret.setLocation(ctsid.getBaseLoc()); + } + } + else if (id instanceof HdbTsId) + { + HdbTsId htsid = (HdbTsId)id; + ret.setInterval(htsid.getInterval()); + if (htsid.getDataTypeId() != null) + { + ret.setDatatypeId(htsid.getDataTypeId().getValue()); + } + else + { + ret.setDatatypeId(DbKey.NullKey.getValue()); + } + if (htsid.getSite() != null && htsid.getSite().getId() != null) + { + ret.setSiteId(htsid.getSite().getId().getValue()); + } + else + { + ret.setSiteId(DbKey.NullKey.getValue()); + } + } + ret.setDatatypeId(id.getDataTypeId().getValue()); + if (ret.getLocation() == null || ret.getLocation().isEmpty()) + { + ret.setLocation(id.getSiteName()); + } + return ret; + } + @GET @Path("tsdata") @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response getTimeSeriesData(@QueryParam("key") Long tsKey, @QueryParam("start") String start, - @QueryParam("end") String end) - throws WebAppException, DbException + @QueryParam("end") String end) + throws WebAppException, DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getTimeSeriesData key=" + tsKey - + ", start=" + start + ", end=" + end); - if (tsKey == null) - throw new WebAppException(ErrorCodes.MISSING_ID, - "Missing required tskey parameter."); - - Date dStart = null, dEnd = null; + { + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Missing required tskey parameter."); + } + + Date dStart = null; + Date dEnd = null; if (start != null) - try { dStart = IDateFormat.parse(start); } + { + try + { + dStart = IDateFormat.parse(start); + } catch(IllegalArgumentException ex) { - throw new WebAppException(ErrorCodes.MISSING_ID, - "Invalid start time. Use [[[CC]YY]/DDD]/HH:MM[:SS] or relative time."); + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Invalid start time. Use [[[CC]YY]/DDD]/HH:MM[:SS] or relative time."); } + } if (end != null) { - try { dEnd = IDateFormat.parse(end); } - catch(IllegalArgumentException ex) + try + { + dEnd = IDateFormat.parse(end); + } + catch (IllegalArgumentException ex) { - throw new WebAppException(ErrorCodes.MISSING_ID, - "Invalid end time. Use [[[CC]YY]/DDD]/HH:MM[:SS] or relative time."); + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Invalid end time. Use [[[CC]YY]/DDD]/HH:MM[:SS] or relative time."); } + } + try (TimeSeriesDAI dai = getLegacyTimeseriesDB().makeTimeSeriesDAO()) + { + CTimeSeries cts = new CTimeSeries(DbKey.createDbKey(tsKey), null, null); + dai.fillTimeSeries(cts, dStart, dEnd); + return Response.status(HttpServletResponse.SC_OK) + .entity(dataMap(cts, dStart, dEnd)).build(); + } + catch (DbIoException | BadTimeSeriesException ex) + { + throw new DbException("Unable to retrieve time series data", ex); } + } - Logger.getLogger(ApiConstants.loggerName).fine("getTimeSeriesData start=" + dStart + ", end=" + dEnd); + static ApiTimeSeriesData dataMap(CTimeSeries cts, Date start, Date end) + { + ApiTimeSeriesData ret = new ApiTimeSeriesData(); + ret.setTsid(map(cts.getTimeSeriesIdentifier())); + ret.setValues(map(cts, start, end)); + return ret; + } - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + static ArrayList map(CTimeSeries cts, Date start, Date end) + { + ArrayList ret = new ArrayList<>(); + Date current = start; + while (current.before(end) || current.equals(end)) + { + TimedVariable value = cts.findWithin(current, 0); + if (value == null) + { + break; + } + double val = Double.parseDouble(value.valueString()); + ApiTimeSeriesValue apiValue = new ApiTimeSeriesValue(value.getTime(), val, value.getFlags()); + ret.add(apiValue); + if (current.equals(end)) + { + current = Date.from(end.toInstant().plusSeconds(1)); + } + else + { + current = cts.findNext(value.getTime()).getTime(); + } + } + return ret; + } + + static ApiTimeSeriesIdentifier map(TimeSeriesIdentifier tsid) + { + ApiTimeSeriesIdentifier ret = new ApiTimeSeriesIdentifier(); + if (tsid.getKey() != null) + { + ret.setKey(tsid.getKey().getValue()); + } + else { - return ApiHttpUtil.createResponse(dao.getTsData(tsKey, dStart, dEnd)); + ret.setKey(DbKey.NullKey.getValue()); } + ret.setUniqueString(tsid.getUniqueString()); + ret.setDescription(tsid.getDescription()); + ret.setStorageUnits(tsid.getStorageUnits()); + return ret; } - + @GET @Path("intervals") @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response getIntervals() - throws DbException + throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getIntervals"); try (DbInterface dbi = new DbInterface(); - ApiRefListDAO rlDAO = new ApiRefListDAO(dbi)) + ApiRefListDAO rlDAO = new ApiRefListDAO(dbi)) { + // TODO: Implement this in OpenDCS HashMap imap = rlDAO.getIntervals(); - return ApiHttpUtil.createResponse(imap.values()); + return Response.status(HttpServletResponse.SC_NOT_IMPLEMENTED) + .entity(imap.values()).build(); } } @@ -153,18 +362,40 @@ public Response getIntervals() @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) public Response postInterval(ApiInterval intv) - throws DbException + throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("postInterval"); - - try (DbInterface dbi = new DbInterface(); - ApiRefListDAO rlDAO = new ApiRefListDAO(dbi)) + try (IntervalDAI dai = getLegacyTimeseriesDB().makeIntervalDAO()) + { + Interval interval = map(intv); + dai.writeInterval(interval); + return Response.status(HttpServletResponse.SC_OK) + .entity(map(interval)).build(); + } + catch (DbIoException ex) { - rlDAO.writeInterval(intv); - return ApiHttpUtil.createResponse(intv); + throw new DbException("Unable to store interval", ex); } } + static Interval map(ApiInterval intv) + { + Interval ret = new Interval(intv.getName()); + ret.setKey(DbKey.createDbKey(intv.getIntervalId())); + ret.setCalConstant(Integer.parseInt(intv.getCalConstant())); + ret.setCalMultiplier(intv.getCalMultilier()); + return ret; + } + + static ApiInterval map(Interval intv) + { + ApiInterval ret = new ApiInterval(); + ret.setIntervalId(intv.getKey().getValue()); + ret.setName(intv.getName()); + ret.setCalConstant(String.valueOf(intv.getCalConstant())); + ret.setCalMultilier(intv.getCalMultiplier()); + return ret; + } + @DELETE @Path("interval") @Consumes(MediaType.APPLICATION_JSON) @@ -172,14 +403,13 @@ public Response postInterval(ApiInterval intv) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) public Response deleteInterval(@QueryParam("intvid") Long intvId) throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("deleteInterval id=" + intvId); - try (DbInterface dbi = new DbInterface(); - ApiRefListDAO rlDAO = new ApiRefListDAO(dbi)) + ApiRefListDAO rlDAO = new ApiRefListDAO(dbi)) { + // TODO: Implement this in OpenDCS rlDAO.deleteInterval(intvId); - return ApiHttpUtil.createResponse("interval with ID=" + intvId + " deleted"); - + return Response.status(HttpServletResponse.SC_NOT_IMPLEMENTED) + .entity("interval with ID=" + intvId + " deleted").build(); } } @@ -189,43 +419,132 @@ public Response deleteInterval(@QueryParam("intvid") Long intvId) throws DbExcep @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response getTsGroupRefs() throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getTsGroupRefs"); - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + try (TsGroupDAI dai = getLegacyTimeseriesDB().makeTsGroupDAO()) { - return ApiHttpUtil.createResponse(dao.getTsGroupRefs()); + return Response.status(HttpServletResponse.SC_OK) + .entity(mapRef(dai.getTsGroupList(null))).build(); + } + catch (DbIoException ex) + { + throw new DbException("Unable to retrieve time series group references", ex); } } + static ArrayList mapRef(ArrayList groups) + { + ArrayList ret = new ArrayList<>(); + for(TsGroup group : groups) + { + ApiTsGroupRef ref = new ApiTsGroupRef(); + if (group.getGroupId() != null) + { + ref.setGroupId(group.getGroupId().getValue()); + } + else + { + ref.setGroupId(DbKey.NullKey.getValue()); + } + ref.setGroupName(group.getGroupName()); + ref.setDescription(group.getDescription()); + ref.setGroupType(group.getGroupType()); + ret.add(ref); + } + return ret; + } + @GET @Path("tsgroup") @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) - public Response getTsGroupRefs(@QueryParam("groupid") Long groupId) throws WebAppException, DbException + public Response getTsGroupRefs(@QueryParam("groupid") Long groupId) throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getTsGroup"); - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + try (TsGroupDAI dai = getLegacyTimeseriesDB().makeTsGroupDAO()) { - return ApiHttpUtil.createResponse(dao.getTsGroup(groupId)); + return Response.status(HttpServletResponse.SC_OK) + .entity(map(dai.getTsGroupById(DbKey.createDbKey(groupId)))).build(); + } + catch (DbIoException ex) + { + throw new DbException("Unable to retrieve time series group by ID", ex); } } - + + static ApiTsGroup map(TsGroup group) + { + ApiTsGroup ret = new ApiTsGroup(); + ret.setGroupName(group.getGroupName()); + ret.setDescription(group.getDescription()); + ret.setGroupType(group.getGroupType()); + if (group.getGroupId() != null) + { + ret.setGroupId(group.getGroupId().getValue()); + } + else + { + ret.setGroupId(DbKey.NullKey.getValue()); + } + ret.getIntersectGroups().addAll(mapRef(group.getIntersectedGroups())); + return ret; + } + @POST @Path("tsgroup") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) - public Response postTsGroup(ApiTsGroup grp) throws WebAppException, DbException + public Response postTsGroup(ApiTsGroup grp) throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("postTsGroup"); - - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + try (TsGroupDAI dai = getLegacyTimeseriesDB().makeTsGroupDAO()) + { + TsGroup group = map(grp); + dai.writeTsGroup(group); + return Response.status(HttpServletResponse.SC_OK) + .entity(map(group)).build(); + } + catch (DbIoException ex) + { + throw new DbException("Unable to store time series group", ex); + } + } + + static TsGroup map(ApiTsGroup grp) + { + TsGroup ret = new TsGroup(); + ret.setDescription(grp.getDescription()); + ret.setGroupName(grp.getGroupName()); + ret.setGroupType(grp.getGroupType()); + ret.setIntersectedGroups(map(grp.getIntersectGroups())); + if (grp.getGroupId() != null) { - dao.writeGroup(grp); - return ApiHttpUtil.createResponse(grp); + ret.setGroupId(DbKey.createDbKey(grp.getGroupId())); } + else + { + ret.setGroupId(DbKey.NullKey); + } + return ret; + } + + static ArrayList map(ArrayList groupRefs) + { + ArrayList ret = new ArrayList<>(); + for(ApiTsGroupRef ref : groupRefs) + { + TsGroup group = new TsGroup(); + group.setGroupName(ref.getGroupName()); + group.setDescription(ref.getDescription()); + group.setGroupType(ref.getGroupType()); + if (ref.getGroupId() != null) + { + group.setGroupId(DbKey.createDbKey(ref.getGroupId())); + } + else + { + group.setGroupId(DbKey.NullKey); + } + ret.add(group); + } + return ret; } @DELETE @@ -233,15 +552,23 @@ public Response postTsGroup(ApiTsGroup grp) throws WebAppException, DbException @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) - public Response deleteTsGroup(@QueryParam("groupid") Long groupId) throws WebAppException, DbException + public Response deleteTsGroup(@QueryParam("groupid") Long groupId) throws DbException, WebAppException { - Logger.getLogger(ApiConstants.loggerName).fine("delete tsgroup id=" + groupId); - - try (DbInterface dbi = new DbInterface(); - ApiTsDAO dao = new ApiTsDAO(dbi)) + if (groupId == null) + { + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Missing required groupid parameter."); + } + + try (TsGroupDAI dai = getLegacyTimeseriesDB().makeTsGroupDAO()) + { + dai.deleteTsGroup(DbKey.createDbKey(groupId)); + return Response.status(HttpServletResponse.SC_OK) + .entity("tsgroup with ID=" + groupId + " deleted").build(); + } + catch (DbIoException ex) { - dao.deleteGroup(groupId); - return ApiHttpUtil.createResponse("tsgroup with ID=" + groupId + " deleted"); + throw new DbException("Unable to delete time series group", ex); } } -} +} \ No newline at end of file diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/TimeSeriesResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/TimeSeriesResourcesTest.java new file mode 100644 index 00000000..1a40f28a --- /dev/null +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/TimeSeriesResourcesTest.java @@ -0,0 +1,348 @@ +package org.opendcs.odcsapi.res; + +import java.sql.Date; +import java.time.Instant; +import java.util.ArrayList; + +import decodes.cwms.CwmsTsId; +import decodes.db.Site; +import decodes.sql.DbKey; +import decodes.tsdb.CTimeSeries; +import decodes.tsdb.TimeSeriesIdentifier; +import decodes.tsdb.TsGroup; +import ilex.var.TimedVariable; +import opendcs.opentsdb.Interval; +import org.junit.jupiter.api.Test; +import org.opendcs.odcsapi.beans.ApiInterval; +import org.opendcs.odcsapi.beans.ApiTimeSeriesData; +import org.opendcs.odcsapi.beans.ApiTimeSeriesIdentifier; +import org.opendcs.odcsapi.beans.ApiTimeSeriesSpec; +import org.opendcs.odcsapi.beans.ApiTimeSeriesValue; +import org.opendcs.odcsapi.beans.ApiTsGroup; +import org.opendcs.odcsapi.beans.ApiTsGroupRef; + +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 static org.opendcs.odcsapi.res.TimeSeriesResources.dataMap; +import static org.opendcs.odcsapi.res.TimeSeriesResources.map; +import static org.opendcs.odcsapi.res.TimeSeriesResources.mapRef; +import static org.opendcs.odcsapi.res.TimeSeriesResources.specMap; + +final class TimeSeriesResourcesTest +{ + @Test + void testTSIdentifierMap() throws Exception + { + ArrayList identifiers = new ArrayList<>(); + TimeSeriesIdentifier id = new CwmsTsId(); + id.setDescription("Local TimeSeries"); + id.setUniqueString("Davis.Flow.Inst.1Hour.0.GOES"); + Site site = new Site(); + site.setPublicName("Instantaneous Flow TimeSeries"); + site.setActive(true); + site.setLastModifyTime(Date.from(Instant.parse("2021-08-01T00:00:00Z"))); + site.setElevation(100.0); + site.setLocationType("PUMP"); + site.setDescription("Pump located in Davis, CA"); + id.setSite(site); + id.setInterval("hour"); + id.setTableSelector("TS"); + id.setStorageUnits("cms"); + id.setSiteName("Davis pump station"); + id.setDisplayName("Davis Pump Station Flow"); + id.setKey(DbKey.createDbKey(88795L)); + id.setReadTime(55933124L); + identifiers.add(id); + + ArrayList apiIdentifiers = map(identifiers, false); + + assertNotNull(apiIdentifiers); + assertEquals(identifiers.size(), apiIdentifiers.size()); + ApiTimeSeriesIdentifier apiId = apiIdentifiers.get(0); + assertNotNull(apiId); + assertMatch(id, apiId); + } + + @Test + void testCTimeSeriesMap() throws Exception + { + CTimeSeries cts = new CTimeSeries(DbKey.createDbKey(86795L), null, null); + cts.setBriefDescription("Computational TimeSeries"); + cts.setComputationId(DbKey.createDbKey(86775L)); + cts.setInterval("hour"); + cts.addDependentCompId(DbKey.createDbKey(86785L)); + cts.addTaskListRecNum(56); + cts.setDisplayName("TimeSeries for computation"); + TimeSeriesIdentifier id = new CwmsTsId(); + id.setDescription("TimeSeries data used for computation"); + id.setUniqueString("SAC.Flow.Inst.1Hour.0.GOES"); + id.setKey(DbKey.createDbKey(88795L)); + id.setInterval("hour"); + id.setSiteName("Sacramento River"); + id.setStorageUnits("m"); + id.setTableSelector("Test"); + Site site = new Site(); + site.setPublicName("Sacramento River Pump Station"); + site.setActive(true); + site.setLastModifyTime(Date.from(Instant.parse("2021-08-01T00:00:00Z"))); + site.setElevation(100.0); + site.setLocationType("PUMP"); + site.setId(DbKey.createDbKey(885L)); + site.setDescription("Pump located on Sacramento River"); + id.setSite(site); + cts.setTimeSeriesIdentifier(id); + TimedVariable tv = new TimedVariable(Date.from(Instant.parse("2021-08-01T00:00:00Z")), 10.0, 0); + cts.addSample(tv); + tv = new TimedVariable(Date.from(Instant.parse("2021-08-01T01:00:00Z")), 20.0, 0); + cts.addSample(tv); + tv = new TimedVariable(Date.from(Instant.parse("2021-08-01T02:00:00Z")), 30.0, 0); + cts.addSample(tv); + tv = new TimedVariable(Date.from(Instant.parse("2021-08-01T03:00:00Z")), 40.0, 0); + cts.addSample(tv); + + ApiTimeSeriesData apiTimeSeriesData = dataMap(cts, + Date.from(Instant.parse("2021-08-01T00:00:00Z")), + Date.from(Instant.parse("2021-08-01T03:00:00Z"))); + + assertNotNull(apiTimeSeriesData); + assertMatch(cts.getTimeSeriesIdentifier(), apiTimeSeriesData.getTsid()); + ArrayList values = apiTimeSeriesData.getValues(); + assertNotNull(values); + assertEquals(cts.size(), values.size()); + for (int i = 0; i < cts.size(); i++) + { + boolean found = false; + for (int j = 0; j < values.size(); j++) + { + if (cts.sampleAt(i).getTime() == values.get(i).getSampleTime()) + { + assertEquals(cts.sampleAt(i).getTime(), values.get(i).getSampleTime()); + assertEquals(cts.sampleAt(i).getDoubleValue(), values.get(i).getValue()); + assertEquals(cts.sampleAt(i).getFlags(), values.get(i).getFlags()); + found = true; + } + } + assertTrue(found); + } + } + + private void assertMatch(TimeSeriesIdentifier id, ApiTimeSeriesIdentifier apiId) + { + assertEquals(id.getDescription(), apiId.getDescription()); + assertEquals(id.getUniqueString(), apiId.getUniqueString()); + assertEquals(id.getKey().getValue(), apiId.getKey()); + assertEquals(id.getStorageUnits(), apiId.getStorageUnits()); + } + + @Test + void testIntervalMap() + { + ApiInterval apiInterval = new ApiInterval(); + apiInterval.setIntervalId(1234L); + apiInterval.setCalConstant("10"); + apiInterval.setName("Hourly"); + apiInterval.setCalMultilier(2); + + Interval interval = map(apiInterval); + + assertNotNull(interval); + assertEquals(apiInterval.getIntervalId(), interval.getKey().getValue()); + assertEquals(Integer.parseInt(apiInterval.getCalConstant()), interval.getCalConstant()); + assertEquals(apiInterval.getName(), interval.getName()); + assertEquals(apiInterval.getCalMultilier(), interval.getCalMultiplier()); + } + + @Test + void testApiIntervalMap() + { + Interval interval = new Interval("Daily"); + interval.setKey(DbKey.createDbKey(1234L)); + interval.setCalConstant(10); + interval.setCalMultiplier(2); + + ApiInterval apiInterval = map(interval); + + assertNotNull(apiInterval); + assertEquals(interval.getKey().getValue(), apiInterval.getIntervalId()); + assertEquals(String.valueOf(interval.getCalConstant()), apiInterval.getCalConstant()); + assertEquals(interval.getName(), apiInterval.getName()); + assertEquals(interval.getCalMultiplier(), apiInterval.getCalMultilier()); + } + + @Test + void testTSGroupRefMap() + { + ArrayList tsGroups = new ArrayList<>(); + TsGroup tsGroup = new TsGroup(); + tsGroup.setGroupId(DbKey.createDbKey(1234L)); + tsGroup.setGroupName("Pump Group"); + tsGroup.setGroupType("Average"); + tsGroup.setDescription("Average flow data TimeSeries"); + tsGroup.setIsExpanded(false); + ArrayList groups = new ArrayList<>(); + TsGroup tsGroup2 = new TsGroup(); + tsGroup2.setGroupId(DbKey.createDbKey(1235L)); + tsGroup2.setGroupName("Lock Group"); + tsGroup2.setGroupType("Instantaneous"); + tsGroup2.setDescription("Instantaneous flow data TimeSeries"); + groups.add(tsGroup2); + tsGroup.setIntersectedGroups(groups); + tsGroups.add(tsGroup); + + ArrayList apiTsGroupRefs = mapRef(tsGroups); + + assertNotNull(apiTsGroupRefs); + ApiTsGroupRef apiTsGroupRef = apiTsGroupRefs.get(0); + assertNotNull(apiTsGroupRef); + assertEquals(tsGroup.getGroupId().getValue(), apiTsGroupRef.getGroupId()); + assertEquals(tsGroup.getGroupName(), apiTsGroupRef.getGroupName()); + assertEquals(tsGroup.getGroupType(), apiTsGroupRef.getGroupType()); + assertEquals(tsGroup.getDescription(), apiTsGroupRef.getDescription()); + } + + @Test + void testTSGroupMap() + { + TsGroup tsGroup = new TsGroup(); + tsGroup.setGroupId(DbKey.createDbKey(1234L)); + tsGroup.setGroupName("TimeSeries group"); + tsGroup.setGroupType("Instantaneous"); + tsGroup.setDescription("A group of instantaneous time series"); + tsGroup.setIsExpanded(false); + ArrayList groups = new ArrayList<>(); + TsGroup tsGroup2 = new TsGroup(); + tsGroup2.setGroupId(DbKey.createDbKey(1235L)); + tsGroup2.setGroupName("TimeSeries group 2"); + tsGroup2.setGroupType("Average"); + tsGroup2.setDescription("A group of average time series"); + groups.add(tsGroup2); + tsGroup.setIntersectedGroups(groups); + + ApiTsGroup apiTsGroup = map(tsGroup); + + assertNotNull(apiTsGroup); + assertEquals(tsGroup.getGroupId().getValue(), apiTsGroup.getGroupId()); + assertEquals(tsGroup.getGroupName(), apiTsGroup.getGroupName()); + assertEquals(tsGroup.getGroupType(), apiTsGroup.getGroupType()); + assertEquals(tsGroup.getDescription(), apiTsGroup.getDescription()); + assertEquals(tsGroup.getIntersectedGroups().size(), apiTsGroup.getIntersectGroups().size()); + TsGroup intersectGroup = tsGroup.getIntersectedGroups().get(0); + ApiTsGroupRef apiIntersectGroup = apiTsGroup.getIntersectGroups().get(0); + assertNotNull(apiIntersectGroup); + assertNotNull(intersectGroup); + assertEquals(intersectGroup.getGroupId().getValue(), apiIntersectGroup.getGroupId()); + assertEquals(intersectGroup.getGroupName(), apiIntersectGroup.getGroupName()); + assertEquals(intersectGroup.getGroupType(), apiIntersectGroup.getGroupType()); + assertEquals(intersectGroup.getDescription(), apiIntersectGroup.getDescription()); + } + + @Test + void testApiTSGroupMap() + { + ApiTsGroup apiTsGroup = new ApiTsGroup(); + apiTsGroup.setDescription("Basin TimeSeries data group"); + apiTsGroup.setGroupName("Basin group"); + apiTsGroup.setGroupType("Average"); + apiTsGroup.setGroupId(1234L); + + TsGroup tsGroup = map(apiTsGroup); + + assertNotNull(tsGroup); + assertEquals(apiTsGroup.getGroupId(), tsGroup.getGroupId().getValue()); + assertEquals(apiTsGroup.getGroupName(), tsGroup.getGroupName()); + assertEquals(apiTsGroup.getGroupType(), tsGroup.getGroupType()); + assertEquals(apiTsGroup.getDescription(), tsGroup.getDescription()); + } + + @Test + void testGroupRefListMap() + { + ArrayList groups = new ArrayList<>(); + TsGroup tsGroup = new TsGroup(); + tsGroup.setGroupId(DbKey.createDbKey(1234L)); + tsGroup.setGroupName("River Group 1"); + tsGroup.setGroupType("River"); + tsGroup.setDescription("TimeSeries data for river"); + tsGroup.setIsExpanded(false); + groups.add(tsGroup); + TsGroup tsGroup2 = new TsGroup(); + tsGroup2.setGroupId(DbKey.createDbKey(1235L)); + tsGroup2.setGroupName("Delta Group 2"); + tsGroup2.setGroupType("Delta"); + tsGroup2.setDescription("TimeSeries data for delta"); + tsGroup2.setIsExpanded(true); + tsGroup2.setIntersectedGroups(groups); + groups.add(tsGroup2); + + ArrayList apiTsGroupRefs = mapRef(groups); + + assertNotNull(apiTsGroupRefs); + assertEquals(groups.size(), apiTsGroupRefs.size()); + ApiTsGroupRef apiTsGroupRef = apiTsGroupRefs.get(0); + assertNotNull(apiTsGroupRef); + assertEquals(tsGroup.getGroupId().getValue(), apiTsGroupRef.getGroupId()); + assertEquals(tsGroup.getGroupName(), apiTsGroupRef.getGroupName()); + assertEquals(tsGroup.getGroupType(), apiTsGroupRef.getGroupType()); + assertEquals(tsGroup.getDescription(), apiTsGroupRef.getDescription()); + ApiTsGroupRef apiTsGroupRef2 = apiTsGroupRefs.get(1); + assertNotNull(apiTsGroupRef2); + assertEquals(tsGroup2.getGroupId().getValue(), apiTsGroupRef2.getGroupId()); + assertEquals(tsGroup2.getGroupName(), apiTsGroupRef2.getGroupName()); + assertEquals(tsGroup2.getGroupType(), apiTsGroupRef2.getGroupType()); + assertEquals(tsGroup2.getDescription(), apiTsGroupRef2.getDescription()); + } + + @Test + void testApiTimeSeriesSpecMap() throws Exception + { + TimeSeriesIdentifier id = new CwmsTsId(); + id.setDescription("Volume TimeSeries"); + id.setKey(DbKey.createDbKey(88795L)); + id.setInterval("minute"); + id.setSiteName("USACE Test Facility"); + id.setStorageUnits("m3"); + id.setTableSelector("VOL"); + Site site = new Site(); + site.setPublicName("TimeSeries data for USACE"); + site.setActive(true); + site.setLastModifyTime(Date.from(Instant.parse("2021-08-01T00:00:00Z"))); + site.setElevation(100.0); + site.setLocationType("USACE"); + site.setDescription("System Testing Facility"); + id.setSite(site); + id.setUniqueString("USACE_TF.Vol.Avg.1Minute.1.latest"); + + ApiTimeSeriesSpec apiSpec = specMap(id); + + assertNotNull(apiSpec); + assertEquals(id.getDescription(), apiSpec.getTsid().getDescription()); + assertEquals(id.getUniqueString(), apiSpec.getTsid().getUniqueString()); + assertEquals(id.getKey().getValue(), apiSpec.getTsid().getKey()); + assertEquals(id.getSiteName(), apiSpec.getLocation()); + assertEquals(id.getInterval(), apiSpec.getInterval()); + assertEquals(id.getStorageUnits(), apiSpec.getTsid().getStorageUnits()); + } + + @Test + void testApiTimeSeriesGroupRefMap() + { + ArrayList apiTsGroupRefs = new ArrayList<>(); + ApiTsGroupRef apiTsGroupRef = new ApiTsGroupRef(); + apiTsGroupRef.setDescription("TimeSeries data for USBR"); + apiTsGroupRef.setGroupId(1234L); + apiTsGroupRef.setGroupName("USBR rainfall"); + apiTsGroupRef.setGroupType("Average"); + apiTsGroupRefs.add(apiTsGroupRef); + + ArrayList groups = map(apiTsGroupRefs); + assertNotNull(groups); + assertEquals(apiTsGroupRefs.size(), groups.size()); + TsGroup tsGroup = groups.get(0); + assertNotNull(tsGroup); + assertEquals(apiTsGroupRef.getGroupId(), tsGroup.getGroupId().getValue()); + assertEquals(apiTsGroupRef.getGroupName(), tsGroup.getGroupName()); + assertEquals(apiTsGroupRef.getGroupType(), tsGroup.getGroupType()); + assertEquals(apiTsGroupRef.getDescription(), tsGroup.getDescription()); + } +} \ No newline at end of file