diff --git a/Dockerfile b/Dockerfile index 14e370760..25e532b40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,11 +13,11 @@ RUN apk add --no-cache bash RUN mkdir /download && \ cd /download && \ - wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.90/bin/apache-tomcat-9.0.90.tar.gz && \ - echo "e77b47d7ded86da81018d38c4f728f5f804c1a65bb941a138a7989b69c859031e88d113ccf4fc3a409062ee24511fa5ccf15dfad333f570838ee2a36dae23e19 *apache-tomcat-9.0.90.tar.gz" > checksum.txt && \ + wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.91/bin/apache-tomcat-9.0.91.tar.gz && \ + echo "b22054c9141782232a693765d23d944f0f50774af17dd8968331e020b425e71459b5877a7ba8c2121246a5ce47e6b6a31c3f4215ef133e942da45b49cb534948 *apache-tomcat-9.0.91.tar.gz" > checksum.txt && \ sha512sum -c checksum.txt && \ tar xzf apache-tomcat-*tar.gz && \ - mv apache-tomcat-9.0.90 /usr/local/tomcat/ && \ + mv apache-tomcat-9.0.91 /usr/local/tomcat/ && \ cd / && \ rm -rf /download CMD ["/usr/local/tomcat/bin/catalina.sh","run"] 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 80f986530..b1852699e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -26,6 +26,7 @@ import static cwms.cda.api.Controllers.NAME; import cwms.cda.api.DownstreamLocationsGetController; +import cwms.cda.api.location.kind.VirtualOutletController; import cwms.cda.api.LookupTypeController; import cwms.cda.api.StreamController; import cwms.cda.api.StreamLocationController; @@ -64,6 +65,7 @@ import cwms.cda.api.LocationController; import cwms.cda.api.LocationGroupController; import cwms.cda.api.OfficeController; +import cwms.cda.api.location.kind.OutletController; import cwms.cda.api.ParametersController; import cwms.cda.api.PoolController; import cwms.cda.api.ProjectController; @@ -99,6 +101,7 @@ import cwms.cda.api.errors.NotFoundException; import cwms.cda.api.errors.RequiredQueryParameterException; import cwms.cda.api.project.ProjectChildLocationHandler; +import cwms.cda.api.location.kind.VirtualOutletCreateController; import cwms.cda.data.dao.JooqDao; import cwms.cda.formatters.Formats; import cwms.cda.formatters.FormattingException; @@ -509,6 +512,14 @@ protected void configureRoutes() { post(turbineChanges, new TurbineChangesPostController(metrics), requiredRoles); delete(turbineChanges, new TurbineChangesDeleteController(metrics), requiredRoles); + String outletPath = format("/projects/outlets/{%s}", NAME); + String virtualOutletPath = format("/projects/{%s}/{%s}/virtual-outlets/{%s}", Controllers.OFFICE, + Controllers.PROJECT_ID, NAME); + String virtualOutletCreatePath = "/projects/virtual-outlets"; + cdaCrudCache(outletPath, new OutletController(metrics), requiredRoles, 1, TimeUnit.DAYS); + cdaCrudCache(virtualOutletPath, new VirtualOutletController(metrics), requiredRoles, 1, TimeUnit.DAYS); + post(virtualOutletCreatePath, new VirtualOutletCreateController(metrics)); + get("/projects/child-locations/", new ProjectChildLocationHandler(metrics)); cdaCrudCache(format("/projects/{%s}", Controllers.NAME), new ProjectController(metrics), requiredRoles,5, TimeUnit.MINUTES); diff --git a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java index 4361105ae..c65e1834f 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/Controllers.java @@ -41,6 +41,9 @@ import io.javalin.http.Context; import java.time.Instant; import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.jetbrains.annotations.Nullable; public final class Controllers { @@ -212,6 +215,27 @@ public static Timer.Context markAndTime(MetricRegistry registry, String classNam return timer.time(); } + /** + * Returns the first matching query param or the provided default value if no match is found. + * + * @param ctx Request Context + * @param name Name of the query param + * @param aliases Alternative names for the query parameter that could be coming in + * @param clazz Return value type. + * @param defaultValue Value to return if no matching queryParam is found. + * @return value + */ + public static T queryParamAsClass(io.javalin.http.Context ctx, + Class clazz, T defaultValue, String name, String ... aliases) { + List items = new ArrayList<>(); + items.add(name); + if (aliases != null) { + items.addAll(Arrays.asList(aliases)); + } + + return queryParamAsClass(ctx, items.toArray(new String[]{}), clazz, defaultValue); + } + /** * Returns the first matching query param or the provided default value if no match is found. * diff --git a/cwms-data-api/src/main/java/cwms/cda/api/StreamController.java b/cwms-data-api/src/main/java/cwms/cda/api/StreamController.java index 713c64d70..11a613343 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/StreamController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/StreamController.java @@ -45,6 +45,7 @@ import static cwms.cda.api.Controllers.STATUS_200; import static cwms.cda.api.Controllers.STATUS_204; import static cwms.cda.api.Controllers.STATUS_404; +import static cwms.cda.api.Controllers.STREAM_ID_MASK; import static cwms.cda.api.Controllers.UPDATE; import static cwms.cda.api.Controllers.requiredParam; import cwms.cda.data.dao.DeleteRule; @@ -91,6 +92,8 @@ private Timer.Context markAndTime(String subject) { queryParams = { @OpenApiParam(name = OFFICE_MASK, description = "Office id for the reservoir project location " + "associated with the streams."), + @OpenApiParam(name = STREAM_ID_MASK, description = "Specifies the stream-id of the stream to be " + + "retrieved."), @OpenApiParam(name = DIVERTS_FROM_STREAM_ID_MASK, description = "Specifies the stream-id of the " + "stream that the returned streams flow from."), @OpenApiParam(name = FLOWS_INTO_STREAM_ID_MASK, description = "Specifies the stream-id of the " + @@ -110,13 +113,14 @@ private Timer.Context markAndTime(String subject) { @Override public void getAll(@NotNull Context ctx) { String office = ctx.queryParam(OFFICE_MASK); + String streamId = ctx.queryParam(STREAM_ID_MASK); String divertsFromStream = ctx.queryParam(DIVERTS_FROM_STREAM_ID_MASK); String flowsIntoStream = ctx.queryParam(FLOWS_INTO_STREAM_ID_MASK); try (Timer.Context ignored = markAndTime(GET_ALL)) { DSLContext dsl = getDslContext(ctx); StreamDao dao = new StreamDao(dsl); String stationUnits = ctx.queryParamAsClass(STATION_UNIT, String.class).getOrDefault("mi"); - List streams = dao.retrieveStreams(office, divertsFromStream, flowsIntoStream, stationUnits); + List streams = dao.retrieveStreams(office, streamId, divertsFromStream, flowsIntoStream, stationUnits); String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : Formats.JSONV1; ContentType contentType = Formats.parseHeader(formatHeader, Stream.class); diff --git a/cwms-data-api/src/main/java/cwms/cda/api/TimeZoneController.java b/cwms-data-api/src/main/java/cwms/cda/api/TimeZoneController.java index c837bf877..c9fe83aee 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/TimeZoneController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/TimeZoneController.java @@ -18,8 +18,8 @@ import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import cwms.cda.data.dao.TimeZoneDao; -import cwms.cda.data.dto.TimeZone; -import cwms.cda.data.dto.TimeZones; +import cwms.cda.data.dto.TimeZoneId; +import cwms.cda.data.dto.TimeZoneIds; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; import io.javalin.apibuilder.CrudHandler; @@ -28,7 +28,6 @@ import io.javalin.plugin.openapi.annotations.OpenApiParam; 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; @@ -89,7 +88,7 @@ public void getAll(Context ctx) { String format = ctx.queryParamAsClass(FORMAT, String.class).getOrDefault(""); String header = ctx.header(ACCEPT); - ContentType contentType = Formats.parseHeaderAndQueryParm(header, format, TimeZone.class); + ContentType contentType = Formats.parseHeaderAndQueryParm(header, format, TimeZoneId.class); String version = contentType.getParameters() .getOrDefault(VERSION, ""); @@ -98,7 +97,7 @@ public void getAll(Context ctx) { String results; if (format.isEmpty() && !isLegacyVersion) { - TimeZones zones = dao.getTimeZones(); + TimeZoneIds zones = dao.getTimeZones(); results = Formats.format(contentType, zones); ctx.contentType(contentType.toString()); } diff --git a/cwms-data-api/src/main/java/cwms/cda/api/UnitsController.java b/cwms-data-api/src/main/java/cwms/cda/api/UnitsController.java index 83abaa457..c60384d7d 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/UnitsController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/UnitsController.java @@ -18,8 +18,6 @@ import com.codahale.metrics.Timer; import cwms.cda.api.errors.CdaError; import cwms.cda.data.dao.UnitsDao; -import cwms.cda.data.dto.TimeZone; -import cwms.cda.data.dto.TimeZones; import cwms.cda.data.dto.Unit; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; @@ -90,7 +88,7 @@ public void getAll(Context ctx) { String format = ctx.queryParamAsClass(FORMAT, String.class).getOrDefault(""); String header = ctx.header(ACCEPT); - ContentType contentType = Formats.parseHeaderAndQueryParm(header, format, TimeZone.class); + ContentType contentType = Formats.parseHeaderAndQueryParm(header, format, Unit.class); String version = contentType.getParameters() .getOrDefault(VERSION, ""); diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/BaseOutletHandler.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/BaseOutletHandler.java new file mode 100644 index 000000000..edb21b600 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/BaseOutletHandler.java @@ -0,0 +1,51 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.api.Controllers; +import io.javalin.http.Handler; +import static com.codahale.metrics.MetricRegistry.name; +import static cwms.cda.api.Controllers.RESULTS; +import static cwms.cda.api.Controllers.SIZE; + +abstract class BaseOutletHandler implements Handler { + + private final MetricRegistry metrics; + private final Histogram requestResultSize; + + BaseOutletHandler(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + + requestResultSize = this.metrics.histogram(name(className, RESULTS, SIZE)); + } + + protected final Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + protected final void addToHistogram(int value) { + requestResultSize.update(value); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java new file mode 100644 index 000000000..b0fdbde12 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/OutletController.java @@ -0,0 +1,236 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.api.Controllers; +import cwms.cda.api.errors.CdaError; +import cwms.cda.data.dao.JooqDao; +import cwms.cda.data.dao.location.kind.OutletDao; +import cwms.cda.data.dto.location.kind.Outlet; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +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 javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; +import static com.codahale.metrics.MetricRegistry.name; +import static cwms.cda.api.Controllers.*; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +public class OutletController implements CrudHandler { + static final String TAG = "Outlets"; + + private final MetricRegistry metrics; + + private final Histogram requestResultSize; + + public OutletController(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + + requestResultSize = this.metrics.histogram((name(className, RESULTS, SIZE))); + } + + private Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + @OpenApi( + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, type = Formats.JSON) + }, + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided ID already exists. Default: true") + }, + description = "Create CWMS Outlet", + method = HttpMethod.POST, + tags = {OutletController.TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Outlet successfully stored to CWMS.") + } + ) + @Override + public void create(@NotNull Context ctx) { + try (Timer.Context ignored = markAndTime(CREATE)) { + String acceptHeader = ctx.req.getContentType(); + String formatHeader = acceptHeader != null ? acceptHeader : Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, Outlet.class); + Outlet outlet = Formats.parseContent(contentType, ctx.body(), Outlet.class); + outlet.validate(); + boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true); + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + dao.storeOutlet(outlet, failIfExists); + ctx.status(HttpServletResponse.SC_CREATED).json("Created Outlet"); + } + } + + @OpenApi( + queryParams = { + @OpenApiParam(name = OFFICE, description = "Office id for the reservoir project location " + + "associated with the outlets. Defaults to the user session id."), + @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project-id of the " + + "Outlets whose data is to be included in the response."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, content = { + @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, isArray = true, type = Formats.JSON) + }) + }, + description = "Returns matching CWMS Outlet Data for a Reservoir Project.", + tags = {OutletController.TAG} + ) + @Override + public void getAll(@NotNull Context ctx) { + String office = ctx.pathParam(OFFICE); + String projectId = ctx.pathParam(PROJECT_ID); + try (Timer.Context ignored = markAndTime(GET_ALL)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + List outlets = dao.retrieveOutletsForProject(office, projectId); + String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : + Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, Outlet.class); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, outlets, Outlet.class); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + } + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the " + + "Outlet to be created."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be retrieved."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, + content = { + @OpenApiContent(from = Outlet.class, type = Formats.JSONV1), + @OpenApiContent(from = Outlet.class, type = Formats.JSON) + }) + }, + description = "Returns CWMS Outlet Data", + tags = {TAG} + ) + @Override + public void getOne(@NotNull Context ctx, @NotNull String name) { + String office = requiredParam(ctx, OFFICE); + try (Timer.Context ignored = markAndTime(GET_ONE)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + Outlet outlet = dao.retrieveOutlet(office, name); + String header = ctx.header(Header.ACCEPT); + String formatHeader = header != null ? header : Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, Outlet.class); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, outlet); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + } + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of " + + "the outlet to be renamed."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be renamed."), + @OpenApiParam(name = NAME, required = true, description = "Specifies the new outlet location-id."), + }, + description = "Rename CWMS Outlet", + method = HttpMethod.PATCH, + tags = {TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "CWMS Outlet successfully renamed.") + } + ) + @Override + public void update(@NotNull Context ctx, @NotNull String name) { + String office = requiredParam(ctx, OFFICE); + String newOutletId = requiredParam(ctx, NAME); + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + dao.renameOutlet(office, name, newOutletId); + ctx.status(HttpServletResponse.SC_NO_CONTENT).json(name + " successfully renamed to " + newOutletId); + } + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = NAME, description = "Specifies the location-id of the outlet to be" + + " deleted."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the outlet to be deleted."), + @OpenApiParam(name = METHOD, description = "Specifies the delete method used. " + + "Defaults to \"DELETE_KEY\"", + type = JooqDao.DeleteMethod.class) + }, + description = "Delete CWMS Outlet", + method = HttpMethod.DELETE, + tags = {TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Outlet successfully deleted from CWMS."), + @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " + + "inputs provided the outlet was not found.") + } + ) + @Override + public void delete(@NotNull Context ctx, @NotNull String name) { + String office = requiredParam(ctx, OFFICE); + JooqDao.DeleteMethod deleteMethod = queryParamAsClass(ctx, JooqDao.DeleteMethod.class, + JooqDao.DeleteMethod.DELETE_KEY, METHOD); + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + dao.deleteOutlet(office, name, deleteMethod.getRule()); + ctx.status(HttpServletResponse.SC_NO_CONTENT).json(name + " Deleted"); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletController.java new file mode 100644 index 000000000..ad0ce8ff8 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletController.java @@ -0,0 +1,188 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.api.Controllers; +import cwms.cda.api.errors.CdaError; +import cwms.cda.data.dao.JooqDao; +import cwms.cda.data.dao.location.kind.OutletDao; +import cwms.cda.data.dto.location.kind.VirtualOutlet; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +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.OpenApiResponse; +import java.util.List; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; +import static com.codahale.metrics.MetricRegistry.name; +import static cwms.cda.api.Controllers.*; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +public class VirtualOutletController implements CrudHandler { + + private final MetricRegistry metrics; + private final Histogram requestResultSize; + + public VirtualOutletController(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + + requestResultSize = this.metrics.histogram(name(className, RESULTS, SIZE)); + } + + private Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + + @OpenApi(ignore = true) + @Override + public void create(@NotNull Context ctx) { + //Implemented in VirtualOutletCreateController + ctx.status(HttpServletResponse.SC_NOT_IMPLEMENTED).json(CdaError.notImplemented()); + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = OFFICE, description = "Office id for the reservoir project location " + + "associated with the virtual outlets. Defaults to the user session id."), + @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project-id of the " + + "virtual outlets whose data is to be included in the response."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, content = { + @OpenApiContent(from = VirtualOutlet.class, isArray = true, type = Formats.JSONV1), + @OpenApiContent(from = VirtualOutlet.class, isArray = true, type = Formats.JSON) + }) + }, + description = "Returns matching CWMS Virtual Outlet Data for a Reservoir Project.", + tags = {OutletController.TAG} + ) + @Override + public void getAll(@NotNull Context ctx) { + String office = ctx.pathParam(OFFICE); + String projectId = ctx.pathParam(PROJECT_ID); + try (Timer.Context ignored = markAndTime(GET_ALL)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + List outlets = dao.retrieveVirtualOutletsForProject(office, projectId); + String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : + Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, VirtualOutlet.class); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, outlets, VirtualOutlet.class); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + } + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the virtual outlet to be retrieved."), + @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project-id of the " + + "virtual outlets whose data is to be included in the response."), + @OpenApiParam(name = NAME, required = true, description = "Specifies the location-id of the " + + "virtual outlet to be created."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, + content = { + @OpenApiContent(from = VirtualOutlet.class, type = Formats.JSONV1), + @OpenApiContent(from = VirtualOutlet.class, type = Formats.JSON) + }) + }, + description = "Returns CWMS Virtual Outlet Data", + tags = {OutletController.TAG} + ) + @Override + public void getOne(@NotNull Context ctx, @NotNull String name) { + String office = ctx.pathParam(OFFICE); + String projectId = ctx.pathParam(PROJECT_ID); + try (Timer.Context ignored = markAndTime(GET_ALL)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + VirtualOutlet outlet = dao.retrieveVirtualOutlet(office, projectId, name); + String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : + Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, VirtualOutlet.class); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, outlet); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + } + } + + @OpenApi(ignore = true) + @Override + public void update(@NotNull Context ctx, @NotNull String s) { + ctx.status(HttpServletResponse.SC_NOT_IMPLEMENTED).json(CdaError.notImplemented()); + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the owning office of " + + "the virtual outlet to be deleted."), + @OpenApiParam(name = PROJECT_ID, required = true, description = "Specifies the project id of " + + "the virtual outlet to be deleted."), + @OpenApiParam(name = NAME, description = "Specifies the location id of " + + "the virtual outlet to be deleted"), + }, + queryParams = { + @OpenApiParam(name = METHOD, description = "Specifies the delete method used. " + + "Defaults to \"DELETE_KEY\"", + type = JooqDao.DeleteMethod.class) + }, + description = "Delete CWMS Virtual Outlet", + method = HttpMethod.DELETE, + tags = {OutletController.TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Virtual Outlet successfully deleted from CWMS."), + @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " + + "inputs provided the virtual outlet was not found.") + } + ) + @Override + public void delete(@NotNull Context ctx, @NotNull String name) { + String office = ctx.pathParam(OFFICE); + String projectId = ctx.pathParam(PROJECT_ID); + JooqDao.DeleteMethod deleteMethod = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class) + .getOrDefault(JooqDao.DeleteMethod.DELETE_KEY); + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + dao.deleteVirtualOutlet(office, projectId, name, deleteMethod.getRule()); + ctx.status(HttpServletResponse.SC_NO_CONTENT).json(name + " Deleted"); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletCreateController.java b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletCreateController.java new file mode 100644 index 000000000..d83b69872 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/location/kind/VirtualOutletCreateController.java @@ -0,0 +1,84 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.data.dao.location.kind.OutletDao; +import cwms.cda.data.dto.location.kind.VirtualOutlet; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +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 javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; +import static cwms.cda.api.Controllers.*; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +public class VirtualOutletCreateController extends BaseOutletHandler { + + public VirtualOutletCreateController(MetricRegistry metrics) { + super(metrics); + } + + @OpenApi( + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = VirtualOutlet.class, type = Formats.JSONV1), + @OpenApiContent(from = VirtualOutlet.class, type = Formats.JSON) + }, + required = true), + queryParams = { + @OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class, + description = "Create will fail if provided ID already exists. Default: true"), + }, + description = "Create CWMS Virtual Outlet", + method = HttpMethod.POST, + tags = {OutletController.TAG}, + responses = { + @OpenApiResponse(status = STATUS_204, description = "Virtual Outlet successfully stored to CWMS.") + } + ) + @Override + public void handle(@NotNull Context ctx) throws Exception { + try (Timer.Context ignored = markAndTime(CREATE)) { + String acceptHeader = ctx.req.getContentType(); + String formatHeader = acceptHeader != null ? acceptHeader : Formats.JSONV1; + ContentType contentType = Formats.parseHeader(formatHeader, VirtualOutlet.class); + VirtualOutlet virtualOutlet = Formats.parseContent(contentType, ctx.body(), VirtualOutlet.class); + boolean failIfExists = queryParamAsClass(ctx, Boolean.class, true, FAIL_IF_EXISTS); + DSLContext dsl = getDslContext(ctx); + OutletDao dao = new OutletDao(dsl); + + String officeId = virtualOutlet.getProjectId().getOfficeId(); + String projectId = virtualOutlet.getProjectId().getName(); + String virtualOutletId = virtualOutlet.getVirtualOutletId().getName(); + dao.storeVirtualOutlet(officeId, projectId, virtualOutletId, virtualOutlet.getVirtualRecords(), failIfExists); + ctx.status(HttpServletResponse.SC_CREATED).json("Created Outlet"); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/Dao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/Dao.java index 19dc4c41d..0fc01a960 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/Dao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/Dao.java @@ -26,11 +26,14 @@ import static usace.cwms.db.jooq.codegen.tables.AV_DB_CHANGE_LOG.AV_DB_CHANGE_LOG; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import cwms.cda.data.dto.CwmsDTO; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.impl.DSL; @@ -42,16 +45,18 @@ public abstract class Dao { public static final int CWMS_21_1_1 = 210101; public static final int CWMS_23_03_16 = 230316; + public static final String PROP_BASE = "cwms.cda.data.dao.dao"; + public static final String VERSION_NAME = "version"; + private static final Cache versionCache = CacheBuilder.newBuilder() + .maximumSize(Integer.getInteger(PROP_BASE + "." + VERSION_NAME + + ".maxSize", 8)) + .expireAfterWrite(Integer.getInteger(PROP_BASE + "." + VERSION_NAME + + ".expireAfterSeconds", 300), TimeUnit.SECONDS) + .build(); + @SuppressWarnings("unused") protected DSLContext dsl; - /* We only need this once per instance of a DAO. - * would probably be good to cache at a larger level but - * this does strike a balance between performance and picking - * up new database features in real-time. - */ - private Integer theDbVersion = null; - protected Dao(DSLContext dsl) { this.dsl = dsl; } @@ -66,14 +71,22 @@ private static String getVersion(DSLContext dsl) { } public int getDbVersion() { - if (theDbVersion == null) { - String version = getVersion(dsl); - String[] parts = version.split("\\."); - theDbVersion = Integer.parseInt(parts[0]) * 10000 - + Integer.parseInt(parts[1]) * 100 - + Integer.parseInt(parts[2]); + Integer cachedValue = versionCache.getIfPresent(VERSION_NAME); + if (cachedValue == null) { + int newValue = versionAsInteger(getVersion(dsl)); + versionCache.put(VERSION_NAME, newValue); + return newValue; + } else { + return cachedValue; } - return theDbVersion; // the unboxing is still cheaper than a database call (the logging was nuts.) + } + + private static int versionAsInteger(String version) { + String[] parts = version.split("\\."); + + return Integer.parseInt(parts[0]) * 10000 + + Integer.parseInt(parts[1]) * 100 + + Integer.parseInt(parts[2]); } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/StreamDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/StreamDao.java index 2b82f2e72..0537aba7d 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/StreamDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/StreamDao.java @@ -67,10 +67,10 @@ public StreamDao(DSLContext dsl) { * @param stationUnits - the station units used for stations and length of stream * @return a list of streams */ - public List retrieveStreams(String officeIdMask, String divertsFromStreamIdMask, String flowsIntStreamIdMask, String stationUnits) { + public List retrieveStreams(String officeIdMask, String streamIdMask, String divertsFromStreamIdMask, String flowsIntStreamIdMask, String stationUnits) { return connectionResult(dsl, conn -> { setOffice(conn, officeIdMask); - Result records = CWMS_STREAM_PACKAGE.call_CAT_STREAMS(DSL.using(conn).configuration(), null, + Result records = CWMS_STREAM_PACKAGE.call_CAT_STREAMS(DSL.using(conn).configuration(), streamIdMask, stationUnits, null, flowsIntStreamIdMask, null, null, null, divertsFromStreamIdMask, null, null, null, null, null, null, diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesDaoImpl.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesDaoImpl.java index c2f13a5cf..62706a58e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesDaoImpl.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeSeriesDaoImpl.java @@ -29,6 +29,8 @@ import cwms.cda.data.dto.VerticalDatumInfo; import cwms.cda.data.dto.catalog.CatalogEntry; import cwms.cda.data.dto.catalog.TimeseriesCatalogEntry; +import cwms.cda.formatters.FormattingException; +import cwms.cda.formatters.xml.XMLv1; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Connection; @@ -52,8 +54,6 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; -import cwms.cda.formatters.FormattingException; -import cwms.cda.formatters.xml.XMLv1; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jooq.CommonTableExpression; @@ -75,13 +75,9 @@ import org.jooq.TableField; import org.jooq.TableLike; import org.jooq.TableOnConditionStep; -import org.jooq.WindowOrderByStep; -import org.jooq.WindowSpecification; -import org.jooq.WindowSpecificationRowsStep; import org.jooq.conf.ParamType; import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; import usace.cwms.db.dao.ifc.ts.CwmsDbTs; import usace.cwms.db.dao.util.OracleTypeMap; import usace.cwms.db.dao.util.services.CwmsDbServiceLookup; @@ -294,13 +290,7 @@ public TimeSeries getTimeseries(String page, int pageSize, String names, String trim, startInclusive, endInclusive, previous, next, versionDateMilli, maxVersion, officeId); - Field tzName; - if (this.getDbVersion() >= Dao.CWMS_21_1_1) { - tzName = AV_CWMS_TS_ID2.TIME_ZONE_ID; - } else { - tzName = DSL.inline(null, SQLDataType.VARCHAR); - } - + Field tzName = AV_CWMS_TS_ID2.TIME_ZONE_ID; Field totalField; if (total != null) { @@ -501,7 +491,7 @@ public Catalog getTimeSeriesCatalog(String page, int pageSize, CatalogRequestPar String cursorOffice = null; Catalog.CatalogPage catPage = null; if (page == null || page.isEmpty()) { - CommonTableExpression limiter = buildWithClause(inputParams, buildWhereConditions(inputParams), new ArrayList(), pageSize, true); + CommonTableExpression limiter = buildWithClause(inputParams, buildWhereConditions(inputParams), new ArrayList<>(), pageSize, true); SelectJoinStep> totalQuery = dsl.with(limiter) .select(countDistinct(limiter.field(AV_CWMS_TS_ID.AV_CWMS_TS_ID.TS_CODE))) .from(limiter); @@ -566,9 +556,9 @@ public Catalog getTimeSeriesCatalog(String page, int pageSize, CatalogRequestPar .units(row.get(AV_CWMS_TS_ID.AV_CWMS_TS_ID.UNIT_ID)) .interval(row.get(AV_CWMS_TS_ID.AV_CWMS_TS_ID.INTERVAL_ID)) .intervalOffset(row.get(AV_CWMS_TS_ID.AV_CWMS_TS_ID.INTERVAL_UTC_OFFSET)); - if (this.getDbVersion() > Dao.CWMS_21_1_1) { - builder.timeZone(row.get("TIME_ZONE_ID", String.class)); - } + + builder.timeZone(row.get("TIME_ZONE_ID", String.class)); + if (params.isIncludeExtents()) { builder.withExtents(new ArrayList<>()); } @@ -628,9 +618,8 @@ public Catalog getTimeSeriesCatalog(String page, int pageSize, CatalogRequestPar cwmsTsIdFields.add(AV_CWMS_TS_ID.AV_CWMS_TS_ID.UNIT_ID); cwmsTsIdFields.add(AV_CWMS_TS_ID.AV_CWMS_TS_ID.INTERVAL_ID); cwmsTsIdFields.add(AV_CWMS_TS_ID.AV_CWMS_TS_ID.INTERVAL_UTC_OFFSET); - if (this.getDbVersion() >= Dao.CWMS_21_1_1) { - cwmsTsIdFields.add(AV_CWMS_TS_ID.AV_CWMS_TS_ID.TIME_ZONE_ID); - } + cwmsTsIdFields.add(AV_CWMS_TS_ID.AV_CWMS_TS_ID.TIME_ZONE_ID); + return cwmsTsIdFields; } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeZoneDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeZoneDao.java index c5ba4ed2b..565122b86 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeZoneDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/TimeZoneDao.java @@ -1,13 +1,12 @@ package cwms.cda.data.dao; -import cwms.cda.data.dto.TimeZone; -import cwms.cda.data.dto.TimeZones; +import cwms.cda.data.dto.TimeZoneId; +import cwms.cda.data.dto.TimeZoneIds; import org.jooq.DSLContext; import org.jooq.Record1; import usace.cwms.db.jooq.codegen.packages.CWMS_CAT_PACKAGE; import usace.cwms.db.jooq.codegen.tables.MV_TIME_ZONE; -import java.util.List; import java.util.stream.Collectors; public class TimeZoneDao extends JooqDao { @@ -20,13 +19,13 @@ public String getTimeZones(String format) { return CWMS_CAT_PACKAGE.call_RETRIEVE_TIME_ZONES_F(dsl.configuration(), format); } - public TimeZones getTimeZones() + public TimeZoneIds getTimeZones() { - return new TimeZones(dsl.select(MV_TIME_ZONE.MV_TIME_ZONE.TIME_ZONE_NAME) + return new TimeZoneIds(dsl.select(MV_TIME_ZONE.MV_TIME_ZONE.TIME_ZONE_NAME) .from(MV_TIME_ZONE.MV_TIME_ZONE) .stream() .map(Record1::component1) - .map(TimeZone::new) + .map(TimeZoneId::new) .collect(Collectors.toList())); } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java index 6ce62f3d9..cc7899e01 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/location/kind/OutletDao.java @@ -20,24 +20,21 @@ package cwms.cda.data.dao.location.kind; -import com.google.common.flogger.FluentLogger; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.JooqDao; import cwms.cda.data.dao.LocationGroupDao; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; import cwms.cda.data.dto.LocationGroup; -import cwms.cda.data.dto.location.kind.CompoundOutletRecord; import cwms.cda.data.dto.location.kind.Outlet; import cwms.cda.data.dto.location.kind.ProjectStructure; +import cwms.cda.data.dto.location.kind.VirtualOutlet; +import cwms.cda.data.dto.location.kind.VirtualOutletRecord; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.jooq.Configuration; import org.jooq.DSLContext; -import org.jooq.exception.DataAccessException; import org.jooq.impl.DSL; import usace.cwms.db.dao.util.OracleTypeMap; import usace.cwms.db.jooq.codegen.packages.CWMS_OUTLET_PACKAGE; @@ -47,160 +44,149 @@ import usace.cwms.db.jooq.codegen.udt.records.STR_TAB_TAB_T; public class OutletDao extends JooqDao { - private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); - private static final String RATING_LOC_GROUP_CATEGORY = "Rating"; public OutletDao(DSLContext dsl) { super(dsl); } - public List retrieveOutletsForProject(String projectId, String officeId) { + public List retrieveOutletsForProject(String officeId, String projectId) { return connectionResult(dsl, conn -> { + setOffice(conn, officeId); Configuration config = DSL.using(conn).configuration(); - Map> compoundOutlets = retrieveCompoundOutletsForProject(config, - projectId, - officeId); LOCATION_REF_T locRef = LocationUtil.getLocationRef(projectId, officeId); LocationGroupDao locGroupDao = new LocationGroupDao(dsl); - List groups = locGroupDao.getLocationGroups(officeId, RATING_LOC_GROUP_CATEGORY); + List groups = locGroupDao.getLocationGroups(officeId, Outlet.RATING_LOC_GROUP_CATEGORY); return CWMS_OUTLET_PACKAGE.call_RETRIEVE_OUTLETS(config, locRef) .stream() - .map(struct -> mapToOutlet(struct, compoundOutlets, groups)) + .map(struct -> mapToOutlet(struct, groups)) .collect(Collectors.toList()); }); } - public Outlet retrieveOutlet(String locationId, String officeId) { + public Outlet retrieveOutlet(String officeId, String locationId) { return connectionResult(dsl, conn -> { LOCATION_REF_T locRef = LocationUtil.getLocationRef(locationId, officeId); Configuration config = DSL.using(conn).configuration(); PROJECT_STRUCTURE_OBJ_T outletStruct = CWMS_OUTLET_PACKAGE.call_RETRIEVE_OUTLET(config, locRef); - - CwmsId projectId = LocationUtil.getLocationIdentifier(outletStruct.getPROJECT_LOCATION_REF()); - - Map> records = new HashMap<>(); - try { - List compoundOutletRecords = retrieveCompoundOutlet(config, locationId, - projectId.getName(), - officeId); - records.put(locationId, compoundOutletRecords); - } catch (DataAccessException e) { - if (isNotFound(e)) { - LOGGER.atFinest().withCause(e).log("No compound outlet records for outlet " + locationId); - } else { - throw e; - } - } - - + LocationGroupDao locGroupDao = new LocationGroupDao(dsl); - List groups = locGroupDao.getLocationGroups(officeId, RATING_LOC_GROUP_CATEGORY); + List groups = locGroupDao.getLocationGroups(officeId, Outlet.RATING_LOC_GROUP_CATEGORY); - return mapToOutlet(outletStruct, records, groups); + return mapToOutlet(outletStruct, groups); }); } - private String getRatingGroupId(String locationId, List groups) { + private CwmsId getRatingGroupId(String locationId, List groups) { return groups.stream() .filter(group -> group.getAssignedLocations() .stream() .anyMatch(loc -> loc.getLocationId().equalsIgnoreCase(locationId))) .findFirst() - .map(LocationGroup::getId) + .map(locGroup -> new CwmsId.Builder().withName(locGroup.getId()) + .withOfficeId(locGroup.getOfficeId()) + .build()) .orElse(null); } - public void storeOutlet(ProjectStructure outlet, String ratingGroup, boolean failIfExists) { + public void storeOutlet(Outlet outlet, boolean failIfExists) { connection(dsl, conn -> { + setOffice(conn, outlet.getProjectId().getOfficeId()); PROJECT_STRUCTURE_OBJ_T structure = mapToProjectStructure(outlet); - CWMS_OUTLET_PACKAGE.call_STORE_OUTLET(DSL.using(conn).configuration(), structure, ratingGroup, + CWMS_OUTLET_PACKAGE.call_STORE_OUTLET(DSL.using(conn).configuration(), structure, outlet.getRatingGroupId().getName(), OracleTypeMap.formatBool(failIfExists)); }); } - public void deleteOutlet(String locationId, String officeId, DeleteRule deleteRule) { - connection(dsl, conn -> CWMS_OUTLET_PACKAGE.call_DELETE_OUTLET(DSL.using(conn).configuration(), locationId, - deleteRule.getRule(), officeId)); - } - - private Map> retrieveCompoundOutletsForProject(Configuration config, - String projectId, - String officeId) { - Map> recordMap = new HashMap<>(); - //projectId and officeId are used as a mask in RETRIEVE_COMPOUND_OUTLETS, however this usage expects a specific - //id, since retrieveOutlets does not use a mask. - STR_TAB_TAB_T tabs = CWMS_OUTLET_PACKAGE.call_RETRIEVE_COMPOUND_OUTLETS(config, projectId, officeId); - - if (!tabs.isEmpty()) { - STR_TAB_T tab = tabs.get(0); - //This table has a minimum of two columns: office, and project location - //Everything else after that is a compound outlet id. This requires an additional retrieval to determine - //the actual compound outlet records for each outlet. - for (int i = 2; i < tab.size(); i++) { - String compoundOutletId = tab.get(i); - try { - List compoundOutletRecords = retrieveCompoundOutlet(config, compoundOutletId, - projectId, officeId); - recordMap.put(compoundOutletId, compoundOutletRecords); - } catch (DataAccessException e) { - if (isNotFound(e)) { - LOGGER.atFinest().withCause(e).log("No compound outlet records for outlet " + compoundOutletId); - } else { - throw e; - } + public void deleteOutlet(String officeId, String locationId, DeleteRule deleteRule) { + connection(dsl, conn -> { + setOffice(conn, officeId); + CWMS_OUTLET_PACKAGE.call_DELETE_OUTLET(DSL.using(conn).configuration(), locationId, deleteRule.getRule(), + officeId); + }); + } + + public List retrieveVirtualOutletsForProject(String officeId, String projectId) { + return connectionResult(dsl, conn -> { + Configuration config = DSL.using(conn).configuration(); + List output = new ArrayList<>(); + //projectId and officeId are used as a mask in RETRIEVE_COMPOUND_OUTLETS, however this usage expects a specific + //id, since retrieveOutlets does not use a mask. + STR_TAB_TAB_T tabs = CWMS_OUTLET_PACKAGE.call_RETRIEVE_COMPOUND_OUTLETS(config, projectId, officeId); + + if (!tabs.isEmpty()) { + STR_TAB_T tab = tabs.get(0); + //This table has a minimum of two columns: office, and project location + //Everything else after that is a virtual outlet id. This requires an additional retrieval to determine + //the actual virtual outlet records for each outlet. + for (int i = 2; i < tab.size(); i++) { + String virtualOutletId = tab.get(i); + VirtualOutlet virtualOutlet = retrieveVirtualOutlet(config, virtualOutletId, projectId, + officeId); + output.add(virtualOutlet); } } - } - return recordMap; + return output; + }); + } + + public VirtualOutlet retrieveVirtualOutlet(String officeId, String projectId, String virtualOutletId) { + return connectionResult(dsl, conn -> { + Configuration config = DSL.using(conn).configuration(); + return retrieveVirtualOutlet(config, virtualOutletId, projectId, officeId); + }); } - private List retrieveCompoundOutlet(Configuration config, String compoundOutletId, - String projectId, String officeId) { - return CWMS_OUTLET_PACKAGE.call_RETRIEVE_COMPOUND_OUTLET(config, compoundOutletId, projectId, officeId) - .stream() - .map(ArrayList::new) - .map(table -> mapToCompoundRecord(table, officeId)) - .collect(Collectors.toList()); + private VirtualOutlet retrieveVirtualOutlet(Configuration config, String virtualOutletId, String projectId, + String officeId) { + List outletRecords = CWMS_OUTLET_PACKAGE.call_RETRIEVE_COMPOUND_OUTLET(config, virtualOutletId, + projectId, officeId) + .stream() + .map(ArrayList::new) + .map(table -> mapToVirtualRecord(table, officeId)) + .collect(Collectors.toList()); + CwmsId.Builder builder = new CwmsId.Builder().withOfficeId(officeId); + + return new VirtualOutlet.Builder().withVirtualOutletId(builder.withName(virtualOutletId).build()) + .withProjectId(builder.withName(projectId).build()) + .withVirtualRecords(outletRecords) + .build(); } - public void storeCompoundOutlet(String projectId, String compoundOutletId, String officeId, - List records, boolean failIfExists) { - List> compoundOutlets = records.stream() - .map(this::mapCompoundRecords) + public void storeVirtualOutlet(String officeId, String projectId, String virtualOutletId, + List records, boolean failIfExists) { + List> virtualOutlets = records.stream() + .map(this::mapVirtualRecords) .collect(Collectors.toList()); STR_TAB_TAB_T outlets = new STR_TAB_TAB_T( - compoundOutlets.stream().map(STR_TAB_T::new).collect(Collectors.toList())); - connection(dsl, - conn -> CWMS_OUTLET_PACKAGE.call_STORE_COMPOUND_OUTLET(DSL.using(conn).configuration(), projectId, - compoundOutletId, outlets, - OracleTypeMap.formatBool(failIfExists), - officeId)); + virtualOutlets.stream().map(STR_TAB_T::new).collect(Collectors.toList())); + connection(dsl, conn -> { + setOffice(conn, officeId); + CWMS_OUTLET_PACKAGE.call_STORE_COMPOUND_OUTLET(DSL.using(conn).configuration(), projectId, virtualOutletId, + outlets, OracleTypeMap.formatBool(failIfExists), officeId); + }); } - public void deleteCompoundOutlet(String projectId, String compoundOutletId, String officeId, - DeleteRule deleteRule) { - connection(dsl, - conn -> CWMS_OUTLET_PACKAGE.call_DELETE_COMPOUND_OUTLET(DSL.using(conn).configuration(), projectId, - compoundOutletId, deleteRule.getRule(), - officeId)); + public void deleteVirtualOutlet(String officeId, String projectId, String virtualOutletId, DeleteRule deleteRule) { + connection(dsl, conn -> { + setOffice(conn, officeId); + CWMS_OUTLET_PACKAGE.call_DELETE_COMPOUND_OUTLET(DSL.using(conn).configuration(), projectId, + virtualOutletId, deleteRule.getRule(), officeId); + }); } private Outlet mapToOutlet(PROJECT_STRUCTURE_OBJ_T projectStructure, - Map> compoundOutletsMap, List groups) { Location location = LocationUtil.getLocation(projectStructure.getSTRUCTURE_LOCATION()); - String ratingGroupId = getRatingGroupId(location.getName(), groups); + CwmsId ratingGroupId = getRatingGroupId(location.getName(), groups); CwmsId projectId = LocationUtil.getLocationIdentifier(projectStructure.getPROJECT_LOCATION_REF()); - List compoundOutletRecords = compoundOutletsMap.get(location.getName()); return new Outlet.Builder().withLocation(location) .withProjectId(projectId) .withRatingGroupId(ratingGroupId) - .withCompoundOutletRecords(compoundOutletRecords) .build(); } @@ -211,7 +197,7 @@ private PROJECT_STRUCTURE_OBJ_T mapToProjectStructure(ProjectStructure outlet) { return output; } - private CompoundOutletRecord mapToCompoundRecord(List table, String officeId) { + private VirtualOutletRecord mapToVirtualRecord(List table, String officeId) { List downstreamOutlets = new ArrayList<>(); String outlet = null; for (int i = 1; i < table.size(); i++) { @@ -223,13 +209,13 @@ private CompoundOutletRecord mapToCompoundRecord(List table, String offi if (!table.isEmpty()) { outlet = table.get(0); } - return new CompoundOutletRecord.Builder().withOutletId( + return new VirtualOutletRecord.Builder().withOutletId( new CwmsId.Builder().withName(outlet).withOfficeId(officeId).build()) - .withDownstreamOutletIds(downstreamOutlets) - .build(); + .withDownstreamOutletIds(downstreamOutlets) + .build(); } - private List mapCompoundRecords(CompoundOutletRecord outletRecord) { + private List mapVirtualRecords(VirtualOutletRecord outletRecord) { List output = outletRecord.getDownstreamOutletIds() .stream() .map(CwmsId::getName) @@ -237,4 +223,11 @@ private List mapCompoundRecords(CompoundOutletRecord outletRecord) { output.add(0, outletRecord.getOutletId().getName()); return output; } + + public void renameOutlet(String officeId, String oldOutletId, String newOutletId) { + connection(dsl, conn -> { + setOffice(conn, officeId); + CWMS_OUTLET_PACKAGE.call_RENAME_OUTLET(DSL.using(conn).configuration(), oldOutletId, newOutletId, officeId); + }); + } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/LookupType.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/LookupType.java index 61529cc05..6835aaf66 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/LookupType.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/LookupType.java @@ -37,26 +37,19 @@ @JsonDeserialize(builder = LookupType.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public final class LookupType extends CwmsDTOBase { - - @JsonProperty(required = true) - private final String officeId; +public final class LookupType extends CwmsDTO { @JsonProperty(required = true) private final String displayValue; private final String tooltip; private final boolean active; private LookupType(Builder builder) { - this.officeId = builder.officeId; + super(builder.officeId); this.displayValue = builder.displayValue; this.tooltip = builder.tooltip; this.active = builder.active; } - public String getOfficeId() { - return officeId; - } - public String getDisplayValue() { return displayValue; } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/Property.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/Property.java index cc666d041..d9f5a5db8 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/Property.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/Property.java @@ -41,29 +41,23 @@ @JsonDeserialize(builder = Property.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public final class Property extends CwmsDTOBase { +public final class Property extends CwmsDTO { @JsonProperty(required = true) private final String category; @JsonProperty(required = true) private final String name; - @JsonProperty(required = true) - private final String officeId; private final String value; private final String comment; // added comment field private Property(Builder builder) { + super(builder.officeId); this.category = builder.category; this.name = builder.name; - this.officeId = builder.officeId; this.value = builder.value; this.comment = builder.comment; // included comment in constructor } - public String getOfficeId() { - return officeId; - } - public String getName() { return name; } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZone.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneId.java similarity index 92% rename from cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZone.java rename to cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneId.java index 2c7a6dfa8..9f68753db 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZone.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneId.java @@ -21,15 +21,15 @@ @FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) @FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public final class TimeZone extends CwmsDTOBase { +public final class TimeZoneId extends CwmsDTOBase { @JsonProperty(required = true) private String timeZone; - public TimeZone() { + public TimeZoneId() { super(); } - public TimeZone(String timeZone) { + public TimeZoneId(String timeZone) { this(); this.timeZone = timeZone; } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZones.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneIds.java similarity index 74% rename from cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZones.java rename to cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneIds.java index 64759d9ca..ab3e23292 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZones.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneIds.java @@ -7,6 +7,7 @@ package cwms.cda.data.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -21,23 +22,24 @@ @FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON}) @FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML}) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public final class TimeZones extends CwmsDTOBase +public final class TimeZoneIds extends CwmsDTOBase { - private List timeZones; + @JsonProperty("time-zones") + private List timeZoneIds; @SuppressWarnings("unused") - private TimeZones() + private TimeZoneIds() { // for JAXB to handle marshalling } - public TimeZones(List timeZones) + public TimeZoneIds(List timeZoneIds) { - this.timeZones = timeZones; + this.timeZoneIds = timeZoneIds; } - public List getTimeZones() + public List getTimeZones() { - return timeZones; + return timeZoneIds; } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Outlet.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Outlet.java index da7bee162..1f8fbc648 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Outlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/Outlet.java @@ -26,51 +26,43 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonNaming; -import cwms.cda.data.dto.CwmsDTOValidator; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; import cwms.cda.formatters.Formats; import cwms.cda.formatters.annotations.FormattableWith; import cwms.cda.formatters.json.JsonV1; -import java.util.ArrayList; -import java.util.List; @FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) @JsonDeserialize(builder = Outlet.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -@JsonPropertyOrder({"projectId", "location"}) -public class Outlet extends ProjectStructure { - private final List compoundOutletRecords = new ArrayList<>(); - private final String ratingGroupId; +@JsonPropertyOrder({"projectId", "location", "ratingCatId", "ratingGroupId"}) +public final class Outlet extends ProjectStructure { + public static final String RATING_LOC_GROUP_CATEGORY = "Rating"; + private final CwmsId ratingCategoryId; + private final CwmsId ratingGroupId; private Outlet(Builder builder) { super(builder.projectId, builder.location); ratingGroupId = builder.ratingGroupId; - if (builder.compoundOutletRecords != null) { - compoundOutletRecords.addAll(builder.compoundOutletRecords); - } - } - - public List getCompoundOutletRecords() { - return new ArrayList<>(compoundOutletRecords); + ratingCategoryId = new CwmsId.Builder().withOfficeId(builder.projectId.getOfficeId()) + .withName(RATING_LOC_GROUP_CATEGORY) + .build(); } - public String getRatingGroupId() { + public CwmsId getRatingGroupId() { return ratingGroupId; } - @Override - protected void validateInternal(CwmsDTOValidator validator) { - super.validateInternal(validator); - validator.validateCollection(compoundOutletRecords); + public CwmsId getRatingCategoryId() { + return ratingCategoryId; } + @JsonIgnoreProperties(value = {"rating-category-id"}) public static final class Builder { private CwmsId projectId; private Location location; - private List compoundOutletRecords; - private String ratingGroupId; + private CwmsId ratingGroupId; public Builder() { } @@ -78,7 +70,6 @@ public Builder() { public Builder(Outlet outlet) { projectId = outlet.getProjectId(); location = outlet.getLocation(); - compoundOutletRecords = new ArrayList<>(outlet.getCompoundOutletRecords()); ratingGroupId = outlet.getRatingGroupId(); } @@ -96,12 +87,7 @@ public Builder withLocation(Location location) { return this; } - public Builder withCompoundOutletRecords(List compoundOutletRecords) { - this.compoundOutletRecords = compoundOutletRecords; - return this; - } - - public Builder withRatingGroupId(String ratingGroupId) { + public Builder withRatingGroupId(CwmsId ratingGroupId) { this.ratingGroupId = ratingGroupId; return this; } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutlet.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutlet.java new file mode 100644 index 000000000..fc50f328b --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutlet.java @@ -0,0 +1,101 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonNaming; +import cwms.cda.api.errors.FieldException; +import cwms.cda.data.dto.CwmsDTOBase; +import cwms.cda.data.dto.CwmsDTOValidator; +import cwms.cda.data.dto.CwmsId; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV1; +import java.util.ArrayList; +import java.util.List; + +@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) +@JsonDeserialize(builder = VirtualOutlet.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@JsonPropertyOrder({"projectId", "virtualOutletId", "virtualRecords"}) +public final class VirtualOutlet extends CwmsDTOBase { + @JsonProperty(required = true) + private final CwmsId projectId; + @JsonProperty(required = true) + private final CwmsId virtualOutletId; + private final List virtualRecords = new ArrayList<>(); + + public VirtualOutlet(Builder builder) { + this.projectId = builder.projectId; + this.virtualOutletId = builder.virtualOutletId; + if (builder.virtualRecords != null) { + virtualRecords.addAll(builder.virtualRecords); + } + } + + public CwmsId getVirtualOutletId() { + return virtualOutletId; + } + + public List getVirtualRecords() { + return virtualRecords; + } + + public CwmsId getProjectId() { + return projectId; + } + + @Override + protected void validateInternal(CwmsDTOValidator validator) { + super.validateInternal(validator); + validator.validateCollection(virtualRecords); + } + + public static final class Builder { + private CwmsId projectId; + private CwmsId virtualOutletId; + private List virtualRecords = new ArrayList<>(); + + public VirtualOutlet build() { + return new VirtualOutlet(this); + } + + public Builder withVirtualOutletId(CwmsId virtualOutletId) { + this.virtualOutletId = virtualOutletId; + return this; + } + + public Builder withVirtualRecords(List virtualRecords) { + this.virtualRecords = virtualRecords; + return this; + } + + public Builder withProjectId(CwmsId projectId) { + this.projectId = projectId; + return this; + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/CompoundOutletRecord.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutletRecord.java similarity index 91% rename from cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/CompoundOutletRecord.java rename to cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutletRecord.java index b26bdcf3d..1d8bff389 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/CompoundOutletRecord.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/location/kind/VirtualOutletRecord.java @@ -35,15 +35,15 @@ import java.util.List; @FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON}) -@JsonDeserialize(builder = CompoundOutletRecord.Builder.class) +@JsonDeserialize(builder = VirtualOutletRecord.Builder.class) @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) -public class CompoundOutletRecord extends CwmsDTOBase { +public final class VirtualOutletRecord extends CwmsDTOBase { @JsonProperty(required = true) private final CwmsId outletId; private final List downstreamOutletIds = new ArrayList<>(); - private CompoundOutletRecord(Builder builder) { + private VirtualOutletRecord(Builder builder) { outletId = builder.outletId; if (builder.downstreamOutletIds != null) { downstreamOutletIds.addAll(builder.downstreamOutletIds); @@ -72,13 +72,13 @@ public static final class Builder { public Builder() { } - public Builder(CompoundOutletRecord clone) { + public Builder(VirtualOutletRecord clone) { outletId = clone.outletId; downstreamOutletIds = new ArrayList<>(clone.downstreamOutletIds); } - public CompoundOutletRecord build() { - return new CompoundOutletRecord(this); + public VirtualOutletRecord build() { + return new VirtualOutletRecord(this); } @JsonProperty(required = true) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/Projects.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/Projects.java index 665e30ba7..a6ef8a1fa 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/Projects.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/Projects.java @@ -35,7 +35,7 @@ import java.util.List; -@FormattableWith(contentType = Formats.JSON, formatter = JsonV2.class) +@FormattableWith(contentType = Formats.JSONV1, aliases = {Formats.JSON, Formats.DEFAULT}, formatter = JsonV2.class) public class Projects extends CwmsDTOPaginated { List projects; diff --git a/cwms-data-api/src/test/java/cwms/cda/api/BasinControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/BasinControllerIT.java similarity index 99% rename from cwms-data-api/src/test/java/cwms/cda/api/BasinControllerTestIT.java rename to cwms-data-api/src/test/java/cwms/cda/api/BasinControllerIT.java index cb90bd861..f761088d6 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/BasinControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/BasinControllerIT.java @@ -56,7 +56,7 @@ import static org.hamcrest.Matchers.is; @Tag("integration") -class BasinControllerTestIT extends DataApiTestIT +class BasinControllerIT extends DataApiTestIT { private static final String OFFICE = "SWT"; private static final Basin BASIN; @@ -130,7 +130,7 @@ public static void setup() throws Exception { } catch (IOException e) { throw new RuntimeException(e); } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @@ -145,7 +145,7 @@ public static void tearDown() throws Exception { locationsDao.deleteLocation(BASIN.getBasinId().getName(), OFFICE, true); basinDao.deleteBasin(BASIN_CONNECT.getBasinId(), DELETE_ACTION); locationsDao.deleteLocation(BASIN_CONNECT.getBasinId().getName(), OFFICE, true); - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @Test diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java b/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java index 650d3b974..e3c589532 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/ControllersTest.java @@ -349,7 +349,54 @@ void testMissingRequiredParams(){ assertThrows(RequiredQueryParameterException.class, () -> Controllers.requiredZdt(ctx, Controllers.BEGIN)); } + @Test + void testQueryParamAsType(){ + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + + Map urlParams = new LinkedHashMap<>(); + urlParams.put(Controllers.METHOD, "DELETE_KEY"); + String paramStr = ControllerTest.buildParamStr(urlParams); + when(request.getQueryString()).thenReturn(paramStr); + + // build real context that uses the mock request/response + Context ctx = new Context(request, response, new LinkedHashMap()); + + // if its present it should work + assertEquals(JooqDao.DeleteMethod.DELETE_KEY, Controllers.queryParamAsClass(ctx, JooqDao.DeleteMethod.class, null, Controllers.METHOD)); + } + + @Test + void testMissingQueryParamAsType(){ + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + + Map urlParams = new LinkedHashMap<>(); + urlParams.put(Controllers.METHOD, "DELETE_KEY"); + String paramStr = ControllerTest.buildParamStr(urlParams); + when(request.getQueryString()).thenReturn(paramStr); + + // build real context that uses the mock request/response + Context ctx = new Context(request, response, new LinkedHashMap()); + // if its present it should work + assertNull(Controllers.queryParamAsClass(ctx, JooqDao.DeleteMethod.class, null, "Not a key")); + } + @Test + void testAliasedQueryParamAsType(){ + final HttpServletRequest request = mock(HttpServletRequest.class); + final HttpServletResponse response = mock(HttpServletResponse.class); + Map urlParams = new LinkedHashMap<>(); + urlParams.put(Controllers.METHOD, "DELETE_KEY"); + String paramStr = ControllerTest.buildParamStr(urlParams); + when(request.getQueryString()).thenReturn(paramStr); + + // build real context that uses the mock request/response + Context ctx = new Context(request, response, new LinkedHashMap()); + + // if its present it should work + assertEquals(JooqDao.DeleteMethod.DELETE_KEY, Controllers.queryParamAsClass(ctx, JooqDao.DeleteMethod.class, null, "Not a key", Controllers.METHOD)); + } } \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/api/DownstreamLocationsGetControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/DownstreamLocationsGetControllerIT.java index fee6321c7..54a2888f5 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/DownstreamLocationsGetControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/DownstreamLocationsGetControllerIT.java @@ -42,7 +42,7 @@ @Tag("integration") final class DownstreamLocationsGetControllerIT extends DataApiTestIT { - private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); + private static final String OFFICE_ID = TestAccounts.KeyUser.SWT_NORMAL.getOperatingOffice(); private static final List STREAMS_CREATED = new ArrayList<>(); @BeforeAll @@ -68,7 +68,7 @@ private static void createAndStoreTestStream(String testLoc) throws SQLException .build(); STREAMS_CREATED.add(streamToStore); streamDao.storeStream(streamToStore, true); - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -83,7 +83,7 @@ public static void tearDown() { } catch (NotFoundException e) { // ignore } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { throw new RuntimeException(ex); } @@ -104,7 +104,7 @@ void test_getDownstreamLocations() throws IOException { // 2) Retrieve the StreamLocation and assert that it exists // 3) Delete the StreamLocation // 4) Retrieve the StreamLocation and assert that it does not exist - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; // Create the StreamLocation given() diff --git a/cwms-data-api/src/test/java/cwms/cda/api/EmbankmentControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/EmbankmentControllerIT.java index d3be5702b..fa387eeb7 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/EmbankmentControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/EmbankmentControllerIT.java @@ -24,6 +24,7 @@ package cwms.cda.api; +import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.LocationsDaoImpl; import cwms.cda.data.dao.location.kind.LocationUtil; @@ -53,6 +54,7 @@ import java.time.Instant; import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; +import static cwms.cda.api.Controllers.OFFICE; import static cwms.cda.data.dao.DaoTest.getDslContext; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; import static io.restassured.RestAssured.given; @@ -81,7 +83,7 @@ public static void setup() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { try { - DSLContext context = getDslContext(c, databaseLink.getOfficeId()); + DSLContext context = getDslContext(c, EMBANKMENT_LOC.getOfficeId()); LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); PROJECT_OBJ_T projectObjT = buildProject(); CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(context.configuration(), projectObjT, "T"); @@ -89,7 +91,7 @@ public static void setup() throws Exception { } catch (IOException e) { throw new RuntimeException(e); } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -97,13 +99,27 @@ public static void tearDown() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { - DSLContext context = getDslContext(c, databaseLink.getOfficeId()); + DSLContext context = getDslContext(c, EMBANKMENT_LOC.getOfficeId()); LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); - locationsDao.deleteLocation(EMBANKMENT_LOC.getName(), databaseLink.getOfficeId(), true); - CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(context.configuration(), PROJECT_LOC.getName(), - DeleteRule.DELETE_ALL.getRule(), databaseLink.getOfficeId()); - locationsDao.deleteLocation(PROJECT_LOC.getName(), databaseLink.getOfficeId(), true); - }); + try { + locationsDao.deleteLocation(EMBANKMENT_LOC.getName(), EMBANKMENT_LOC.getOfficeId(), true); + + } catch (NotFoundException ex) { + /* only an error within the tests below. */ + } + try { + CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(context.configuration(), PROJECT_LOC.getName(), + DeleteRule.DELETE_ALL.getRule(), PROJECT_LOC.getOfficeId()); + + } catch (NotFoundException ex) { + /* only an error within the tests below. */ + } + try { + locationsDao.deleteLocation(PROJECT_LOC.getName(), PROJECT_LOC.getOfficeId(), true); + } catch (NotFoundException ex) { + /* only an error within the tests below. */ + } + }, CwmsDataApiSetupCallback.getWebUser()); } @Test diff --git a/cwms-data-api/src/test/java/cwms/cda/api/StreamControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/StreamControllerTestIT.java index 22bf5549e..fe999a19d 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/StreamControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/StreamControllerTestIT.java @@ -28,6 +28,7 @@ import static cwms.cda.api.Controllers.OFFICE; import static cwms.cda.api.Controllers.OFFICE_MASK; import static cwms.cda.api.Controllers.STATION_UNIT; +import static cwms.cda.api.Controllers.STREAM_ID_MASK; import cwms.cda.api.errors.NotFoundException; import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; @@ -89,7 +90,7 @@ private static void createAndStoreTestStream(String testLoc) throws SQLException .build(); STREAMS_CREATED.add(streamToStore); streamDao.storeStream(streamToStore, true); - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -104,7 +105,7 @@ public static void tearDown() { } catch (NotFoundException e) { //ignore } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } catch(SQLException ex) { throw new RuntimeException(ex); } @@ -283,6 +284,7 @@ void test_get_all() throws IOException { .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) .queryParam(OFFICE_MASK, office) + .queryParam(STREAM_ID_MASK, streamId) .queryParam(STATION_UNIT, "km") .when() .redirects().follow(true) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/StreamLocationControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/StreamLocationControllerTestIT.java index 7b5c7a738..f80f55792 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/StreamLocationControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/StreamLocationControllerTestIT.java @@ -25,6 +25,7 @@ package cwms.cda.api; import static cwms.cda.api.Controllers.AREA_UNIT; +import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; import static cwms.cda.api.Controllers.NAME; import static cwms.cda.api.Controllers.NAME_MASK; import static cwms.cda.api.Controllers.OFFICE; @@ -33,7 +34,6 @@ import static cwms.cda.api.Controllers.STATION_UNIT; import static cwms.cda.api.Controllers.STREAM_ID; import static cwms.cda.api.Controllers.STREAM_ID_MASK; -import cwms.cda.api.errors.NotFoundException; import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.StreamDao; @@ -68,17 +68,17 @@ @Tag("integration") final class StreamLocationControllerTestIT extends DataApiTestIT { - private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); + private static final String OFFICE_ID = TestAccounts.KeyUser.SWT_NORMAL.getOperatingOffice(); private static final List STREAMS_CREATED = new ArrayList<>(); @BeforeAll public static void setup() throws SQLException { - String testLoc = "StreamLoc123"; // match the stream location name in the json file + String testLoc = "StreamLoc321"; // match the stream location name in the json file createLocation(testLoc, true, OFFICE_ID, "STREAM_LOCATION"); - createAndStoreTestStream("ImOnThisStream"); + createAndStoreTestStream("ImOnThisStream2"); } - private static void createAndStoreTestStream(String testLoc) throws SQLException { + static void createAndStoreTestStream(String testLoc) throws SQLException { createLocation(testLoc, true, OFFICE_ID, "STREAM"); CwmsDatabaseContainer db = CwmsDataApiSetupCallback.getDatabaseLink(); db.connection(c -> { @@ -92,8 +92,8 @@ private static void createAndStoreTestStream(String testLoc) throws SQLException .withLengthUnits("km") .build(); STREAMS_CREATED.add(streamToStore); - streamDao.storeStream(streamToStore, true); - }); + streamDao.storeStream(streamToStore, false); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -105,10 +105,10 @@ public static void tearDown() { StreamDao streamDao = new StreamDao(getDslContext(c, OFFICE_ID)); try { streamDao.deleteStream(stream.getId().getOfficeId(), stream.getId().getName(), DeleteRule.DELETE_ALL); - } catch (NotFoundException e) { + } catch (Exception e) { // ignore } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { throw new RuntimeException(ex); } @@ -118,7 +118,7 @@ public static void tearDown() { @Test void test_get_create_delete() throws IOException { - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location.json"); + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location3.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); assertNotNull(json); @@ -129,12 +129,13 @@ void test_get_create_delete() throws IOException { // 2) Retrieve the StreamLocation and assert that it exists // 3) Delete the StreamLocation // 4) Retrieve the StreamLocation and assert that it does not exist - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; // Create the StreamLocation given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) + .queryParam(FAIL_IF_EXISTS, false) .contentType(Formats.JSON) .body(json) .header(AUTH_HEADER, user.toHeaderValue()) @@ -220,7 +221,7 @@ void test_get_create_delete() throws IOException { @Test void test_update_does_not_exist() { - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; given() .log().ifValidationFails(LogDetail.ALL, true) .queryParam(Controllers.OFFICE, user.getOperatingOffice()) @@ -238,7 +239,7 @@ void test_update_does_not_exist() { @Test void test_delete_does_not_exist() { - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; given() .log().ifValidationFails(LogDetail.ALL, true) .queryParam(Controllers.OFFICE, user.getOperatingOffice()) @@ -257,7 +258,7 @@ void test_delete_does_not_exist() { @Test void test_get_all() throws IOException { - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location.json"); + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location3.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); assertNotNull(json); @@ -267,12 +268,13 @@ void test_get_all() throws IOException { // 1) Create the StreamLocation // 2) Retrieve the StreamLocation with getAll and assert that it exists // 3) Delete the StreamLocation - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; // Create the StreamLocation given() .log().ifValidationFails(LogDetail.ALL, true) .contentType(Formats.JSON) + .queryParam(FAIL_IF_EXISTS, false) .body(json) .header(AUTH_HEADER, user.toHeaderValue()) .when() diff --git a/cwms-data-api/src/test/java/cwms/cda/api/StreamReachControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/StreamReachControllerTestIT.java index 1431ccad2..364831550 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/StreamReachControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/StreamReachControllerTestIT.java @@ -124,7 +124,7 @@ private static void createAndStoreTestStream(String testLoc) throws SQLException .build(); STREAMS_CREATED.add(streamToStore); streamDao.storeStream(streamToStore, false); - }); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -139,7 +139,7 @@ public static void tearDown() { } catch (NotFoundException e) { // ignore } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { throw new RuntimeException(ex); } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/TimeZoneControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/TimeZoneIdControllerTestIT.java similarity index 97% rename from cwms-data-api/src/test/java/cwms/cda/api/TimeZoneControllerTestIT.java rename to cwms-data-api/src/test/java/cwms/cda/api/TimeZoneIdControllerTestIT.java index 717e7dd4d..e683eaafd 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/TimeZoneControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/TimeZoneIdControllerTestIT.java @@ -13,7 +13,7 @@ import static org.hamcrest.Matchers.is; @Tag("integration") -class TimeZoneControllerTestIT extends DataApiTestIT +class TimeZoneIdControllerTestIT extends DataApiTestIT { @ParameterizedTest @EnumSource(GetAllTest.class) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/TurbineChangesControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/TurbineChangesControllerIT.java index 65fd18192..cd04d6338 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/TurbineChangesControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/TurbineChangesControllerIT.java @@ -24,12 +24,10 @@ package cwms.cda.api; -import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; -import static cwms.cda.api.Controllers.PROJECT_ID; +import static cwms.cda.api.Controllers.OVERRIDE_PROTECTION; import static cwms.cda.data.dao.DaoTest.getDslContext; import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -82,7 +80,7 @@ final class TurbineChangesControllerIT extends DataApiTestIT { Class c = TurbineChangesControllerIT.class; Charset utf8 = StandardCharsets.UTF_8; ContentType contentType = new ContentType(Formats.JSONV1); - try(InputStream projectStream = c.getResourceAsStream("/cwms/cda/api/project_location_turb.json"); + try(InputStream projectStream = c.getResourceAsStream("/cwms/cda/api/project_location_turb_changes.json"); InputStream turbineStream = c.getResourceAsStream("/cwms/cda/api/turbine.json"); InputStream turbineChangesStream = c.getResourceAsStream("/cwms/cda/api/turbine-changes.json")) { String json = IOUtils.toString(Objects.requireNonNull(projectStream), utf8); @@ -99,7 +97,6 @@ final class TurbineChangesControllerIT extends DataApiTestIT { @BeforeAll public static void setup() throws Exception { - tearDown(); CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { try { @@ -115,12 +112,11 @@ public static void setup() throws Exception { .build()) .withLocation(TURBINE_LOC) .build(); - new TurbineDao(context).storeTurbine(turbine, false); + new TurbineDao(context).storeTurbine(turbine, true); } catch (IOException e) { throw new RuntimeException(e); } - }, - CwmsDataApiSetupCallback.getWebUser()); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -151,7 +147,7 @@ void test_get_create_delete() { .contentType(Formats.JSONV1) .body(json) .header(AUTH_HEADER, user.toHeaderValue()) - .queryParam(FAIL_IF_EXISTS, "false") + .queryParam(OVERRIDE_PROTECTION, "true") .when() .redirects().follow(true) .redirects().max(3) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/TurbineControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/TurbineControllerIT.java index 18c0989dc..7b952d403 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/TurbineControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/TurbineControllerIT.java @@ -65,16 +65,14 @@ final class TurbineControllerIT extends DataApiTestIT { private static final String OFFICE = TestAccounts.KeyUser.SWT_NORMAL.getOperatingOffice(); private static final Location PROJECT_LOC; - private static final Location TURBINE_LOC; private static final Turbine TURBINE; static { - try(InputStream projectStream = TurbineControllerIT.class.getResourceAsStream("/cwms/cda/api/project_location.json"); - InputStream turbineStream = TurbineControllerIT.class.getResourceAsStream("/cwms/cda/api/turbine.json")) { + try(InputStream projectStream = TurbineControllerIT.class.getResourceAsStream("/cwms/cda/api/project_location_turb.json"); + InputStream turbineStream = TurbineControllerIT.class.getResourceAsStream("/cwms/cda/api/turbine_phys.json")) { String projectLocJson = IOUtils.toString(projectStream, StandardCharsets.UTF_8); PROJECT_LOC = Formats.parseContent(new ContentType(Formats.JSONV1), projectLocJson, Location.class); String turbineJson = IOUtils.toString(turbineStream, StandardCharsets.UTF_8); TURBINE = Formats.parseContent(new ContentType(Formats.JSONV1), turbineJson, Turbine.class); - TURBINE_LOC = TURBINE.getLocation(); } catch(Exception ex) { throw new RuntimeException(ex); } @@ -82,20 +80,12 @@ final class TurbineControllerIT extends DataApiTestIT { @BeforeAll public static void setup() throws Exception { - tearDown(); CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { - try { - DSLContext context = getDslContext(c, OFFICE); - LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); - PROJECT_OBJ_T projectObjT = buildProject(); - CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(context.configuration(), projectObjT, "T"); - locationsDao.storeLocation(TURBINE_LOC); - } catch (IOException e) { - throw new RuntimeException(e); - } - }, - CwmsDataApiSetupCallback.getWebUser()); + DSLContext context = getDslContext(c, OFFICE); + PROJECT_OBJ_T projectObjT = buildProject(); + CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(context.configuration(), projectObjT, "T"); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -103,10 +93,9 @@ public static void tearDown() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE); - cleanTurbine(context, TURBINE_LOC); + cleanTurbine(context, TURBINE.getLocation()); cleanProject(context, PROJECT_LOC); - }, - CwmsDataApiSetupCallback.getWebUser()); + }, CwmsDataApiSetupCallback.getWebUser()); } @Test diff --git a/cwms-data-api/src/test/java/cwms/cda/api/UpstreamLocationsGetControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/UpstreamLocationsGetControllerIT.java index e144ea174..be622a266 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/UpstreamLocationsGetControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/UpstreamLocationsGetControllerIT.java @@ -2,13 +2,13 @@ import static cwms.cda.api.Controllers.ALL_UPSTREAM; import static cwms.cda.api.Controllers.AREA_UNIT; +import static cwms.cda.api.Controllers.FAIL_IF_EXISTS; import static cwms.cda.api.Controllers.NAME; import static cwms.cda.api.Controllers.OFFICE; import static cwms.cda.api.Controllers.SAME_STREAM_ONLY; import static cwms.cda.api.Controllers.STAGE_UNIT; import static cwms.cda.api.Controllers.STATION_UNIT; import static cwms.cda.api.Controllers.STREAM_ID; -import cwms.cda.api.errors.NotFoundException; import static cwms.cda.data.dao.DaoTest.getDslContext; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.StreamDao; @@ -42,14 +42,14 @@ @Tag("integration") final class UpstreamLocationsGetControllerIT extends DataApiTestIT { - private static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); + private static final String OFFICE_ID = TestAccounts.KeyUser.SWT_NORMAL.getOperatingOffice(); private static final List STREAMS_CREATED = new ArrayList<>(); @BeforeAll public static void setup() throws SQLException { - createLocation("StreamLoc123", true, OFFICE_ID, "STREAM_LOCATION"); - createLocation("StreamLoc2", true, OFFICE_ID, "STREAM_LOCATION"); - createAndStoreTestStream("ImOnThisStream"); + createLocation("StreamLoc1234", true, OFFICE_ID, "STREAM_LOCATION"); + createLocation("StreamLoc23", true, OFFICE_ID, "STREAM_LOCATION"); + createAndStoreTestStream("ImOnThisStream3"); } private static void createAndStoreTestStream(String testLoc) throws SQLException { @@ -67,8 +67,8 @@ private static void createAndStoreTestStream(String testLoc) throws SQLException .withStartsDownstream(true) .build(); STREAMS_CREATED.add(streamToStore); - streamDao.storeStream(streamToStore, true); - }); + streamDao.storeStream(streamToStore, false); + }, CwmsDataApiSetupCallback.getWebUser()); } @AfterAll @@ -80,10 +80,10 @@ public static void tearDown() { StreamDao streamDao = new StreamDao(getDslContext(c, OFFICE_ID)); try { streamDao.deleteStream(stream.getId().getOfficeId(), stream.getId().getName(), DeleteRule.DELETE_ALL); - } catch (NotFoundException e) { + } catch (Exception e) { // ignore } - }); + }, CwmsDataApiSetupCallback.getWebUser()); } catch (SQLException ex) { throw new RuntimeException(ex); } @@ -93,7 +93,7 @@ public static void tearDown() { @Test void test_getUpstreamLocations() throws IOException { - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location.json"); + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location4.json"); assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); assertNotNull(json); @@ -105,12 +105,13 @@ void test_getUpstreamLocations() throws IOException { // 3) Retrieve upstream locations and assert they exist // 4) Delete the StreamLocation - TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + TestAccounts.KeyUser user = TestAccounts.KeyUser.SWT_NORMAL; // Create the StreamLocation given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) + .queryParam(FAIL_IF_EXISTS, false) .contentType(Formats.JSON) .body(json) .header(AUTH_HEADER, user.toHeaderValue()) @@ -158,7 +159,7 @@ void test_getUpstreamLocations() throws IOException { .body("area-units", equalTo(streamLocation.getAreaUnits())) .body("stage-units", equalTo(streamLocation.getStageUnits())); - resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location2.json"); + resource = this.getClass().getResourceAsStream("/cwms/cda/api/stream_location5.json"); assertNotNull(resource); json = IOUtils.toString(resource, StandardCharsets.UTF_8); assertNotNull(json); @@ -170,6 +171,7 @@ void test_getUpstreamLocations() throws IOException { given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) + .queryParam(FAIL_IF_EXISTS, false) .contentType(Formats.JSON) .body(json) .header(AUTH_HEADER, user.toHeaderValue()) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/VersionedTimeseriesControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/VersionedTimeseriesControllerTestIT.java index 8b544c35c..bd42fdabd 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/VersionedTimeseriesControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/VersionedTimeseriesControllerTestIT.java @@ -4,6 +4,7 @@ import static cwms.cda.api.Controllers.END; import static cwms.cda.api.Controllers.NAME; import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.TRIM; import static cwms.cda.api.Controllers.UNIT; import static cwms.cda.api.Controllers.VERSION_DATE; import static io.restassured.RestAssured.given; @@ -135,6 +136,7 @@ void test_create_get_delete_get_versioned() throws Exception { .queryParam(VERSION_DATE, ts.getVersionDate().toString()) .queryParam(BEGIN, BEGIN_STR) .queryParam(END, END_STR) // put old end date back in map + .queryParam(TRIM, false) .when() .redirects().follow(true) .redirects().max(3) @@ -288,6 +290,7 @@ void test_create_get_update_get_delete_get_versioned() throws Exception { .queryParam(VERSION_DATE, ts.getVersionDate().toString()) .queryParam(BEGIN, BEGIN_STR) .queryParam(END, END_STR) + .queryParam(TRIM, false) .when() .redirects().follow(true) .redirects().max(3) @@ -437,6 +440,7 @@ void test_create_get_update_get_delete_get_unversioned() throws Exception { .queryParam(UNIT, ts.getUnits()) .queryParam(BEGIN, BEGIN_STR) .queryParam(END, END_STR) + .queryParam(TRIM, false) .when() .redirects().follow(true) .redirects().max(3) diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/StreamDaoTestIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/StreamDaoTestIT.java index 4f1119ebd..198fdf9c4 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/StreamDaoTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/StreamDaoTestIT.java @@ -104,7 +104,7 @@ void testRoundTrip() throws Exception { Stream retrievedStream2 = streamDao.retrieveStream(stream2.getId().getOfficeId(), stream2.getId().getName(), stream2.getLengthUnits()); DTOMatch.assertMatch(stream2, retrievedStream2); //also test retrieve in bulk - List retrievedStreams = streamDao.retrieveStreams(OFFICE_ID, null, null, "km"); + List retrievedStreams = streamDao.retrieveStreams(OFFICE_ID, null,null, null, "km"); assertFalse(retrievedStreams.isEmpty()); retrievedStreams = retrievedStreams.stream() .filter(s -> s.getId().getName().equalsIgnoreCase(stream.getId().getName()) || s.getId().getName().equalsIgnoreCase(stream2.getId().getName())) diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/EmbankmentDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/EmbankmentDaoIT.java index b62373652..57ee7887a 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/EmbankmentDaoIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/EmbankmentDaoIT.java @@ -61,9 +61,9 @@ @TestInstance(Lifecycle.PER_CLASS) final class EmbankmentDaoIT extends ProjectStructureDaoIT { private static final String EMBANKMENT_KIND = "EMBANKMENT"; - private static final Location EMBANK_LOC1 = buildProjectStructureLocation("PROJECT-EMBANK_LOC1", EMBANKMENT_KIND); - private static final Location EMBANK_LOC2 = buildProjectStructureLocation("EMBANK_LOC2", EMBANKMENT_KIND); - private static final Location EMBANK_LOC3 = buildProjectStructureLocation("EMBANK_LOC3", EMBANKMENT_KIND); + private static final Location EMBANK_LOC1 = buildProjectStructureLocation("PROJECT-EMBANK_LOC1_IT", EMBANKMENT_KIND); + private static final Location EMBANK_LOC2 = buildProjectStructureLocation("EMBANK_LOC2_IT", EMBANKMENT_KIND); + private static final Location EMBANK_LOC3 = buildProjectStructureLocation("EMBANK_LOC3_IT", EMBANKMENT_KIND); @BeforeAll public void setup() throws Exception { diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoCompoundIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoCompoundIT.java index 5175ca0ff..fa273397a 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoCompoundIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoCompoundIT.java @@ -24,8 +24,9 @@ import cwms.cda.data.dao.LocationsDaoImpl; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; -import cwms.cda.data.dto.location.kind.CompoundOutletRecord; import cwms.cda.data.dto.location.kind.Outlet; +import cwms.cda.data.dto.location.kind.VirtualOutlet; +import cwms.cda.data.dto.location.kind.VirtualOutletRecord; import cwms.cda.helpers.DTOMatch; import fixtures.CwmsDataApiSetupCallback; import java.io.IOException; @@ -33,7 +34,6 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import java.util.stream.IntStream; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; import org.jooq.DSLContext; import org.junit.jupiter.api.AfterAll; @@ -45,12 +45,18 @@ class OutletDaoCompoundIT extends ProjectStructureDaoIT { private static final String OUTLET_KIND = "OUTLET"; - private static final String COMPOUND_OUTLET_RATING_GROUP = "Rating-" + PROJECT_LOC2.getName() + "-CompoundOutlet"; - - private static final Location COMPOUND_OUTLET_1_LOC = buildProjectStructureLocation("CompoundOutlet1", OUTLET_KIND); - private static final Location COMPOUND_OUTLET_2_LOC = buildProjectStructureLocation( - PROJECT_LOC2.getName() + "-CompoundOutlet2", OUTLET_KIND); - private static final Location COMPOUND_OUTLET_3_LOC = buildProjectStructureLocation("CompoundOutlet3", OUTLET_KIND); + private static final CwmsId VIRTUAL_OUTLET_RATING_GROUP = new CwmsId.Builder().withName("Rating-" + PROJECT_LOC2.getName() + "-VirtualOutlet") + .withOfficeId(OFFICE_ID) + .build(); + + private static final CwmsId EXISTING_VIRTUAL_OUTLET_ID = new CwmsId.Builder().withName("Virtual Outlet 1") + .withOfficeId(OFFICE_ID) + .build(); + private static final CwmsId BASE_AND_SUB_VIRTUAL_OUTLET_ID = new CwmsId.Builder().withName( + PROJECT_LOC2.getName() + "-Virtual Outlet 2").withOfficeId(OFFICE_ID).build(); + private static final CwmsId BASE_ONLY_VIRTUAL_OUTLET_ID = new CwmsId.Builder().withName("Virtual Outlet 3") + .withOfficeId(OFFICE_ID) + .build(); private static final Location CO1_I53 = buildProjectStructureLocation("I53", OUTLET_KIND); private static final Location CO1_I25 = buildProjectStructureLocation("I25", OUTLET_KIND); @@ -68,39 +74,27 @@ class OutletDaoCompoundIT extends ProjectStructureDaoIT { private static final Location CO3_I3 = buildProjectStructureLocation("I3", OUTLET_KIND); private static final Location CO3_CONDUIT = buildProjectStructureLocation("Conduit", OUTLET_KIND); - private static final Outlet EXISTING_COMPOUND_OUTLET = buildTestOutlet(COMPOUND_OUTLET_1_LOC, PROJECT_LOC2, - buildCompoundOutletRecord(CO1_I25, - CO1_LOW_FLOW), - buildCompoundOutletRecord(CO1_I53, - CO1_LOW_FLOW), - buildCompoundOutletRecord(CO1_LOW_FLOW)); - - private static final Outlet BASE_AND_SUB_LOC_COMPOUND_OUTLET = buildTestOutlet(COMPOUND_OUTLET_2_LOC, PROJECT_LOC2, - buildCompoundOutletRecord(CO2_INTAKE, - CO2_WEIR, - CO2_CONDUIT), - buildCompoundOutletRecord(CO2_WEIR), - buildCompoundOutletRecord( - CO2_CONDUIT)); - - private static final Outlet BASE_LOC_ONLY_COMPOUND_OUTLET = buildTestOutlet(COMPOUND_OUTLET_3_LOC, PROJECT_LOC2, - buildCompoundOutletRecord(CO3_I1, - CO3_CONDUIT), - buildCompoundOutletRecord(CO3_I2, - CO3_CONDUIT), - buildCompoundOutletRecord(CO3_I3, - CO3_CONDUIT), - buildCompoundOutletRecord(CO3_CONDUIT)); - private static final Outlet CO1_I25_OUTLET = buildTestOutlet(CO1_I25, PROJECT_LOC2); - private static final Outlet CO1_I53_OUTLET = buildTestOutlet(CO1_I53, PROJECT_LOC2); - private static final Outlet CO1_LOW_FLOW_OUTLET = buildTestOutlet(CO1_LOW_FLOW, PROJECT_LOC2); - private static final Outlet CO2_CONDUIT_OUTLET = buildTestOutlet(CO2_CONDUIT, PROJECT_LOC2); - private static final Outlet CO2_INTAKE_OUTLET = buildTestOutlet(CO2_INTAKE, PROJECT_LOC2); - private static final Outlet CO2_WEIR_OUTLET = buildTestOutlet(CO2_WEIR, PROJECT_LOC2); - private static final Outlet CO3_I1_OUTLET = buildTestOutlet(CO3_I1, PROJECT_LOC2); - private static final Outlet CO3_I2_OUTLET = buildTestOutlet(CO3_I2, PROJECT_LOC2); - private static final Outlet CO3_I3_OUTLET = buildTestOutlet(CO3_I3, PROJECT_LOC2); - private static final Outlet CO3_CONDUIT_OUTLET = buildTestOutlet(CO3_CONDUIT, PROJECT_LOC2); + private static final List EXISTING_VIRTUAL_OUTLET = Arrays.asList( + buildVirtualOutletRecord(CO1_I25, CO1_LOW_FLOW), buildVirtualOutletRecord(CO1_I53, CO1_LOW_FLOW), + buildVirtualOutletRecord(CO1_LOW_FLOW)); + + private static final List BASE_AND_SUB_VIRTUAL_OUTLET = Arrays.asList( + buildVirtualOutletRecord(CO2_INTAKE, CO2_WEIR, CO2_CONDUIT), buildVirtualOutletRecord(CO2_WEIR), + buildVirtualOutletRecord(CO2_CONDUIT)); + + private static final List BASE_ONLY_VIRTUAL_OUTLET = Arrays.asList( + buildVirtualOutletRecord(CO3_I1, CO3_CONDUIT), buildVirtualOutletRecord(CO3_I2, CO3_CONDUIT), + buildVirtualOutletRecord(CO3_I3, CO3_CONDUIT), buildVirtualOutletRecord(CO3_CONDUIT)); + private static final Outlet CO1_I25_OUTLET = buildTestOutlet(CO1_I25, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO1_I53_OUTLET = buildTestOutlet(CO1_I53, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO1_LOW_FLOW_OUTLET = buildTestOutlet(CO1_LOW_FLOW, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO2_CONDUIT_OUTLET = buildTestOutlet(CO2_CONDUIT, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO2_INTAKE_OUTLET = buildTestOutlet(CO2_INTAKE, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO2_WEIR_OUTLET = buildTestOutlet(CO2_WEIR, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO3_I1_OUTLET = buildTestOutlet(CO3_I1, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO3_I2_OUTLET = buildTestOutlet(CO3_I2, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO3_I3_OUTLET = buildTestOutlet(CO3_I3, VIRTUAL_OUTLET_RATING_GROUP); + private static final Outlet CO3_CONDUIT_OUTLET = buildTestOutlet(CO3_CONDUIT, VIRTUAL_OUTLET_RATING_GROUP); @BeforeAll static void setup() throws Exception { @@ -111,9 +105,6 @@ static void setup() throws Exception { LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); OutletDao outletDao = new OutletDao(context); try { - locationsDao.storeLocation(COMPOUND_OUTLET_1_LOC); - locationsDao.storeLocation(COMPOUND_OUTLET_2_LOC); - locationsDao.storeLocation(COMPOUND_OUTLET_3_LOC); locationsDao.storeLocation(CO1_I25); locationsDao.storeLocation(CO1_I53); locationsDao.storeLocation(CO1_LOW_FLOW); @@ -125,26 +116,19 @@ static void setup() throws Exception { locationsDao.storeLocation(CO3_I3); locationsDao.storeLocation(CO3_CONDUIT); - outletDao.storeOutlet(CO1_I25_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO1_I53_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO1_LOW_FLOW_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO2_CONDUIT_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO2_INTAKE_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO2_WEIR_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO3_I1_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO3_I2_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO3_I3_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(CO3_CONDUIT_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - - //Stored last, so the db has all the compound outlets - outletDao.storeOutlet(EXISTING_COMPOUND_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(BASE_AND_SUB_LOC_COMPOUND_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - outletDao.storeOutlet(BASE_LOC_ONLY_COMPOUND_OUTLET, COMPOUND_OUTLET_RATING_GROUP, false); - - //Should always have outlet 1 as a compound outlet - outletDao.storeCompoundOutlet(PROJECT_LOC2.getName(), COMPOUND_OUTLET_1_LOC.getName(), - COMPOUND_OUTLET_1_LOC.getOfficeId(), - EXISTING_COMPOUND_OUTLET.getCompoundOutletRecords(), false); + outletDao.storeOutlet(CO1_I25_OUTLET, false); + outletDao.storeOutlet(CO1_I53_OUTLET, false); + outletDao.storeOutlet(CO1_LOW_FLOW_OUTLET, false); + outletDao.storeOutlet(CO2_CONDUIT_OUTLET, false); + outletDao.storeOutlet(CO2_INTAKE_OUTLET, false); + outletDao.storeOutlet(CO2_WEIR_OUTLET, false); + outletDao.storeOutlet(CO3_I1_OUTLET, false); + outletDao.storeOutlet(CO3_I2_OUTLET, false); + outletDao.storeOutlet(CO3_I3_OUTLET, false); + outletDao.storeOutlet(CO3_CONDUIT_OUTLET, false); + + outletDao.storeVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_LOC2.getName(), + EXISTING_VIRTUAL_OUTLET_ID.getName(), EXISTING_VIRTUAL_OUTLET, false); } catch (IOException e) { throw new RuntimeException(e); } @@ -159,132 +143,126 @@ static void teardown() throws Exception { LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); OutletDao outletDao = new OutletDao(context); - //Compound outlets - outletDao.deleteCompoundOutlet(PROJECT_LOC2.getName(), EXISTING_COMPOUND_OUTLET.getLocation().getName(), - PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - - outletDao.deleteOutlet(CO1_I25.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO1_I53.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO1_LOW_FLOW.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO2_CONDUIT.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO2_INTAKE.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO2_WEIR.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO3_I1.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO3_I2.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO3_I3.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(CO3_CONDUIT.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(COMPOUND_OUTLET_1_LOC.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(COMPOUND_OUTLET_2_LOC.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(COMPOUND_OUTLET_3_LOC.getName(), PROJECT_LOC2.getOfficeId(), DeleteRule.DELETE_ALL); - - locationsDao.deleteLocation(COMPOUND_OUTLET_1_LOC.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(COMPOUND_OUTLET_2_LOC.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(COMPOUND_OUTLET_3_LOC.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO1_I25.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO1_I53.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO1_LOW_FLOW.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO2_CONDUIT.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO2_INTAKE.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO2_WEIR.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO3_I1.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO3_I2.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO3_I3.getName(), PROJECT_LOC.getOfficeId(), true); - locationsDao.deleteLocation(CO3_CONDUIT.getName(), PROJECT_LOC.getOfficeId(), true); + outletDao.deleteVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_LOC2.getName(), + EXISTING_VIRTUAL_OUTLET_ID.getName(), DeleteRule.DELETE_ALL); + + outletDao.deleteOutlet(CO1_I25.getOfficeId(), CO1_I25.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO1_I53.getOfficeId(), CO1_I53.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO1_LOW_FLOW.getOfficeId(), CO1_LOW_FLOW.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO2_CONDUIT.getOfficeId(), CO2_CONDUIT.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO2_INTAKE.getOfficeId(), CO2_INTAKE.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO2_WEIR.getOfficeId(), CO2_WEIR.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO3_I1.getOfficeId(), CO3_I1.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO3_I2.getOfficeId(), CO3_I2.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO3_I3.getOfficeId(), CO3_I3.getName(), DeleteRule.DELETE_ALL); + outletDao.deleteOutlet(CO3_CONDUIT.getOfficeId(), CO3_CONDUIT.getName(), DeleteRule.DELETE_ALL); + + locationsDao.deleteLocation(CO1_I25.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO1_I53.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO1_LOW_FLOW.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO2_CONDUIT.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO2_INTAKE.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO2_WEIR.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO3_I1.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO3_I2.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO3_I3.getName(), PROJECT_LOC2.getOfficeId(), true); + locationsDao.deleteLocation(CO3_CONDUIT.getName(), PROJECT_LOC2.getOfficeId(), true); }, CwmsDataApiSetupCallback.getWebUser()); tearDownProject(); } @Test - void test_get_all_outlets_with_compound_outlets() throws Exception { + void test_get_all_outlets_with_virtual_outlets() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - List outlets = dao.retrieveOutletsForProject(PROJECT_LOC2.getName(), PROJECT_LOC.getOfficeId()); - - Outlet compoundOutlet = outlets.stream() - .filter(outlet -> outlet.getLocation() - .getName() - .equalsIgnoreCase( - EXISTING_COMPOUND_OUTLET.getLocation() - .getName())) - .findFirst() - .orElse(null); - assertNotNull(compoundOutlet); - List compoundOutletRecords = compoundOutlet.getCompoundOutletRecords(); - assertEquals(EXISTING_COMPOUND_OUTLET.getCompoundOutletRecords().size(), compoundOutletRecords.size()); - assertAll(EXISTING_COMPOUND_OUTLET.getCompoundOutletRecords() - .stream() - .map(compoundOutletRecord -> () -> compareOutletRecords( - compoundOutletRecord, compoundOutletRecords))); + List records = dao.retrieveVirtualOutletsForProject(PROJECT_LOC2.getOfficeId(), + PROJECT_LOC2.getName()); + VirtualOutlet outlet = records.stream() + .filter(vo -> equals(EXISTING_VIRTUAL_OUTLET_ID, vo.getVirtualOutletId())) + .findFirst() + .orElse(null); + assertNotNull(outlet); + DTOMatch.assertMatch(outlet.getVirtualOutletId(), EXISTING_VIRTUAL_OUTLET_ID); + DTOMatch.assertMatch(outlet.getProjectId(), PROJECT_2_ID); + DTOMatch.assertMatch(outlet.getVirtualRecords(), EXISTING_VIRTUAL_OUTLET, DTOMatch::assertMatch); + + List virtualRecords = outlet.getVirtualRecords(); + assertEquals(EXISTING_VIRTUAL_OUTLET.size(), virtualRecords.size()); + assertAll(EXISTING_VIRTUAL_OUTLET.stream() + .map(virtualOutletRecord -> () -> compareOutletRecords(virtualOutletRecord, + virtualRecords))); }, CwmsDataApiSetupCallback.getWebUser()); } + private boolean equals(CwmsId left, CwmsId right) { + return left.getName().equalsIgnoreCase(right.getName()) && left.getOfficeId() + .equalsIgnoreCase(right.getOfficeId()); + } + @Test - void test_compound_outlet_only_base_loc() throws Exception { + void test_virtual_outlet_only_base_loc() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - dao.storeCompoundOutlet(BASE_LOC_ONLY_COMPOUND_OUTLET.getProjectId().getName(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getName(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getOfficeId(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getCompoundOutletRecords(), false); - - List compoundOutletRecords = dao.retrieveOutlet( - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getName(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getOfficeId()).getCompoundOutletRecords(); - dao.deleteCompoundOutlet(BASE_LOC_ONLY_COMPOUND_OUTLET.getProjectId().getName(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getName(), - BASE_LOC_ONLY_COMPOUND_OUTLET.getLocation().getOfficeId(), DeleteRule.DELETE_ALL); - - assertEquals(BASE_LOC_ONLY_COMPOUND_OUTLET.getCompoundOutletRecords().size(), compoundOutletRecords.size()); - assertAll(BASE_LOC_ONLY_COMPOUND_OUTLET.getCompoundOutletRecords() - .stream() - .map(compoundOutletRecord -> () -> compareOutletRecords( - compoundOutletRecord, compoundOutletRecords))); + dao.storeVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_LOC2.getName(), + BASE_ONLY_VIRTUAL_OUTLET_ID.getName(), BASE_ONLY_VIRTUAL_OUTLET, false); + + VirtualOutlet virtualOutlet = dao.retrieveVirtualOutlet(PROJECT_2_ID.getOfficeId(), + PROJECT_LOC2.getName(), + BASE_ONLY_VIRTUAL_OUTLET_ID.getName()); + assertNotNull(virtualOutlet); + dao.deleteVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_2_ID.getName(), + BASE_ONLY_VIRTUAL_OUTLET_ID.getName(), DeleteRule.DELETE_ALL); + + + assertAll(BASE_ONLY_VIRTUAL_OUTLET.stream() + .map(virtualOutletRecord -> () -> compareOutletRecords( + virtualOutletRecord, virtualOutlet.getVirtualRecords()))); }, CwmsDataApiSetupCallback.getWebUser()); } - @Disabled("Currently there's a bug with compound outlets and sub-locations - this is coming back without a hyphen") + @Disabled("Currently there's a bug with virtual outlets and sub-locations - this is coming back without a hyphen") @Test - void test_compound_outlet_crud_with_sub_loc() throws Exception { + void test_virtual_outlet_crud_with_sub_loc() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - dao.storeCompoundOutlet(BASE_AND_SUB_LOC_COMPOUND_OUTLET.getProjectId().getName(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getName(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getOfficeId(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getCompoundOutletRecords(), false); - - List compoundOutletRecords = dao.retrieveOutlet( - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getName(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getOfficeId()).getCompoundOutletRecords(); - dao.deleteCompoundOutlet(BASE_AND_SUB_LOC_COMPOUND_OUTLET.getProjectId().getName(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getName(), - BASE_AND_SUB_LOC_COMPOUND_OUTLET.getLocation().getOfficeId(), - DeleteRule.DELETE_ALL); - - assertEquals(BASE_AND_SUB_LOC_COMPOUND_OUTLET.getCompoundOutletRecords().size(), - compoundOutletRecords.size()); - assertAll(BASE_AND_SUB_LOC_COMPOUND_OUTLET.getCompoundOutletRecords() - .stream() - .map(compoundOutletRecord -> () -> compareOutletRecords( - compoundOutletRecord, compoundOutletRecords))); + dao.storeVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_LOC2.getName(), + BASE_AND_SUB_VIRTUAL_OUTLET_ID.getName(), BASE_AND_SUB_VIRTUAL_OUTLET, false); + + VirtualOutlet outlet = dao.retrieveVirtualOutlet(PROJECT_LOC2.getOfficeId(), + PROJECT_LOC2.getName(), + BASE_AND_SUB_VIRTUAL_OUTLET_ID.getName()); + dao.deleteVirtualOutlet(PROJECT_LOC2.getOfficeId(), PROJECT_LOC2.getName(), + BASE_AND_SUB_VIRTUAL_OUTLET_ID.getName(), DeleteRule.DELETE_ALL); + List virtualOutletRecords = outlet.getVirtualRecords(); + + assertEquals(BASE_AND_SUB_VIRTUAL_OUTLET.size(), virtualOutletRecords.size()); + assertAll(BASE_AND_SUB_VIRTUAL_OUTLET.stream() + .map(virtualOutletRecord -> () -> compareOutletRecords( + virtualOutletRecord, virtualOutletRecords))); }, CwmsDataApiSetupCallback.getWebUser()); } - private static void compareOutletRecords(CompoundOutletRecord expectedRecord, - List receivedRecords) { + private static void compareOutletRecords(VirtualOutletRecord expectedRecord, + List receivedRecords) { //the received record outlet ids are unique, so we should assert that others don't exist in there. - CompoundOutletRecord receivedRecord = null; + VirtualOutletRecord receivedRecord = null; List errors = new ArrayList<>(); - for (CompoundOutletRecord outletRecord : receivedRecords) { - if (outletRecord.getOutletId().getName().equalsIgnoreCase(expectedRecord.getOutletId().getName()) - && outletRecord.getOutletId().getOfficeId().equalsIgnoreCase(expectedRecord.getOutletId().getOfficeId())) { + for (VirtualOutletRecord outletRecord : receivedRecords) { + if (outletRecord.getOutletId() + .getName() + .equalsIgnoreCase(expectedRecord.getOutletId().getName()) && outletRecord.getOutletId() + .getOfficeId() + .equalsIgnoreCase( + expectedRecord.getOutletId() + .getOfficeId())) { if (receivedRecord != null) { errors.add("Duplicate record for: " + outletRecord.getOutletId()); } else { @@ -303,21 +281,18 @@ private static void compareOutletRecords(CompoundOutletRecord expectedRecord, } - private static Outlet buildTestOutlet(Location outletLoc, Location projectLoc, - CompoundOutletRecord... compoundOutletRecords) { - Outlet.Builder builder = new Outlet.Builder().withProjectId( - new CwmsId.Builder().withName(projectLoc.getName()).withOfficeId(projectLoc.getOfficeId()).build()) - .withLocation(outletLoc); - if (compoundOutletRecords != null) { - builder.withCompoundOutletRecords(Arrays.asList(compoundOutletRecords)); - } - return builder.build(); + private static Outlet buildTestOutlet(Location outletLoc, CwmsId ratingGroup) { + return new Outlet.Builder().withProjectId( + new CwmsId.Builder().withName(ProjectStructureDaoIT.PROJECT_LOC2.getName()) + .withOfficeId(ProjectStructureDaoIT.PROJECT_LOC2.getOfficeId()) + .build()).withLocation(outletLoc).withRatingGroupId(ratingGroup).build(); } - private static CompoundOutletRecord buildCompoundOutletRecord(Location upstream, Location... downstream) { + private static VirtualOutletRecord buildVirtualOutletRecord(Location upstream, Location... downstream) { if (downstream == null || downstream.length == 0) { - return new CompoundOutletRecord.Builder().withOutletId( - new CwmsId.Builder().withName(upstream.getName()).withOfficeId(upstream.getOfficeId()).build()).build(); + return new VirtualOutletRecord.Builder().withOutletId( + new CwmsId.Builder().withName(upstream.getName()).withOfficeId(upstream.getOfficeId()).build()) + .build(); } List downstreamOutletIds = Arrays.stream(downstream) @@ -325,9 +300,9 @@ private static CompoundOutletRecord buildCompoundOutletRecord(Location upstream, .withOfficeId(loc.getOfficeId()) .build()) .collect(Collectors.toList()); - return new CompoundOutletRecord.Builder().withOutletId( - new CwmsId.Builder().withName(upstream.getName()).withOfficeId(upstream.getOfficeId()).build()) - .withDownstreamOutletIds(downstreamOutletIds) - .build(); + return new VirtualOutletRecord.Builder().withOutletId( + new CwmsId.Builder().withName(upstream.getName()).withOfficeId(upstream.getOfficeId()).build()) + .withDownstreamOutletIds(downstreamOutletIds) + .build(); } } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoIT.java index 59a43a0a7..24f88a6f1 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/OutletDaoIT.java @@ -20,6 +20,7 @@ package cwms.cda.data.dao.location.kind; +import com.google.common.flogger.FluentLogger; import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.LocationsDaoImpl; @@ -34,6 +35,7 @@ import org.jooq.DSLContext; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import static cwms.cda.data.dao.DaoTest.getDslContext; @@ -41,21 +43,25 @@ @Tag("integration") class OutletDaoIT extends ProjectStructureDaoIT { - private static final String TAINTER_GATE_RATING_GROUP = "Rating-" + PROJECT_LOC.getName() + "-TainterGate"; - private static final String BOX_CULVERT_RATING_GROUP = "Rating-" + PROJECT_LOC2.getName() + "-BoxCulvert"; - private static final String TAINTER_GATE_RATING_GROUP_MODIFIED = "Rating-" + PROJECT_LOC.getName() + "-TainterGate Modified"; + private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass(); + private static final CwmsId TAINTER_GATE_RATING_GROUP = new CwmsId.Builder().withName("Rating-" + PROJECT_1_ID.getName() + "-TainterGate").withOfficeId(OFFICE_ID).build(); + private static final CwmsId BOX_CULVERT_RATING_GROUP = new CwmsId.Builder().withName("Rating-" + PROJECT_2_ID.getName() + "-BoxCulvert").withOfficeId(OFFICE_ID).build(); + private static final CwmsId TAINTER_GATE_RATING_GROUP_MODIFIED = new CwmsId.Builder().withName("Rating-" + PROJECT_1_ID.getName() + "-TainterGate Modified").withOfficeId(OFFICE_ID).build(); private static final String OUTLET_KIND = "OUTLET"; - private static final Location TAINTER_GATE_1_LOC = buildProjectStructureLocation(PROJECT_LOC.getName() + "-TG1", + private static final Location TAINTER_GATE_1_LOC = buildProjectStructureLocation(PROJECT_1_ID.getName() + "-TG1", OUTLET_KIND); - private static final Location TAINTER_GATE_2_LOC = buildProjectStructureLocation(PROJECT_LOC.getName() + "-TG2", + private static final Location TAINTER_GATE_2_LOC = buildProjectStructureLocation(PROJECT_1_ID.getName() + "-TG2", OUTLET_KIND); - private static final Location TAINTER_GATE_3_LOC = buildProjectStructureLocation(PROJECT_LOC.getName() + "-TG3", + private static final Location TAINTER_GATE_3_LOC = buildProjectStructureLocation(PROJECT_1_ID.getName() + "-TG3", OUTLET_KIND); private static final Location BOX_CULVERT_1_LOC = buildProjectStructureLocation("BC1", OUTLET_KIND); private static final Outlet TAINTER_GATE_1_OUTLET = buildTestOutlet(TAINTER_GATE_1_LOC, PROJECT_LOC, TAINTER_GATE_RATING_GROUP); private static final Outlet TAINTER_GATE_2_OUTLET = buildTestOutlet(TAINTER_GATE_2_LOC, PROJECT_LOC, TAINTER_GATE_RATING_GROUP); private static final Outlet TAINTER_GATE_3_OUTLET = buildTestOutlet(TAINTER_GATE_3_LOC, PROJECT_LOC, TAINTER_GATE_RATING_GROUP); private static final Outlet BOX_CULVERT_1_OUTLET = buildTestOutlet(BOX_CULVERT_1_LOC, PROJECT_LOC2, BOX_CULVERT_RATING_GROUP); + private static final CwmsId TG_LOC4_ID = new CwmsId.Builder().withOfficeId(OFFICE_ID) + .withName(PROJECT_2_ID.getName() + "-TG4") + .build(); @BeforeAll static void setup() throws Exception { @@ -63,38 +69,52 @@ static void setup() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); - LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); OutletDao outletDao = new OutletDao(context); + deleteLocation(context, TG_LOC4_ID.getName(), TG_LOC4_ID.getOfficeId()); try { - locationsDao.storeLocation(TAINTER_GATE_1_LOC); - locationsDao.storeLocation(TAINTER_GATE_2_LOC); - locationsDao.storeLocation(TAINTER_GATE_3_LOC); - locationsDao.storeLocation(BOX_CULVERT_1_LOC); - outletDao.storeOutlet(TAINTER_GATE_1_OUTLET, TAINTER_GATE_1_OUTLET.getRatingGroupId(), false); - outletDao.storeOutlet(TAINTER_GATE_2_OUTLET, TAINTER_GATE_2_OUTLET.getRatingGroupId(), false); - outletDao.storeOutlet(BOX_CULVERT_1_OUTLET, BOX_CULVERT_1_OUTLET.getRatingGroupId(), false); + storeLocation(context, TAINTER_GATE_1_LOC); + storeLocation(context, TAINTER_GATE_2_LOC); + storeLocation(context, TAINTER_GATE_3_LOC); + storeLocation(context, BOX_CULVERT_1_LOC); + outletDao.storeOutlet(TAINTER_GATE_1_OUTLET, false); + outletDao.storeOutlet(TAINTER_GATE_2_OUTLET, false); + outletDao.storeOutlet(BOX_CULVERT_1_OUTLET, false); } catch (IOException e) { throw new RuntimeException(e); } }, CwmsDataApiSetupCallback.getWebUser()); } + static void deleteLocation(DSLContext context, String locId, String officeId) { + LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); + try { + locationsDao.deleteLocation(locId, officeId, true); + } catch (NotFoundException ex) { + LOGGER.atFinest().withCause(ex).log("No data found for " + officeId + "." + locId); + } + } + + static void storeLocation(DSLContext context, Location loc) throws IOException { + LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); + deleteLocation(context, loc.getName(), loc.getOfficeId()); + locationsDao.storeLocation(loc); + } + @AfterAll static void tearDown() throws Exception { CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); - LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); OutletDao outletDao = new OutletDao(context); - outletDao.deleteOutlet(TAINTER_GATE_1_LOC.getName(), TAINTER_GATE_1_LOC.getOfficeId(), + outletDao.deleteOutlet(TAINTER_GATE_1_LOC.getOfficeId(), TAINTER_GATE_1_LOC.getName(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(TAINTER_GATE_2_LOC.getName(), TAINTER_GATE_2_LOC.getOfficeId(), + outletDao.deleteOutlet(TAINTER_GATE_2_LOC.getOfficeId(), TAINTER_GATE_2_LOC.getName(), DeleteRule.DELETE_ALL); - outletDao.deleteOutlet(BOX_CULVERT_1_LOC.getName(), BOX_CULVERT_1_LOC.getOfficeId(), DeleteRule.DELETE_ALL); - locationsDao.deleteLocation(TAINTER_GATE_1_LOC.getName(), TAINTER_GATE_1_LOC.getBoundingOfficeId(), true); - locationsDao.deleteLocation(TAINTER_GATE_2_LOC.getName(), TAINTER_GATE_2_LOC.getBoundingOfficeId(), true); - locationsDao.deleteLocation(TAINTER_GATE_3_LOC.getName(), TAINTER_GATE_3_LOC.getBoundingOfficeId(), true); - locationsDao.deleteLocation(BOX_CULVERT_1_LOC.getName(), BOX_CULVERT_1_LOC.getBoundingOfficeId(), true); + outletDao.deleteOutlet(BOX_CULVERT_1_LOC.getOfficeId(), BOX_CULVERT_1_LOC.getName(), DeleteRule.DELETE_ALL); + deleteLocation(context, TAINTER_GATE_1_LOC.getName(), TAINTER_GATE_1_LOC.getOfficeId()); + deleteLocation(context, TAINTER_GATE_2_LOC.getName(), TAINTER_GATE_2_LOC.getOfficeId()); + deleteLocation(context, TAINTER_GATE_3_LOC.getName(), TAINTER_GATE_3_LOC.getOfficeId()); + deleteLocation(context, BOX_CULVERT_1_LOC.getName(), BOX_CULVERT_1_LOC.getOfficeId()); }, CwmsDataApiSetupCallback.getWebUser()); tearDownProject(); } @@ -105,7 +125,7 @@ void test_outlet_with_base_loc_only() throws Exception { databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - Outlet outlet = dao.retrieveOutlet(BOX_CULVERT_1_LOC.getName(), BOX_CULVERT_1_LOC.getOfficeId()); + Outlet outlet = dao.retrieveOutlet(BOX_CULVERT_1_LOC.getOfficeId(), BOX_CULVERT_1_LOC.getName()); DTOMatch.assertMatch(BOX_CULVERT_1_OUTLET, outlet); }, CwmsDataApiSetupCallback.getWebUser()); } @@ -116,36 +136,34 @@ void test_outlet_crud() throws Exception { databaseLink.connection(c -> { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - List initialOutlets = dao.retrieveOutletsForProject(PROJECT_LOC.getName(), - PROJECT_LOC.getBoundingOfficeId()); - - //This shouldn't exist in the DB yet. - dao.storeOutlet(TAINTER_GATE_3_OUTLET, TAINTER_GATE_3_OUTLET.getRatingGroupId(), true); - - List retrievedOutlets = dao.retrieveOutletsForProject(PROJECT_LOC.getName(), - PROJECT_LOC.getBoundingOfficeId()); - assertNotEquals(initialOutlets.size(), retrievedOutlets.size()); - DTOMatch.assertMatch(TAINTER_GATE_3_OUTLET, retrievedOutlets.get(2)); - - Outlet newOutlet = new Outlet.Builder(TAINTER_GATE_3_OUTLET) - .withRatingGroupId(TAINTER_GATE_RATING_GROUP_MODIFIED) - .build(); - dao.storeOutlet(newOutlet, TAINTER_GATE_RATING_GROUP_MODIFIED, false); - Outlet updatedOutlet = dao.retrieveOutlet(TAINTER_GATE_3_LOC.getName(), - TAINTER_GATE_3_LOC.getBoundingOfficeId()); - - //DELETE_KEY will just remove the AT_OUTLET key, and since we don't have any changes this will be fine. - dao.deleteOutlet(TAINTER_GATE_3_LOC.getName(), TAINTER_GATE_3_LOC.getBoundingOfficeId(), - DeleteRule.DELETE_KEY); - - DTOMatch.assertMatch(newOutlet, updatedOutlet); - - List finalOutlets = dao.retrieveOutletsForProject(PROJECT_LOC.getName(), - PROJECT_LOC.getBoundingOfficeId()); - assertEquals(initialOutlets.size(), finalOutlets.size()); - - assertThrows(NotFoundException.class, () -> dao.retrieveOutlet(TAINTER_GATE_3_LOC.getName(), - TAINTER_GATE_3_LOC.getBoundingOfficeId())); + + //Delete + dao.deleteOutlet(TAINTER_GATE_2_LOC.getOfficeId(), TAINTER_GATE_2_LOC.getName(), DeleteRule.DELETE_KEY); + + //Retrieve for project + List retrievedOutlets = dao.retrieveOutletsForProject(PROJECT_1_ID.getOfficeId(), + PROJECT_1_ID.getName()); + doesNotContainOutlet(retrievedOutlets, TAINTER_GATE_2_OUTLET); + + //Create + Outlet modifiedOutlet = new Outlet.Builder(TAINTER_GATE_2_OUTLET).withRatingGroupId( + TAINTER_GATE_RATING_GROUP_MODIFIED).build(); + dao.storeOutlet(modifiedOutlet, true); + + //Single retrieve + Outlet retrievedModifiedOutlet = dao.retrieveOutlet(TAINTER_GATE_2_LOC.getOfficeId(), + TAINTER_GATE_2_LOC.getName()); + DTOMatch.assertMatch(modifiedOutlet, retrievedModifiedOutlet); + + //Update (back to original) + dao.storeOutlet(TAINTER_GATE_2_OUTLET, false); + + List finalOutlets = dao.retrieveOutletsForProject(PROJECT_1_ID.getOfficeId(), + PROJECT_1_ID.getName()); + containsOutlet(finalOutlets, TAINTER_GATE_2_OUTLET); + + assertThrows(NotFoundException.class, () -> dao.retrieveOutlet(TAINTER_GATE_3_LOC.getOfficeId(), + TAINTER_GATE_3_LOC.getName())); }, CwmsDataApiSetupCallback.getWebUser()); } @@ -156,18 +174,66 @@ void test_retrieve_outlets_for_project() throws Exception { DSLContext context = getDslContext(c, OFFICE_ID); OutletDao dao = new OutletDao(context); - List outlets = dao.retrieveOutletsForProject(PROJECT_LOC.getName(), - PROJECT_LOC.getBoundingOfficeId()); + List outlets = dao.retrieveOutletsForProject(PROJECT_1_ID.getOfficeId(), + PROJECT_1_ID.getName()); + containsOutlet(outlets, TAINTER_GATE_1_OUTLET); + containsOutlet(outlets, TAINTER_GATE_2_OUTLET); + }, CwmsDataApiSetupCallback.getWebUser()); + } - assertEquals(2, outlets.size()); - DTOMatch.assertMatch(TAINTER_GATE_1_OUTLET, outlets.get(0)); - DTOMatch.assertMatch(TAINTER_GATE_2_OUTLET, outlets.get(1)); + private void containsOutlet(List outlets, Outlet expectedOutlet) { + String name = expectedOutlet.getLocation().getName(); + Outlet receivedOutlet = outlets.stream() + .filter(outlet -> outlet.getLocation() + .getName() + .equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + assertNotNull(receivedOutlet); + DTOMatch.assertMatch(expectedOutlet, receivedOutlet); + } + + private void doesNotContainOutlet(List outlets, Outlet expectedOutlet) { + String name = expectedOutlet.getLocation().getName(); + Outlet receivedOutlet = outlets.stream() + .filter(outlet -> outlet.getLocation() + .getName() + .equalsIgnoreCase(name)) + .findFirst() + .orElse(null); + assertNull(receivedOutlet); + } + + @Disabled("Disabled due to a DB issue. See https://jira.hecdev.net/browse/CWDB-296") + @Test + void test_rename_outlets() throws Exception { + CwmsDatabaseContainer databaseLink = CwmsDataApiSetupCallback.getDatabaseLink(); + databaseLink.connection(c -> { + DSLContext context = getDslContext(c, OFFICE_ID); + OutletDao dao = new OutletDao(context); + + //Shouldn't exist in the db. + dao.storeOutlet(TAINTER_GATE_3_OUTLET, true); + dao.renameOutlet(OFFICE_ID, TAINTER_GATE_3_LOC.getName(), TG_LOC4_ID.getName()); + Outlet outlet = dao.retrieveOutlet(TG_LOC4_ID.getOfficeId(), TG_LOC4_ID.getName()); + assertThrows(NotFoundException.class, () -> dao.retrieveOutlet(OFFICE_ID, TAINTER_GATE_3_LOC.getName())); + assertNotNull(outlet); + dao.deleteOutlet(TG_LOC4_ID.getOfficeId(), TG_LOC4_ID.getName(), DeleteRule.DELETE_KEY); + + //Location gets renamed, so let's delete the new location, then store the old one. + LocationsDaoImpl locationsDao = new LocationsDaoImpl(context); + locationsDao.deleteLocation(TG_LOC4_ID.getName(), TG_LOC4_ID.getOfficeId(), true); + try { + locationsDao.storeLocation(TAINTER_GATE_3_LOC); + } catch (IOException e) { + throw new RuntimeException(e); + } }, CwmsDataApiSetupCallback.getWebUser()); } - private static Outlet buildTestOutlet(Location outletLoc, Location projectLoc, String ratingId) { + private static Outlet buildTestOutlet(Location outletLoc, Location projectLoc, CwmsId ratingId) { return new Outlet.Builder().withProjectId(new CwmsId.Builder().withName(projectLoc.getName()) - .withOfficeId(projectLoc.getBoundingOfficeId()) + .withOfficeId(projectLoc.getOfficeId()) .build()) .withLocation(outletLoc) .withRatingGroupId(ratingId) diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/ProjectStructureDaoIT.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/ProjectStructureDaoIT.java index f54bcdb73..eac20d5d5 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/ProjectStructureDaoIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/location/kind/ProjectStructureDaoIT.java @@ -11,6 +11,7 @@ import cwms.cda.api.enums.Nation; import cwms.cda.data.dao.DeleteRule; import cwms.cda.data.dao.LocationsDaoImpl; +import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; import fixtures.CwmsDataApiSetupCallback; import fixtures.TestAccounts; @@ -31,6 +32,12 @@ abstract class ProjectStructureDaoIT extends DataApiTestIT protected static final String OFFICE_ID = TestAccounts.KeyUser.SPK_NORMAL.getOperatingOffice(); static final Location PROJECT_LOC = buildProjectLocation("PROJECT1"); static final Location PROJECT_LOC2 = buildProjectLocation("PROJECT2"); + static final CwmsId PROJECT_1_ID = new CwmsId.Builder().withName(PROJECT_LOC.getName()) + .withOfficeId(PROJECT_LOC.getOfficeId()) + .build(); + static final CwmsId PROJECT_2_ID = new CwmsId.Builder().withName(PROJECT_LOC2.getName()) + .withOfficeId(PROJECT_LOC2.getOfficeId()) + .build(); static void setupProject() throws Exception { //Don't tag this as a @BeforeAll - JUnit can't guarantee this occurs first. @@ -102,20 +109,22 @@ static PROJECT_OBJ_T buildProject(Location location) { static Location buildProjectStructureLocation(String locationId, String locationKind) { return new Location.Builder(locationId, locationKind, ZoneId.of("UTC"), - 38.5613824, -121.7298432, "NVGD29", OFFICE_ID) - .withElevation(10.0) - .withElevationUnits("m") - .withLocationType("SITE") - .withCountyName("Sacramento") - .withNation(Nation.US) - .withActive(true) - .withStateInitial("CA") - .withBoundingOfficeId(OFFICE_ID) - .withPublishedLatitude(38.5613824) - .withPublishedLongitude(-121.7298432) - .withLongName("UNITED STATES") - .withDescription("for testing") - .withNearestCity("Davis") - .build(); + 38.5613824, -121.7298432, "NVGD29", OFFICE_ID) + .withPublicName("Integration Test " + locationId) + .withLongName("Integration Test " + locationId + " " + locationKind) + .withElevation(10.0) + .withElevationUnits("m") + .withLocationType("SITE") + .withCountyName("Sacramento") + .withNation(Nation.US) + .withActive(true) + .withStateInitial("CA") + .withBoundingOfficeId(OFFICE_ID) + .withPublishedLatitude(38.5613824) + .withPublishedLongitude(-121.7298432) + .withLongName("UNITED STATES") + .withDescription("for testing") + .withNearestCity("Davis") + .build(); } } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneIdTest.java similarity index 74% rename from cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneTest.java rename to cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneIdTest.java index 14a7b95c7..c04e31184 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/TimeZoneIdTest.java @@ -9,10 +9,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.util.List; @@ -21,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.*; -class TimeZoneTest +class TimeZoneIdTest { @ParameterizedTest @@ -29,9 +26,9 @@ class TimeZoneTest void test_serialization(SerializationType test) { String tz = "UTC"; - TimeZone expectedTz = new TimeZone(tz); + TimeZoneId expectedTz = new TimeZoneId(tz); String json = Formats.format(test._contentType, expectedTz); - TimeZone receivedTz = Formats.parseContent(test._contentType, json, TimeZone.class); + TimeZoneId receivedTz = Formats.parseContent(test._contentType, json, TimeZoneId.class); assertEquals(expectedTz.getTimeZone(), receivedTz.getTimeZone()); } @@ -39,7 +36,7 @@ void test_serialization(SerializationType test) void test_getTimeZone() { String expectedTz = "UTC"; - TimeZone zone = new TimeZone(expectedTz); + TimeZoneId zone = new TimeZoneId(expectedTz); String receivedZone = zone.getTimeZone(); assertEquals(expectedTz, receivedZone); } @@ -47,7 +44,7 @@ void test_getTimeZone() @Test void test_getTimeZone_null() { - TimeZone zone = new TimeZone(); + TimeZoneId zone = new TimeZoneId(); String receivedZone = zone.getTimeZone(); assertNull(receivedZone); } @@ -56,7 +53,7 @@ void test_getTimeZone_null() void test_validate() { String expectedTz = "UTC"; - TimeZone zone = new TimeZone(expectedTz); + TimeZoneId zone = new TimeZoneId(expectedTz); assertDoesNotThrow(zone::validate); } @@ -64,7 +61,7 @@ void test_validate() void test_validate_failure() { String expectedTz = "Not a Time Zone"; - TimeZone zone = new TimeZone(expectedTz); + TimeZoneId zone = new TimeZoneId(expectedTz); assertThrows(FieldException.class, zone::validate); } @@ -72,15 +69,15 @@ void test_validate_failure() void test_serialize_list() { ContentType contentType = new ContentType(Formats.JSONV2); - TimeZones tzs = new TimeZones(ZoneId.getAvailableZoneIds() + TimeZoneIds tzs = new TimeZoneIds(ZoneId.getAvailableZoneIds() .stream() - .map(TimeZone::new) + .map(TimeZoneId::new) .collect(Collectors.toList())); String json = Formats.format(contentType, tzs); - TimeZones receivedTzs = Formats.parseContent(contentType, json, TimeZones.class); + TimeZoneIds receivedTzs = Formats.parseContent(contentType, json, TimeZoneIds.class); - List expectedZones = tzs.getTimeZones(); - List receivedZones = receivedTzs.getTimeZones(); + List expectedZones = tzs.getTimeZones(); + List receivedZones = receivedTzs.getTimeZones(); assertEquals(expectedZones.size(), receivedZones.size()); @@ -96,11 +93,11 @@ void test_from_resource_file() throws Exception assertNotNull(resource); String json = IOUtils.toString(resource, StandardCharsets.UTF_8); ContentType contentType = new ContentType(Formats.JSONV2); - TimeZones receivedTzs = Formats.parseContent(contentType, json, TimeZones.class); + TimeZoneIds receivedTzs = Formats.parseContent(contentType, json, TimeZoneIds.class); assertTrue(!receivedTzs.getTimeZones().isEmpty()); } - private Executable testZone(TimeZone expected, TimeZone received) + private Executable testZone(TimeZoneId expected, TimeZoneId received) { return () -> assertEquals(expected.getTimeZone(), received.getTimeZone()); } diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/OutletTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/OutletTest.java index 0dddbc4aa..c6e6fe141 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/OutletTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/OutletTest.java @@ -29,19 +29,16 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.ZoneId; -import java.util.Arrays; -import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNotNull; final class OutletTest { private static final String SPK = "SPK"; - private static final String PROJECT_LOC = "location"; - private static final String OUTLET_LOC = PROJECT_LOC + "-outlet"; - private static final String BASE_OUTLET_LOC = "outlet"; - private static final String COMPOUND_OUTLET_LOC1 = "CO1"; - private static final String RATING_GROUP_ID = "Rating-" + OUTLET_LOC; + private static final String PROJECT_LOC = "BIGH"; + private static final String OUTLET_LOC = PROJECT_LOC + "-TG1"; + private static final String BASE_OUTLET_LOC = "TG2"; + private static final CwmsId RATING_GROUP_ID = new CwmsId.Builder().withName("Rating-" + OUTLET_LOC).withOfficeId(SPK).build(); @Test void test_serialization() { @@ -85,47 +82,7 @@ void test_serialize_from_file_base_loc_only() throws Exception { DTOMatch.assertMatch(outlet, deserialized); } - @Test - void test_serialize_compound_outlets() { - ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); - Outlet outlet = buildTestOutlet(COMPOUND_OUTLET_LOC1, - buildCompoundOutletRecord("TG1", SPK, "TG2"), - buildCompoundOutletRecord("TG2", SPK, "TG3"), - buildCompoundOutletRecord("TG3", SPK)); - String json = Formats.format(contentType, outlet); - - Outlet parsedOutlet = Formats.parseContent(contentType, json, Outlet.class); - DTOMatch.assertMatch(outlet, parsedOutlet); - } - - @Test - void test_serialize_compound_outlets_from_file() throws Exception { - ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); - Outlet outlet = buildTestOutlet(COMPOUND_OUTLET_LOC1, - buildCompoundOutletRecord("TG1", SPK, "TG2"), - buildCompoundOutletRecord("TG2", SPK, "TG3"), - buildCompoundOutletRecord("TG3", SPK, "TG4", "TG5"), - buildCompoundOutletRecord("TG4", SPK), - buildCompoundOutletRecord("TG5", SPK)); - InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/data/dto/location/kind/compound_outlet.json"); - assertNotNull(resource); - String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8); - Outlet deserialized = Formats.parseContent(contentType, serialized, Outlet.class); - DTOMatch.assertMatch(outlet, deserialized); - } - - private CompoundOutletRecord buildCompoundOutletRecord(String outletId, String officeId, String ... downstreamOutlets) { - return new CompoundOutletRecord.Builder() - .withOutletId(new CwmsId.Builder().withName(outletId).withOfficeId(officeId).build()) - .withDownstreamOutletIds(Arrays.stream(downstreamOutlets) - .map(dso -> new CwmsId.Builder().withOfficeId(officeId) - .withName(dso) - .build()) - .collect(Collectors.toList())) - .build(); - } - - private Outlet buildTestOutlet(String outletLocId, CompoundOutletRecord ... records) { + private Outlet buildTestOutlet(String outletLocId) { CwmsId identifier = new CwmsId.Builder() .withName(PROJECT_LOC) .withOfficeId(SPK) @@ -143,7 +100,6 @@ private Outlet buildTestOutlet(String outletLocId, CompoundOutletRecord ... reco return new Outlet.Builder().withProjectId(identifier) .withLocation(loc) .withRatingGroupId(RATING_GROUP_ID) - .withCompoundOutletRecords(Arrays.asList(records)) .build(); } } \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/CompoundOutletRecordTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletRecordTest.java similarity index 78% rename from cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/CompoundOutletRecordTest.java rename to cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletRecordTest.java index c2dbf1ee5..be13cc99c 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/CompoundOutletRecordTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletRecordTest.java @@ -23,7 +23,6 @@ import cwms.cda.data.dto.CwmsId; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; -import cwms.cda.formatters.json.JsonV2; import cwms.cda.helpers.DTOMatch; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -33,39 +32,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -class CompoundOutletRecordTest { +class VirtualOutletRecordTest { private static final String SPK = "SPK"; @Test void test_serialization() { ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); - CompoundOutletRecord data = buildTestData(); + VirtualOutletRecord data = buildTestData(); String json = Formats.format(contentType, data); - CompoundOutletRecord deserialized = Formats.parseContent(contentType, json, CompoundOutletRecord.class); + VirtualOutletRecord deserialized = Formats.parseContent(contentType, json, VirtualOutletRecord.class); DTOMatch.assertMatch(data, deserialized); } @Test void test_serialize_from_file() throws Exception { ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); - CompoundOutletRecord data = buildTestData(); + VirtualOutletRecord data = buildTestData(); InputStream resource = this.getClass() - .getResourceAsStream("/cwms/cda/data/dto/location/kind/compound_outlet_record.json"); + .getResourceAsStream("/cwms/cda/data/dto/location/kind/virtual-outlet-record.json"); assertNotNull(resource); String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8); - CompoundOutletRecord deserialized = Formats.parseContent(contentType, serialized, CompoundOutletRecord.class); + VirtualOutletRecord deserialized = Formats.parseContent(contentType, serialized, VirtualOutletRecord.class); DTOMatch.assertMatch(data, deserialized); } - private CompoundOutletRecord buildTestData() { - return new CompoundOutletRecord.Builder().withOutletId(new CwmsId.Builder().withName("TG01").withOfficeId(SPK).build()) - .withDownstreamOutletIds(Arrays.asList( + private VirtualOutletRecord buildTestData() { + return new VirtualOutletRecord.Builder().withOutletId(new CwmsId.Builder().withName("TG01").withOfficeId(SPK).build()) + .withDownstreamOutletIds(Arrays.asList( new CwmsId.Builder().withName("TG02") .withOfficeId(SPK) .build(), new CwmsId.Builder().withName("TG03") .withOfficeId(SPK) .build())) - .build(); + .build(); } } \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletTest.java new file mode 100644 index 000000000..edb65970e --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/location/kind/VirtualOutletTest.java @@ -0,0 +1,81 @@ +/* + * MIT License + * Copyright (c) 2024 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.location.kind; + +import cwms.cda.data.dto.CwmsId; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import cwms.cda.helpers.DTOMatch; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class VirtualOutletTest { + private static final String SPK = "SPK"; + + @Test + void test_serialization() { + ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); + VirtualOutlet data = buildTestData(); + String json = Formats.format(contentType, data); + + VirtualOutlet deserialized = Formats.parseContent(contentType, json, VirtualOutlet.class); + DTOMatch.assertMatch(data, deserialized); + } + + @Test + void test_serialize_from_file() throws Exception { + ContentType contentType = Formats.parseHeader(Formats.JSON, Outlet.class); + VirtualOutlet data = buildTestData(); + InputStream resource = this.getClass() + .getResourceAsStream("/cwms/cda/data/dto/location/kind/virtual-outlet.json"); + assertNotNull(resource); + String serialized = IOUtils.toString(resource, StandardCharsets.UTF_8); + VirtualOutlet deserialized = Formats.parseContent(contentType, serialized, VirtualOutlet.class); + DTOMatch.assertMatch(data, deserialized); + } + + private VirtualOutlet buildTestData() { + CwmsId.Builder builder = new CwmsId.Builder().withOfficeId(SPK); + return new VirtualOutlet.Builder().withProjectId(builder.withName("BIGH").build()) + .withVirtualOutletId(builder.withName("Compound Tainter Gates").build()) + .withVirtualRecords( + Arrays.asList(buildRecord("TG1", "TG2"), buildRecord("TG2", "TG3"), + buildRecord("TG3", "TG4", "TG5"), buildRecord("TG4"), + buildRecord("TG5"))) + .build(); + } + + private VirtualOutletRecord buildRecord(String upstream, String ... downstream) { + CwmsId.Builder builder = new CwmsId.Builder().withOfficeId(SPK); + List downstreamIds = Arrays.stream(downstream) + .map(id -> builder.withName(id).build()) + .collect(Collectors.toList()); + return new VirtualOutletRecord.Builder().withOutletId(builder.withName(upstream).build()) + .withDownstreamOutletIds(downstreamIds) + .build(); + } +} diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectTest.java index 6df7bf85e..578e5a801 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectTest.java @@ -24,6 +24,11 @@ package cwms.cda.data.dto.project; +import static helpers.SameEpochMillis.assertSameEpoch; +import static org.junit.jupiter.api.Assertions.assertAll; +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 com.fasterxml.jackson.databind.ObjectWriter; @@ -38,8 +43,6 @@ import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class ProjectTest { @@ -104,7 +107,6 @@ void testDeserialize() throws IOException { Location loc = project.getLocation(); assertNotNull(loc); - assertAll( () -> assertEquals("SPK", loc.getOfficeId(), "Office ID does not match"), () -> assertEquals("Project Id", loc.getName(), "Project ID does not match"), @@ -114,10 +116,10 @@ void testDeserialize() throws IOException { () -> assertEquals(50.0, project.getNonFederalCost().doubleValue(), "Non-federal Cost does not match"), () -> assertEquals(10.0, project.getFederalOAndMCost().doubleValue(), "Federal O And M Cost does not match"), () -> assertEquals(5.0, project.getNonFederalOAndMCost().doubleValue(), "Non-Federal O And M Cost does not match"), - () -> assertEquals(1717199914902L, project.getCostYear().toEpochMilli(), "Cost Year does not match"), + () -> assertSameEpoch( 1717199914902L, project.getCostYear(), "Cost Year does not match"), () -> assertEquals("$", project.getCostUnit(), "Cost Unit does not match"), - () -> assertEquals(1717199914902L, project.getYieldTimeFrameStart().toEpochMilli(), "Yield Time Frame Start does not match"), - () -> assertEquals(1717199914902L, project.getYieldTimeFrameEnd().toEpochMilli(), "Yield Time Frame End does not match"), + () -> assertSameEpoch( 1717199914902L, project.getYieldTimeFrameStart(), "Yield Time Frame Start does not match"), + () -> assertSameEpoch( 1717199914902L, project.getYieldTimeFrameEnd(), "Yield Time Frame End does not match"), () -> assertEquals("Remarks", project.getProjectRemarks(), "Project Remarks do not match"), () -> assertEquals("Pumpback Location Id", project.getPumpBackLocation().getName(), "Pumpback Location ID does not match"), () -> assertEquals("SPK", project.getPumpBackLocation().getOfficeId(), "Pumpback Location Office ID does not match"), diff --git a/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java b/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java index 149dfb5b2..0eaf5a797 100644 --- a/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java +++ b/cwms-data-api/src/test/java/cwms/cda/helpers/DTOMatch.java @@ -24,6 +24,7 @@ package cwms.cda.helpers; +import cwms.cda.data.dto.location.kind.VirtualOutlet; import cwms.cda.data.dto.stream.StreamLocationNode; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -31,7 +32,7 @@ import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.LookupType; -import cwms.cda.data.dto.location.kind.CompoundOutletRecord; +import cwms.cda.data.dto.location.kind.VirtualOutletRecord; import cwms.cda.data.dto.location.kind.Embankment; import cwms.cda.data.dto.location.kind.Outlet; import cwms.cda.data.dto.location.kind.Turbine; @@ -199,30 +200,29 @@ public static void assertMatch(Outlet first, Outlet second) { assertAll( () -> assertMatch(first.getProjectId(), second.getProjectId()), () -> assertEquals(first.getLocation(), second.getLocation()), - () -> assertEquals(first.getRatingGroupId(), second.getRatingGroupId()), - () -> assertMatch(first.getCompoundOutletRecords(), second.getCompoundOutletRecords(), DTOMatch::assertMatch) + () -> assertMatch(first.getRatingGroupId(), second.getRatingGroupId()) ); } - public static void assertMatch(CompoundOutletRecord first, CompoundOutletRecord second) { + public static void assertMatch(VirtualOutletRecord first, VirtualOutletRecord second) { assertAll(() -> assertMatch(first.getOutletId(), second.getOutletId()), - () -> assertEquals(first.getDownstreamOutletIds().size(), second.getDownstreamOutletIds().size()), - () -> assertAll(IntStream.range(0, first.getDownstreamOutletIds().size()) - .mapToObj(i -> () -> DTOMatch.assertMatch( - first.getDownstreamOutletIds().get(i), - second.getDownstreamOutletIds().get(i), - "Downstream Outlet Id " + i))) + () -> assertMatch(first.getDownstreamOutletIds(), second.getDownstreamOutletIds(), DTOMatch::assertMatch) ); } - private static void assertMatch(List first, List second, AssertMatchMethod matcher) { + public static void assertMatch(VirtualOutlet first, VirtualOutlet second) { + assertAll(() -> assertMatch(first.getVirtualOutletId(), second.getVirtualOutletId()), + () -> assertMatch(first.getVirtualRecords(), second.getVirtualRecords(), DTOMatch::assertMatch)); + } + + public static void assertMatch(List first, List second, AssertMatchMethod matcher) { assertAll(() -> assertEquals(first.size(), second.size()), () -> assertAll(IntStream.range(0, first.size()) .mapToObj(i -> () -> matcher.assertMatch(first.get(i), second.get(i))))); } @FunctionalInterface - private interface AssertMatchMethod{ + public interface AssertMatchMethod{ void assertMatch(T first, T second); } } diff --git a/cwms-data-api/src/test/java/fixtures/CwmsDataApiSetupCallback.java b/cwms-data-api/src/test/java/fixtures/CwmsDataApiSetupCallback.java index 7b2f7ddfd..e8688adce 100644 --- a/cwms-data-api/src/test/java/fixtures/CwmsDataApiSetupCallback.java +++ b/cwms-data-api/src/test/java/fixtures/CwmsDataApiSetupCallback.java @@ -13,6 +13,7 @@ import org.apache.commons.io.IOUtils; import mil.army.usace.hec.test.database.CwmsDatabaseContainer; +import mil.army.usace.hec.test.database.TeamCityUtilities; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -61,7 +62,7 @@ public void beforeAll(ExtensionContext context) throws Exception { cwmsDb = new CwmsDatabaseContainer(ORACLE_IMAGE) .withOfficeEroc("s0") .withOfficeId("HQ") - .withVolumeName(ORACLE_VOLUME) + .withVolumeName(TeamCityUtilities.cleanupBranchName(ORACLE_VOLUME)) .withSchemaImage(CWMS_DB_IMAGE); cwmsDb.start(); diff --git a/cwms-data-api/src/test/java/helpers/SameEpochMillis.java b/cwms-data-api/src/test/java/helpers/SameEpochMillis.java new file mode 100644 index 000000000..25f5dd2ff --- /dev/null +++ b/cwms-data-api/src/test/java/helpers/SameEpochMillis.java @@ -0,0 +1,100 @@ +package helpers; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import cwms.cda.formatters.json.JsonV2; +import java.time.Instant; +import java.time.ZonedDateTime; +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class SameEpochMillis extends TypeSafeMatcher { + private final long expected; + + public SameEpochMillis(long expected) { + this.expected = expected; + } + + @Override + public void describeTo(Description description) { + description.appendText(" must parse to the same Instant as " + expected + " (" + Instant.ofEpochMilli(expected) + ")"); + } + + @Override + protected boolean matchesSafely(Long inputMilli) { + return expected == inputMilli; + } + + public static Instant jacksonParse(String inputStr) throws JsonProcessingException { + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + ObjectReader objectReader = objectMapper.readerFor(Holder.class); + Holder obj = objectReader.readValue("{\"instant\":\"" + inputStr + "\"}"); + return obj.instant; + } + + @Factory + public static Matcher sameEpochMillis(String expectedStr) { + Instant instant; + + try { + instant = jacksonParse(expectedStr); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return sameEpochMillis(instant); + } + + @Factory + public static Matcher sameEpochMillis(Instant expectedInstant) { + return sameEpochMillis(expectedInstant.toEpochMilli()); + } + + @Factory + public static Matcher sameEpochMillis(long expected) { + return new SameEpochMillis(expected); + } + + @Factory + public static Matcher sameEpochMillis(ZonedDateTime expectedZdt) { + return sameEpochMillis(expectedZdt.toInstant()); + } + + private static class Holder { + public Holder() { + } + + public Instant instant; + } + + // junit style + public static void assertSameEpoch(long expected, Long actual, String message) { + assertEquals(expected, actual, message); + } + + public static void assertSameEpoch(long expected, String actual, String message) throws JsonProcessingException { + Instant actualInstant = jacksonParse(actual); + assertSameEpoch(expected, actualInstant, message); + } + + public static void assertSameEpoch(long expected, Instant actualInstant, String message) { + assertEquals(expected, actualInstant.toEpochMilli(), message); + } + + public static void assertSameEpoch(Instant expected, Long actual, String message) { + assertThat(message, actual, sameEpochMillis(expected)); + } + + public static void assertSameEpoch(ZonedDateTime expected, Long actual, String message) { + assertThat(message, actual, sameEpochMillis(expected)); + } + + public static void assertSameEpoch(String expected, Long actual, String message) { + assertThat(message, actual, sameEpochMillis(expected)); + } +} diff --git a/cwms-data-api/src/test/java/helpers/SameEpochMillisTest.java b/cwms-data-api/src/test/java/helpers/SameEpochMillisTest.java new file mode 100644 index 000000000..da04695d8 --- /dev/null +++ b/cwms-data-api/src/test/java/helpers/SameEpochMillisTest.java @@ -0,0 +1,28 @@ +package helpers; + +import static helpers.SameEpochMillis.assertSameEpoch; +import static helpers.SameEpochMillis.sameEpochMillis; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.Instant; +import org.junit.jupiter.api.Test; + +class SameEpochMillisTest { + @Test + void testSameEpochMillis() { + + Instant expected = Instant.parse("2021-06-21T08:00:00Z"); + + assertAll( + () -> assertThat(1624262400000L, sameEpochMillis("2021-06-21T08:00:00Z")), + () -> assertThat(1624262400000L, sameEpochMillis(expected)), + () -> assertThat(1624262400000L, sameEpochMillis("2021-06-21T08:00:00Z")), + () -> assertThat(1624262400000L, sameEpochMillis("1624262400000")), + () -> assertThat(1624262400000L, sameEpochMillis(1624262400000L)) + // This is meant to be used with RestAssured so we really just want the jackson supported formats. + ); + + assertSameEpoch("2021-06-21T08:00:00Z",1624262400000L, "Should be the same" ); + } +} \ No newline at end of file diff --git a/cwms-data-api/src/test/java/helpers/SameInstantString.java b/cwms-data-api/src/test/java/helpers/SameInstantString.java new file mode 100644 index 000000000..adce375c2 --- /dev/null +++ b/cwms-data-api/src/test/java/helpers/SameInstantString.java @@ -0,0 +1,86 @@ +package helpers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import cwms.cda.formatters.json.JsonV2; +import java.time.Instant; +import java.time.ZonedDateTime; +import org.hamcrest.Description; +import org.hamcrest.Factory; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public class SameInstantString extends TypeSafeMatcher { + private final Instant expected; + + + public SameInstantString(Instant expected) { + this.expected = expected; + } + + @Override + public void describeTo(Description description) { + description.appendText(" must parse to the same Instant as " + expected); + } + + @Override + protected boolean matchesSafely(String inputStr) { + + Instant inputInstant; + try { + inputInstant = jacksonParse(inputStr); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + + return inputInstant.equals(expected); + } + + public static Instant jacksonParse(String inputStr) throws JsonProcessingException { + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + ObjectReader objectReader = objectMapper.readerFor(Holder.class); + Holder obj = objectReader.readValue("{\"instant\":\"" + inputStr + "\"}"); + return obj.instant; + } + + @Factory + public static Matcher sameInstant(String expectedStr) { + Instant instant; + +// //Parse expectedStr into Instant using DateUtils. +// - Not doing this b/c focussing on using from RestAssured. +// ZonedDateTime expectedZdt = DateUtils.parseUserDate(expectedStr, "UTC"); +// instant = expectedZdt.toInstant(); + + try { + instant = jacksonParse(expectedStr); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + return new SameInstantString(instant); + } + + @Factory + public static Matcher sameInstant(long expected) { + return sameInstant(String.valueOf(expected)); + } + + @Factory + public static Matcher sameInstant(Instant expectedInstant) { + return new SameInstantString(expectedInstant); + } + + @Factory + public static Matcher sameInstant(ZonedDateTime expectedZdt) { + return new SameInstantString(expectedZdt.toInstant()); + } + + + private static class Holder { + public Holder() { + } + + public Instant instant; + } +} diff --git a/cwms-data-api/src/test/java/helpers/SameInstantTest.java b/cwms-data-api/src/test/java/helpers/SameInstantTest.java new file mode 100644 index 000000000..563f82e40 --- /dev/null +++ b/cwms-data-api/src/test/java/helpers/SameInstantTest.java @@ -0,0 +1,27 @@ +package helpers; + +import static helpers.SameInstantString.sameInstant; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.Instant; +import org.junit.jupiter.api.Test; + +class SameInstantTest { + + @Test + void testSameInstant() { + + Instant expected = Instant.parse("2021-06-21T08:00:00Z"); + + assertAll( + () -> assertThat("2021-06-21T08:00:00Z", sameInstant("2021-06-21T08:00:00Z")), + () -> assertThat("2021-06-21T08:00:00Z", sameInstant(expected)), + () -> assertThat("1624262400000", sameInstant("2021-06-21T08:00:00Z")), + () -> assertThat("1624262400000", sameInstant("1624262400000")), + () -> assertThat("1624262400000", sameInstant(1624262400000L)) + // This is meant to be used with RestAssured so we really just want the jackson supported formats. + ); + } + +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb.json b/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb.json index 9538d2b74..dda7840db 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb.json @@ -1,6 +1,6 @@ { "office-id": "SWT", - "name": "PROJ_TURB", + "name": "PROJ_TURB_PHYS", "latitude": 36.153980, "longitude": -95.992775, "active": true, diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb_changes.json b/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb_changes.json new file mode 100644 index 000000000..9538d2b74 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project_location_turb_changes.json @@ -0,0 +1,22 @@ +{ + "office-id": "SWT", + "name": "PROJ_TURB", + "latitude": 36.153980, + "longitude": -95.992775, + "active": true, + "public-name": "CDA-PROJECT", + "long-name": "UNITED STATES", + "description": "for testing", + "timezone-name": "UTC", + "location-type": "SITE", + "location-kind": "PROJECT", + "nation": "US", + "state-initial": "CA", + "county-name": "Sacramento", + "horizontal-datum": "NVGD29", + "published-longitude": -95.992775, + "published-latitude": 36.153980, + "elevation": 10.0, + "elevation-units": "m", + "bounding-office-id": "SWT" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location.json b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location.json index ae93b0023..8cadc1497 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location.json @@ -1,12 +1,12 @@ { "stream-location-node":{ "id": { - "office-id": "SPK", + "office-id": "SWT", "name": "StreamLoc123" }, "stream-node": { "stream-id": { - "office-id": "SPK", + "office-id": "SWT", "name": "ImOnThisStream" }, "bank": "R", diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location2.json b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location2.json index 47a666a6b..f5972bf8b 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location2.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location2.json @@ -1,12 +1,12 @@ { "stream-location-node":{ "id": { - "office-id": "SPK", + "office-id": "SWT", "name": "StreamLoc2" }, "stream-node": { "stream-id": { - "office-id": "SPK", + "office-id": "SWT", "name": "ImOnThisStream" }, "bank": "R", diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location3.json b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location3.json new file mode 100644 index 000000000..301ce1cd6 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location3.json @@ -0,0 +1,24 @@ +{ + "stream-location-node":{ + "id": { + "office-id": "SWT", + "name": "StreamLoc321" + }, + "stream-node": { + "stream-id": { + "office-id": "SWT", + "name": "ImOnThisStream2" + }, + "bank": "R", + "station": 690, + "station-units": "km" + } + }, + "published-station": 690, + "navigation-station": 12, + "lowest-measurable-stage": 1.5, + "total-drainage-area": 10.5, + "ungaged-drainage-area": 0.01, + "area-units": "km2", + "stage-units": "m" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location4.json b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location4.json new file mode 100644 index 000000000..91547d20f --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location4.json @@ -0,0 +1,24 @@ +{ + "stream-location-node":{ + "id": { + "office-id": "SWT", + "name": "StreamLoc1234" + }, + "stream-node": { + "stream-id": { + "office-id": "SWT", + "name": "ImOnThisStream3" + }, + "bank": "R", + "station": 690, + "station-units": "km" + } + }, + "published-station": 690, + "navigation-station": 12, + "lowest-measurable-stage": 1.5, + "total-drainage-area": 10.5, + "ungaged-drainage-area": 0.01, + "area-units": "km2", + "stage-units": "m" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/stream_location5.json b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location5.json new file mode 100644 index 000000000..e8a579eb6 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/stream_location5.json @@ -0,0 +1,24 @@ +{ + "stream-location-node":{ + "id": { + "office-id": "SWT", + "name": "StreamLoc23" + }, + "stream-node": { + "stream-id": { + "office-id": "SWT", + "name": "ImOnThisStream3" + }, + "bank": "R", + "station": 680, + "station-units": "km" + } + }, + "published-station": 680, + "navigation-station": 12, + "lowest-measurable-stage": 1.5, + "total-drainage-area": 10.5, + "ungaged-drainage-area": 0.01, + "area-units": "km2", + "stage-units": "m" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/turbine_phys.json b/cwms-data-api/src/test/resources/cwms/cda/api/turbine_phys.json new file mode 100644 index 000000000..07a5a7d10 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/turbine_phys.json @@ -0,0 +1,28 @@ +{ + "project-id": { + "office-id": "SWT", + "name": "PROJ_TURB_PHYS" + }, + "location": { + "office-id": "SWT", + "name": "PROJ_TURB_PHYS-TURBINE_PHYS", + "latitude": 36.153980, + "longitude": -95.992775, + "active": true, + "public-name": "TURBINE_LOC", + "long-name": "TURBINE_LOC", + "description": "for testing", + "timezone-name": "UTC", + "location-type": "SITE", + "location-kind": "SITE", + "nation": "US", + "state-initial": "CA", + "county-name": "Sacramento", + "horizontal-datum": "NVGD29", + "published-longitude": -95.992775, + "published-latitude": 36.153980, + "elevation": 10.0, + "bounding-office-id": "SWT", + "elevation-units": "m" + } +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/base_outlet.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/base_outlet.json index 47915b459..6677c936f 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/base_outlet.json +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/base_outlet.json @@ -1,19 +1,26 @@ { "project-id": { "office-id": "SPK", - "name": "location" + "name": "BIGH" }, "location": { "office-id": "SPK", - "name": "outlet", + "name": "TG2", "latitude": 0.0, "longitude": 0.0, "active": true, - "public-name": "outlet", + "public-name": "TG2", "timezone-name": "UTC", "location-kind": "Outlet", "horizontal-datum": "NAD84", "vertical-datum": "NAVD88" }, - "rating-group-id": "Rating-location-outlet" + "rating-category-id": { + "office-id": "SPK", + "name": "Rating" + }, + "rating-group-id": { + "office-id": "SPK", + "name": "Rating-BIGH-TG1" + } } \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/outlet.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/outlet.json index 906335dfe..fb591e701 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/outlet.json +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/outlet.json @@ -1,19 +1,26 @@ { "project-id": { "office-id": "SPK", - "name": "location" + "name": "BIGH" }, "location": { "office-id": "SPK", - "name": "location-outlet", + "name": "BIGH-TG1", "latitude": 0.0, "longitude": 0.0, "active": true, - "public-name": "location-outlet", + "public-name": "BIGH-TG1", "timezone-name": "UTC", "location-kind": "Outlet", "horizontal-datum": "NAD84", "vertical-datum": "NAVD88" }, - "rating-group-id": "Rating-location-outlet" + "rating-category-id": { + "office-id": "SPK", + "name": "Rating" + }, + "rating-group-id": { + "office-id": "SPK", + "name": "Rating-BIGH-TG1" + } } \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/compound_outlet_record.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/virtual-outlet-record.json similarity index 100% rename from cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/compound_outlet_record.json rename to cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/virtual-outlet-record.json diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/compound_outlet.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/virtual-outlet.json similarity index 74% rename from cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/compound_outlet.json rename to cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/virtual-outlet.json index ff7eab8fe..408214880 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/compound_outlet.json +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location/kind/virtual-outlet.json @@ -1,21 +1,13 @@ { "project-id": { "office-id": "SPK", - "name": "location" + "name": "BIGH" }, - "location": { + "virtual-outlet-id": { "office-id": "SPK", - "name": "CO1", - "latitude": 0.0, - "longitude": 0.0, - "active": true, - "public-name": "CO1", - "timezone-name": "UTC", - "location-kind": "Outlet", - "horizontal-datum": "NAD84", - "vertical-datum": "NAVD88" + "name": "Compound Tainter Gates" }, - "compound-outlet-records": [ + "virtual-records": [ { "outlet-id": { "office-id": "SPK", @@ -70,6 +62,5 @@ }, "downstream-outlet-ids": [] } - ], - "rating-group-id": "Rating-location-outlet" + ] } \ No newline at end of file diff --git a/gradle.properties.example b/gradle.properties.example index f289fb6b3..f441a8114 100644 --- a/gradle.properties.example +++ b/gradle.properties.example @@ -7,6 +7,12 @@ CDA_JDBC_URL=jdbc:oracle:thin:@localhost/CWMSDB CDA_JDBC_USERNAME=username CDA_JDBC_PASSWORD=password CDA_LISTEN_PORT=7000 +# While testing it is recommened to force the pool size to 1 +# to Expose connection management issues. +CDA_POOL_INIT_SIZE=1 +CDA_POOL_MAX_ACTIVE=1 +CDA_POOL_MAX_IDLE=1 +CDA_POOL_MIN_IDLE=0 # Allows an override of the default `spk-data` context used in the gradle run task cda.war.context=cwms-data diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ee28ff94..7abdb128b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ google-findbugs = "3.0.2" error_prone_annotations = "2.15.0" cwms-ratings = "2.0.2" javalin = "4.6.7" -tomcat = "9.0.90" +tomcat = "9.0.91" swagger-core = "2.2.2" swagger-ui = "5.9.0" jackson = "2.17.1"