From 209d80b1751c8e0888b74b63f72623d62706cbe4 Mon Sep 17 00:00:00 2001
From: rma-rripken <89810919+rma-rripken@users.noreply.github.com>
Date: Fri, 28 Jun 2024 09:29:31 -0700
Subject: [PATCH 01/16] Query Project children of a kind.
---
.../src/main/java/cwms/cda/ApiServlet.java | 2 +
.../main/java/cwms/cda/api/Controllers.java | 8 +-
.../api/project/ProjectChildrenHandler.java | 115 ++++++++
.../data/dao/project/ProjectChildrenDao.java | 262 ++++++++++++++++++
.../cda/data/dto/project/ProjectChildren.java | 146 ++++++++++
.../api/project/ProjectChildrenHandlerIT.java | 237 ++++++++++++++++
.../data/dto/project/ProjectChildrenTest.java | 253 +++++++++++++++++
.../cwms/cda/data/dto/project_children.json | 48 ++++
.../cda/data/dto/project_children_list.json | 18 ++
9 files changed, 1082 insertions(+), 7 deletions(-)
create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
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 64bcd74d7..383b5bf56 100644
--- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
+++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
@@ -88,6 +88,7 @@
import cwms.cda.api.errors.JsonFieldsException;
import cwms.cda.api.errors.NotFoundException;
import cwms.cda.api.errors.RequiredQueryParameterException;
+import cwms.cda.api.project.ProjectChildrenHandler;
import cwms.cda.data.dao.JooqDao;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.FormattingException;
@@ -474,6 +475,7 @@ protected void configureRoutes() {
get(forecastFilePath, new ForecastFileController(metrics));
addCacheControl(forecastFilePath, 1, TimeUnit.DAYS);
+ get("/projects/children/", new ProjectChildrenHandler(metrics));
cdaCrudCache(format("/projects/{%s}", Controllers.NAME),
new ProjectController(metrics), requiredRoles,5, TimeUnit.MINUTES);
cdaCrudCache(format("/properties/{%s}", Controllers.NAME),
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 c52bcfb2b..bb3a2cac2 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
@@ -74,9 +74,6 @@ public final class Controllers {
public static final String LOCATION_CATEGORY_LIKE = "location-category-like";
public static final String LOCATION_GROUP_LIKE = "location-group-like";
-
-
-
public static final String TIMESERIES_GROUP_LIKE = "timeseries-group-like";
public static final String ACCEPT = "Accept";
public static final String CLOB_ID = "clob-id";
@@ -142,7 +139,6 @@ public final class Controllers {
public static final String ANY_MASK = "*";
public static final String OFFICE_MASK = "office-mask";
public static final String ID_MASK = "id-mask";
- public static final String LOCATION_MASK = "location-mask";
public static final String NAME_MASK = "name-mask";
public static final String BOTTOM_MASK = "bottom-mask";
public static final String TOP_MASK = "top-mask";
@@ -161,9 +157,6 @@ public final class Controllers {
public static final String STATUS_501 = "501";
public static final String STATUS_400 = "400";
public static final String TEXT_MASK = "text-mask";
- public static final String DELETE_MODE = "delete-mode";
- public static final String MIN_ATTRIBUTE = "min-attribute";
- public static final String MAX_ATTRIBUTE = "max-attribute";
public static final String STANDARD_TEXT_ID_MASK = "standard-text-id-mask";
public static final String STANDARD_TEXT_ID = "standard-text-id";
public static final String TRIM = "trim";
@@ -174,6 +167,7 @@ public final class Controllers {
public static final String DEFAULT_VALUE = "default-value";
public static final String CATEGORY = "category";
public static final String PREFIX = "prefix";
+ public static final String PROJECT_LIKE = "project-like";
private static final String DEPRECATED_HEADER = "CWMS-DATA-Format-Deprecated";
private static final String DEPRECATED_TAB = "2024-11-01 TAB is not used often.";
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
new file mode 100644
index 000000000..25c5267ae
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
@@ -0,0 +1,115 @@
+/*
+ * 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.project;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static cwms.cda.api.Controllers.GET_ALL;
+import static cwms.cda.api.Controllers.LOCATION_KIND_LIKE;
+import static cwms.cda.api.Controllers.OFFICE;
+import static cwms.cda.api.Controllers.PROJECT_LIKE;
+import static cwms.cda.api.Controllers.STATUS_200;
+import static cwms.cda.api.Controllers.requiredParam;
+
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import cwms.cda.api.Controllers;
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dao.project.ProjectChildrenDao;
+import cwms.cda.data.dto.project.ProjectChildren;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import io.javalin.core.util.Header;
+import io.javalin.http.Context;
+import io.javalin.http.Handler;
+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;
+
+
+public class ProjectChildrenHandler implements Handler {
+ public static final String TAGS = "Projects";
+ public static final String PATH = "/projects/children";
+ private final MetricRegistry metrics;
+ private final Histogram requestResultSize;
+
+ private Timer.Context markAndTime(String subject) {
+ return Controllers.markAndTime(metrics, getClass().getName(), subject);
+ }
+
+ public ProjectChildrenHandler(MetricRegistry metrics) {
+ this.metrics = metrics;
+ requestResultSize = this.metrics.histogram((name(ProjectChildrenHandler.class, Controllers.RESULTS,
+ Controllers.SIZE)));
+ }
+
+
+ @OpenApi(
+ description = "Get a list of project child locations",
+ queryParams = {
+ @OpenApiParam(name = OFFICE, required = true),
+ @OpenApiParam(name = PROJECT_LIKE, description = "Posix regular expression matching "
+ + "against the project_id."),
+ @OpenApiParam(name = LOCATION_KIND_LIKE, description = "Posix regular expression matching against the "
+ + "location kind. The pattern will be matched against "
+ + "the valid location-kinds for Project Children:"
+ + "{\"EMBANKMENT\", \"TURBINE\", \"OUTLET\", \"LOCK\", \"GATE\"}. "
+ + "Multiple kinds can be matched by using Regular Expression "
+ + "OR clauses. For example: \"(TURBINE|OUTLET)\"")
+ },
+ responses = {
+ @OpenApiResponse(status = STATUS_200, content = {
+ @OpenApiContent(type = Formats.JSON, from = ProjectChildren.class, isArray = true)})
+ },
+ tags = {TAGS},
+ path = PATH,
+ method = HttpMethod.GET
+ )
+ @Override
+ public void handle(@NotNull Context ctx) throws Exception {
+ String office = requiredParam(ctx, OFFICE);
+ try (Timer.Context ignored = markAndTime(GET_ALL)) {
+ ProjectChildrenDao lockDao = new ProjectChildrenDao(JooqDao.getDslContext(ctx));
+ String projLike = ctx.queryParamAsClass(PROJECT_LIKE, String.class).getOrDefault(null);
+ String kindLike = ctx.queryParamAsClass(LOCATION_KIND_LIKE, String.class).getOrDefault(null);
+
+ List children = lockDao.children(office, projLike, kindLike);
+ String formatHeader = ctx.header(Header.ACCEPT);
+ ContentType contentType = Formats.parseHeader(formatHeader, ProjectChildren.class);
+ String result = Formats.format(contentType, children, ProjectChildren.class);
+ ctx.result(result).contentType(contentType.toString());
+ requestResultSize.update(result.length());
+ ctx.status(HttpServletResponse.SC_OK);
+ }
+ }
+
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
new file mode 100644
index 000000000..228517b6c
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
@@ -0,0 +1,262 @@
+/*
+ * 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.dao.project;
+
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.project.ProjectChildren;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jooq.DSLContext;
+import usace.cwms.db.jooq.codegen.tables.AV_EMBANKMENT;
+import usace.cwms.db.jooq.codegen.tables.AV_GATE;
+import usace.cwms.db.jooq.codegen.tables.AV_LOCK;
+import usace.cwms.db.jooq.codegen.tables.AV_OUTLET;
+import usace.cwms.db.jooq.codegen.tables.AV_TURBINE;
+
+public class ProjectChildrenDao extends JooqDao {
+
+ public ProjectChildrenDao(DSLContext dsl) {
+ super(dsl);
+ }
+
+
+ public List children(String office, String projLike, String kindRegex) {
+
+ List allKinds = getProjectKinds();
+
+ Set kinds = getMatchingKinds(kindRegex, allKinds);
+
+ return children(office, projLike, kinds);
+ }
+
+ private List children(String office, String projLike, Set kinds) {
+
+ Map builderMap = new LinkedHashMap<>(); // proj-id->
+
+ for (String kind : kinds) {
+ Map> locsOfKind = getChildrenOfKind(office, projLike, kind);
+ if (locsOfKind != null) {
+ for (Map.Entry> entry : locsOfKind.entrySet()) {
+ String projId = entry.getKey();
+ List locs = entry.getValue();
+ ProjectChildren.Builder builder = builderMap.computeIfAbsent(projId, k ->
+ new ProjectChildren.Builder()
+ .withProject(new CwmsId.Builder()
+ .withOfficeId(office)
+ .withName(projId)
+ .build()));
+ switch (kind) {
+ case "EMBANKMENT":
+ builder.withEmbankments(locs);
+ break;
+ case "LOCK":
+ builder.withLocks(locs);
+ break;
+ case "OUTLET":
+ builder.withOutlets(locs);
+ break;
+ case "TURBINE":
+ builder.withTurbines(locs);
+ break;
+ case "GATE":
+ builder.withGates(locs);
+ break;
+ default:
+ break;
+ }
+
+ }
+ }
+ }
+
+ return builderMap.values().stream()
+ .map(ProjectChildren.Builder::build)
+ .collect(Collectors.toList());
+
+ }
+
+ @Nullable
+ private Map> getChildrenOfKind(String office, String projLike, String kind) {
+ switch (kind) {
+
+ case "EMBANKMENT":
+ return getEmbankmentChildren(office, projLike);
+ case "TURBINE":
+ return getTurbineChildren(office, projLike);
+ case "OUTLET":
+ return getOutletChildren(office, projLike);
+ case "LOCK":
+ return getLockChildren(office, projLike);
+ case "GATE":
+ return getGateChildren(office, projLike);
+ default:
+ return null;
+ }
+
+ }
+
+ private Map> getGateChildren(String office, @Nullable String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_GATE view = AV_GATE.AV_GATE;
+ dsl.selectDistinct(view.OFFICE_ID, view.GATE_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.GATE_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.GATE_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getLockChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_LOCK view = AV_LOCK.AV_LOCK;
+ dsl.selectDistinct(view.DB_OFFICE_ID, view.LOCK_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.DB_OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.DB_OFFICE_ID, view.PROJECT_ID, view.LOCK_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.DB_OFFICE_ID))
+ .withName(row.get(view.LOCK_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getOutletChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_OUTLET view = AV_OUTLET.AV_OUTLET;
+ dsl.selectDistinct(view.OFFICE_ID, view.OUTLET_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.OUTLET_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getTurbineChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_TURBINE view = AV_TURBINE.AV_TURBINE;
+ dsl.selectDistinct(view.OFFICE_ID, view.TURBINE_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.TURBINE_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.TURBINE_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getEmbankmentChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_EMBANKMENT view = AV_EMBANKMENT.AV_EMBANKMENT;
+ dsl.selectDistinct(view.OFFICE_ID, view.EMBANKMENT_LOCATION_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office)
+ .and(view.UNIT_SYSTEM.eq("SI")))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.EMBANKMENT_LOCATION_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.EMBANKMENT_LOCATION_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private static @NotNull List getProjectKinds() {
+ // Maybe this should be an enum? Maybe just the Project types.
+ // "SITE", "EMBANKMENT", "OVERFLOW", "TURBINE", "STREAM", "PROJECT",
+ // "STREAMGAGE", "BASIN", "OUTLET", "LOCK", "GATE"
+ String [] kinds = { "EMBANKMENT", "TURBINE", "OUTLET", "LOCK", "GATE"};
+ return Arrays.asList(kinds);
+ }
+
+ private Set getMatchingKinds(String regex, List allKinds) {
+ Set kinds = new LinkedHashSet<>();
+
+ if (regex == null) {
+ kinds.addAll(allKinds);
+ } else {
+ Pattern p = Pattern.compile(regex);
+ for (String kind : allKinds) {
+ Matcher matcher = p.matcher(kind);
+ if (matcher.matches()) {
+ kinds.add(kind);
+ }
+ }
+ }
+
+ return kinds;
+ }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
new file mode 100644
index 000000000..b5591f721
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
@@ -0,0 +1,146 @@
+/*
+ * 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.project;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import cwms.cda.api.errors.FieldException;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+import java.util.Collections;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+@JsonDeserialize(builder = ProjectChildren.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+@FormattableWith(contentType = Formats.JSON, formatter = JsonV2.class)
+public class ProjectChildren implements CwmsDTOBase {
+
+ private final CwmsId project;
+
+ private final List embankments;
+ private final List locks;
+ private final List outlets;
+ private final List turbines;
+ private final List gates;
+
+ private ProjectChildren(Builder builder) {
+ this.project = builder.project;
+ this.embankments = builder.embankments;
+ this.locks = builder.locks;
+ this.outlets = builder.outlets;
+ this.turbines = builder.turbines;
+ this.gates = builder.gates;
+ }
+
+ @Override
+ public void validate() throws FieldException {
+
+ }
+
+ public CwmsId getProject() {
+ return project;
+ }
+
+ public List getEmbankments() {
+ return embankments;
+ }
+
+ public List getLocks() {
+ return locks;
+ }
+
+ public List getOutlets() {
+ return outlets;
+ }
+
+ public List getTurbines() {
+ return turbines;
+ }
+
+ public List getGates() {
+ return gates;
+ }
+
+
+ public static class Builder {
+ private CwmsId project;
+ private List embankments;
+ private List locks;
+ private List outlets;
+ private List turbines;
+ private List gates;
+
+ public Builder withProject(CwmsId project) {
+ this.project = project;
+ return this;
+ }
+
+ public Builder withEmbankments(List embankments) {
+ this.embankments = wrapList(embankments);
+ return this;
+ }
+
+
+ public Builder withLocks(List locks) {
+ this.locks = wrapList(locks);
+ return this;
+ }
+
+ public Builder withOutlets(List outlets) {
+ this.outlets = outlets;
+ return this;
+ }
+
+ public Builder withTurbines(List turbines) {
+ this.turbines = wrapList(turbines);
+ return this;
+ }
+
+ public Builder withGates(List gates) {
+ this.gates = wrapList(gates);
+ return this;
+ }
+
+ @Nullable
+ private static List wrapList(@Nullable List embankments) {
+ List retval = null;
+ if (embankments != null) {
+ retval = Collections.unmodifiableList(embankments);
+ }
+ return retval;
+ }
+
+ public ProjectChildren build() {
+ return new ProjectChildren(this);
+ }
+ }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
new file mode 100644
index 000000000..3f6883763
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
@@ -0,0 +1,237 @@
+/*
+ * 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.project;
+
+
+import static cwms.cda.api.Controllers.LOCATION_KIND_LIKE;
+import static cwms.cda.api.Controllers.PROJECT_LIKE;
+import static cwms.cda.data.dao.JooqDao.getDslContext;
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.core.Is.is;
+
+import cwms.cda.api.Controllers;
+import cwms.cda.api.DataApiTestIT;
+import cwms.cda.api.enums.Nation;
+import cwms.cda.data.dao.DeleteRule;
+import cwms.cda.data.dao.LocationsDaoImpl;
+import cwms.cda.data.dao.location.kind.EmbankmentDao;
+import cwms.cda.data.dao.project.ProjectDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.Location;
+import cwms.cda.data.dto.LookupType;
+import cwms.cda.data.dto.location.kind.Embankment;
+import cwms.cda.data.dto.project.Project;
+import cwms.cda.formatters.Formats;
+import fixtures.CwmsDataApiSetupCallback;
+import io.restassured.filter.log.LogDetail;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.time.ZoneId;
+import javax.servlet.http.HttpServletResponse;
+import mil.army.usace.hec.test.database.CwmsDatabaseContainer;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("integration")
+public class ProjectChildrenHandlerIT extends DataApiTestIT {
+
+ public static final String OFFICE = "SPK";
+
+ @Test
+ void test_get_embankment() throws Exception {
+ CwmsDatabaseContainer> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+ databaseLink.connection(c -> {
+ DSLContext dsl = getDslContext(c, OFFICE);
+
+ ProjectDao prjDao = new ProjectDao(dsl);
+
+ String projectName = "projChild";
+ CwmsId projectId = new CwmsId.Builder()
+ .withName(projectName)
+ .withOfficeId(OFFICE)
+ .build();
+
+ Project testProject = buildTestProject(OFFICE, projectName);
+ prjDao.create(testProject);
+
+ String locName1 = "TEST_LOCATION1";
+ String locName2 = "TEST_LOCATION2";
+ try {
+ createLocation(locName1, true, OFFICE);
+ createLocation(locName2, true, OFFICE);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ LocationsDaoImpl locationsDao = new LocationsDaoImpl(dsl);
+ Location location1 = buildTestLocation(OFFICE, locName1);
+ try {
+ locationsDao.storeLocation(location1);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Location location2 = buildTestLocation(OFFICE, locName2);
+ try {
+ locationsDao.storeLocation(location2);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+
+ Embankment embank = buildTestEmbankment(location1, projectId);
+ EmbankmentDao embankmentDao = new EmbankmentDao(dsl);
+ embankmentDao.storeEmbankment(embank, false);
+
+ Embankment embank2 = buildTestEmbankment(location2, projectId);
+ embankmentDao.storeEmbankment(embank2, false);
+ try {
+
+ given()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .accept(Formats.JSON)
+ .queryParam(Controllers.OFFICE, OFFICE)
+ .queryParam(PROJECT_LIKE, projectName)
+ .queryParam(LOCATION_KIND_LIKE, "(EMBANKMENT|TURBINE)")
+ .when()
+ .redirects().follow(true)
+ .redirects().max(3)
+ .get("/projects/children/")
+ .then()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .assertThat()
+ .statusCode(is(HttpServletResponse.SC_OK))
+ .body("size()", is(1))
+ .body("[0].project.office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].project.name", equalToIgnoringCase(projectName))
+ .body("[0].embankments.size()", is(2))
+ .body("[0].embankments[0].office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].embankments[0].name", equalToIgnoringCase(locName1))
+ .body("[0].embankments[1].office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].embankments[1].name", equalToIgnoringCase(locName2))
+ ;
+ } finally {
+ embankmentDao.deleteEmbankment(embank.getLocation().getName(), OFFICE, DeleteRule.DELETE_ALL );
+ embankmentDao.deleteEmbankment(embank2.getLocation().getName(), OFFICE, DeleteRule.DELETE_ALL);
+ prjDao.delete(projectId.getOfficeId(), projectId.getName(), DeleteRule.DELETE_ALL);
+ }
+ });
+
+ }
+
+ private Embankment buildTestEmbankment(Location location, CwmsId projId) {
+ return new Embankment.Builder()
+ .withLocation(location)
+ .withMaxHeight(5.0)
+ .withProjectId(projId)
+ .withStructureLength(10.0)
+ .withStructureType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Rolled Earth-Filled")
+ .withTooltip("An embankment formed by compacted earth")
+ .withActive(true)
+ .build())
+ .withDownstreamProtectionType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Concrete Arch Facing")
+ .withTooltip("Protected by the faces of the concrete arches")
+ .withActive(true)
+ .build())
+ .withUpstreamProtectionType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Concrete Blanket")
+ .withTooltip("Protected by blanket of concrete")
+ .withActive(true)
+ .build())
+ .withUpstreamSideSlope(15.0)
+ .withLengthUnits("ft")
+ .withTopWidth(20.0)
+ .withStructureLength(25.0)
+ .withDownstreamSideSlope(90.0)
+ .build();
+ }
+
+ private Location buildTestLocation(String office, String name) {
+ return new Location.Builder(name, "EMBANKMENT", ZoneId.of("UTC"),
+ 50.0, 50.0, "NVGD29", office)
+ .withElevation(10.0)
+ .withElevationUnits("ft")
+ .withLocationType("SITE")
+ .withActive(true)
+ .withNation(Nation.US)
+ .withStateInitial("CA")
+ .withCountyName("Yolo")
+ .withBoundingOfficeId(office)
+ .withPublishedLatitude(38.54)
+ .withPublishedLongitude(-121.74)
+ .withDescription("for testing")
+ .withNearestCity("Davis")
+ .build();
+ }
+
+ public static Project buildTestProject(String office, String prjId) {
+ Location pbLoc = new Location.Builder(office,prjId + "-PB")
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+ Location ngLoc = new Location.Builder(office,prjId + "-NG")
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+
+ Location prjLoc = new Location.Builder(office, prjId)
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+
+ return new Project.Builder()
+ .withLocation(prjLoc)
+ .withProjectOwner("Project Owner")
+ .withAuthorizingLaw("Authorizing Law")
+ .withFederalCost(BigDecimal.valueOf(100.0))
+ .withNonFederalCost(BigDecimal.valueOf(50.0))
+ .withFederalOAndMCost(BigDecimal.valueOf(10.0))
+ .withNonFederalOAndMCost(BigDecimal.valueOf(5.0))
+ .withCostYear(Instant.now())
+ .withCostUnit("$")
+ .withYieldTimeFrameEnd(Instant.now())
+ .withYieldTimeFrameStart(Instant.now())
+ .withFederalOAndMCost(BigDecimal.valueOf(10.0))
+ .withNonFederalOAndMCost(BigDecimal.valueOf(5.0))
+ .withProjectRemarks("Remarks")
+ .withPumpBackLocation(pbLoc)
+ .withNearGageLocation(ngLoc)
+ .withBankFullCapacityDesc("Bank Full Capacity Description")
+ .withDownstreamUrbanDesc("Downstream Urban Description")
+ .withHydropowerDesc("Hydropower Description")
+ .withSedimentationDesc("Sedimentation Description")
+ .build();
+
+ }
+
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
new file mode 100644
index 000000000..c3e8b808e
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.project;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.json.JsonV2;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+class ProjectChildrenTest {
+
+ public static final String OFFICE = "SPK";
+
+ @Test
+ void test_ctor(){
+ CwmsId proj = buildId(OFFICE, "TestProject");
+
+ List embanks = new ArrayList<>();
+ embanks.add(buildId(OFFICE, "TestEmbankment1"));
+ embanks.add(buildId(OFFICE, "TestEmbankment2"));
+
+ List locks = new ArrayList<>();
+ locks.add(buildId(OFFICE, "TestLock1"));
+ locks.add(buildId(OFFICE, "TestLock2"));
+ locks.add(buildId(OFFICE, "TestLock3"));
+
+ List gates = new ArrayList<>();
+ gates.add(buildId(OFFICE, "TestGate1"));
+
+ List turbines = new ArrayList<>();
+ turbines.add(buildId(OFFICE, "TestTurbine1"));
+
+ List outlets = new ArrayList<>();
+ outlets.add(buildId(OFFICE, "TestOutlet1"));
+
+ ProjectChildren projectChildren = new ProjectChildren.Builder()
+ .withProject(proj)
+ .withEmbankments(embanks)
+ .withLocks(locks)
+ .withGates(gates)
+ .withTurbines(turbines)
+ .withOutlets(outlets)
+ .build();
+ assertNotNull(projectChildren);
+
+ assertEquals(proj, projectChildren.getProject());
+ assertEquals(embanks, projectChildren.getEmbankments());
+ assertEquals(locks, projectChildren.getLocks());
+
+ String json = Formats.format(new ContentType(Formats.JSON), projectChildren);
+
+
+ assertNotNull(json);
+
+ }
+
+ private ProjectChildren buildTestProjectChildren(String office, String projectName) {
+ CwmsId proj = buildId(office, projectName);
+
+ List embanks = new ArrayList<>();
+ embanks.add(buildId(office, "TestEmbankment1"));
+ embanks.add(buildId(office, "TestEmbankment2"));
+
+ List locks = new ArrayList<>();
+ locks.add(buildId(office, "TestLock1"));
+ locks.add(buildId(office, "TestLock2"));
+ locks.add(buildId(office, "TestLock3"));
+
+ List gates = new ArrayList<>();
+ gates.add(buildId(office, "TestGate1"));
+
+ List turbines = new ArrayList<>();
+ turbines.add(buildId(office, "TestTurbine1"));
+
+ List outlets = new ArrayList<>();
+ outlets.add(buildId(office, "TestOutlet1"));
+
+ return new ProjectChildren.Builder()
+ .withProject(proj)
+ .withEmbankments(embanks)
+ .withLocks(locks)
+ .withGates(gates)
+ .withTurbines(turbines)
+ .withOutlets(outlets)
+ .build();
+ }
+
+ @Test
+ void test_list_serialization(){
+ List list = new ArrayList<>();
+
+ list.add(buildTestProjectChildren(OFFICE, "TestProject1"));
+ list.add(buildTestProjectChildren(OFFICE, "TestProject2"));
+
+ String json = Formats.format(new ContentType(Formats.JSON), list, ProjectChildren.class);
+ assertNotNull(json);
+ assertFalse(json.isEmpty());
+ assertTrue(json.contains("TestProject1"));
+ assertTrue(json.contains("TestProject2"));
+ }
+
+ @Test
+ void test_list_deserialization() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children_list.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ List list = om.readValue(input, new TypeReference>(){});
+
+ assertNotNull(list);
+ assertFalse(list.isEmpty());
+ }
+
+
+ @Test
+ void testDeserialize() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ ProjectChildren projectChildren = om.readValue(input, ProjectChildren.class);
+ assertNotNull(projectChildren);
+
+ CwmsId project = projectChildren.getProject();
+ assertNotNull(project);
+ assertEquals("SPK", project.getOfficeId());
+ assertEquals("TestProject", project.getName());
+
+ List embankments = projectChildren.getEmbankments();
+ assertNotNull(embankments);
+ assertEquals(2, embankments.size());
+ assertEquals("SPK", embankments.get(0).getOfficeId());
+ assertEquals("TestEmbankment1", embankments.get(0).getName());
+ assertEquals("SPK", embankments.get(1).getOfficeId());
+ assertEquals("TestEmbankment2", embankments.get(1).getName());
+
+ List locks = projectChildren.getLocks();
+ assertNotNull(locks);
+ assertEquals(3, locks.size());
+ assertEquals("SPK", locks.get(0).getOfficeId());
+ assertEquals("TestLock1", locks.get(0).getName());
+ assertEquals("SPK", locks.get(1).getOfficeId());
+ assertEquals("TestLock2", locks.get(1).getName());
+ assertEquals("SPK", locks.get(2).getOfficeId());
+ assertEquals("TestLock3", locks.get(2).getName());
+
+ List outlets = projectChildren.getOutlets();
+ assertNotNull(outlets);
+ assertEquals(1, outlets.size());
+ assertEquals("SPK", outlets.get(0).getOfficeId());
+ assertEquals("TestOutlet1", outlets.get(0).getName());
+
+ List turbines = projectChildren.getTurbines();
+ assertNotNull(turbines);
+ assertEquals(1, turbines.size());
+ assertEquals("SPK", turbines.get(0).getOfficeId());
+ assertEquals("TestTurbine1", turbines.get(0).getName());
+
+ List gates = projectChildren.getGates();
+ assertNotNull(gates);
+ assertEquals(1, gates.size());
+ assertEquals("SPK", gates.get(0).getOfficeId());
+ assertEquals("TestGate1", gates.get(0).getName());
+ }
+
+ @Test
+ void testRoundtrip() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ ProjectChildren projectChildren1 = om.readValue(input, ProjectChildren.class);
+ assertNotNull(projectChildren1);
+
+ String json = om.writeValueAsString(projectChildren1);
+ ProjectChildren projectChildren2 = om.readValue(json, ProjectChildren.class);
+ assertNotNull(projectChildren2);
+
+ assertProjectChildrenEqual(projectChildren1, projectChildren2);
+
+ }
+
+ private static void assertProjectChildrenEqual(ProjectChildren projectChildren1, ProjectChildren projectChildren2) {
+ assertAll("ProjectChildren",
+ () -> assertCwmsIdEqual(projectChildren1.getProject(), projectChildren2.getProject()),
+ () -> assertListEqual(projectChildren1.getEmbankments(), projectChildren2.getEmbankments()),
+ () -> assertListEqual(projectChildren1.getLocks(), projectChildren2.getLocks()),
+ () -> assertListEqual(projectChildren1.getGates(), projectChildren2.getGates()),
+ () -> assertListEqual(projectChildren1.getTurbines(), projectChildren2.getTurbines()),
+ () -> assertListEqual(projectChildren1.getOutlets(), projectChildren2.getOutlets())
+ );
+ }
+
+ private static void assertCwmsIdEqual(CwmsId project1, CwmsId project2) {
+ assertAll("CwmsId",
+ () -> assertEquals(project1.getOfficeId(), project2.getOfficeId()),
+ () -> assertEquals(project1.getName(), project2.getName())
+ );
+ }
+
+ private static void assertListEqual(List locks1, List locks2) {
+ assertEquals(locks1.size(), locks2.size());
+ for(int i = 0; i < locks1.size(); i++){
+ assertCwmsIdEqual(locks1.get(i), locks2.get(i));
+ }
+ }
+
+ private static CwmsId buildId(String office, String name) {
+ return new CwmsId.Builder()
+ .withOfficeId(office)
+ .withName(name)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
new file mode 100644
index 000000000..3d677d47e
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
@@ -0,0 +1,48 @@
+{
+ "project": {
+ "office-id": "SPK",
+ "name": "TestProject"
+ },
+ "embankments": [
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment2"
+ }
+ ],
+ "locks": [
+ {
+ "office-id": "SPK",
+ "name": "TestLock1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestLock2"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestLock3"
+ }
+ ],
+ "outlets": [
+ {
+ "office-id": "SPK",
+ "name": "TestOutlet1"
+ }
+ ],
+ "turbines": [
+ {
+ "office-id": "SPK",
+ "name": "TestTurbine1"
+ }
+ ],
+ "gates": [
+ {
+ "office-id": "SPK",
+ "name": "TestGate1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
new file mode 100644
index 000000000..d20a26db9
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
@@ -0,0 +1,18 @@
+[
+ {
+ "project": {
+ "office-id": "SPK",
+ "name": "TestProject1"
+ },
+ "embankments": [
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment2"
+ }
+ ]
+ }
+]
\ No newline at end of file
From 5caaff51c83391a1dba7823ed7b40a9e4565cbf0 Mon Sep 17 00:00:00 2001
From: rma-rripken
Date: Wed, 3 Jul 2024 16:23:31 -0700
Subject: [PATCH 02/16] Added enum type and fixed copy/paste variable name.
---
.../data/dao/project/ProjectChildrenDao.java | 86 +++++------------
.../cda/data/dao/project/ProjectKind.java | 33 +++++++
.../cda/data/dao/project/ProjectKindTest.java | 96 +++++++++++++++++++
3 files changed, 153 insertions(+), 62 deletions(-)
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectKind.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dao/project/ProjectKindTest.java
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
index 228517b6c..557879a80 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
@@ -27,17 +27,9 @@
import cwms.cda.data.dao.JooqDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.project.ProjectChildren;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import java.util.*;
import java.util.stream.Collectors;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jooq.DSLContext;
import usace.cwms.db.jooq.codegen.tables.AV_EMBANKMENT;
@@ -54,19 +46,14 @@ public ProjectChildrenDao(DSLContext dsl) {
public List children(String office, String projLike, String kindRegex) {
-
- List allKinds = getProjectKinds();
-
- Set kinds = getMatchingKinds(kindRegex, allKinds);
-
- return children(office, projLike, kinds);
+ return children(office, projLike, ProjectKind.getMatchingKinds(kindRegex));
}
- private List children(String office, String projLike, Set kinds) {
+ private List children(String office, String projLike, Set kinds) {
Map builderMap = new LinkedHashMap<>(); // proj-id->
- for (String kind : kinds) {
+ for (ProjectKind kind : kinds) {
Map> locsOfKind = getChildrenOfKind(office, projLike, kind);
if (locsOfKind != null) {
for (Map.Entry> entry : locsOfKind.entrySet()) {
@@ -79,19 +66,19 @@ private List children(String office, String projLike, Set children(String office, String projLike, Set> getChildrenOfKind(String office, String projLike, String kind) {
+ private Map> getChildrenOfKind(String office, String projLike, ProjectKind kind) {
switch (kind) {
- case "EMBANKMENT":
+ case EMBANKMENT:
return getEmbankmentChildren(office, projLike);
- case "TURBINE":
+ case TURBINE:
return getTurbineChildren(office, projLike);
- case "OUTLET":
+ case OUTLET:
return getOutletChildren(office, projLike);
- case "LOCK":
+ case LOCK:
return getLockChildren(office, projLike);
- case "GATE":
+ case GATE:
return getGateChildren(office, projLike);
default:
return null;
@@ -138,11 +125,11 @@ private Map> getGateChildren(String office, @Nullable Strin
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.GATE_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
- CwmsId embankment = new CwmsId.Builder()
+ CwmsId gate = new CwmsId.Builder()
.withOfficeId(row.get(view.OFFICE_ID))
.withName(row.get(view.GATE_ID))
.build();
- retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(gate);
});
@@ -159,11 +146,11 @@ private Map> getLockChildren(String office, String projLike
.orderBy(view.DB_OFFICE_ID, view.PROJECT_ID, view.LOCK_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
- CwmsId embankment = new CwmsId.Builder()
+ CwmsId lock = new CwmsId.Builder()
.withOfficeId(row.get(view.DB_OFFICE_ID))
.withName(row.get(view.LOCK_ID))
.build();
- retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(lock);
});
@@ -180,11 +167,11 @@ private Map> getOutletChildren(String office, String projLi
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
- CwmsId embankment = new CwmsId.Builder()
+ CwmsId outlet = new CwmsId.Builder()
.withOfficeId(row.get(view.OFFICE_ID))
.withName(row.get(view.OUTLET_ID))
.build();
- retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(outlet);
});
@@ -201,14 +188,13 @@ private Map> getTurbineChildren(String office, String projL
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.TURBINE_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
- CwmsId embankment = new CwmsId.Builder()
+ CwmsId turbine = new CwmsId.Builder()
.withOfficeId(row.get(view.OFFICE_ID))
.withName(row.get(view.TURBINE_ID))
.build();
- retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(turbine);
});
-
return retval;
}
@@ -234,29 +220,5 @@ private Map> getEmbankmentChildren(String office, String pr
return retval;
}
- private static @NotNull List getProjectKinds() {
- // Maybe this should be an enum? Maybe just the Project types.
- // "SITE", "EMBANKMENT", "OVERFLOW", "TURBINE", "STREAM", "PROJECT",
- // "STREAMGAGE", "BASIN", "OUTLET", "LOCK", "GATE"
- String [] kinds = { "EMBANKMENT", "TURBINE", "OUTLET", "LOCK", "GATE"};
- return Arrays.asList(kinds);
- }
- private Set getMatchingKinds(String regex, List allKinds) {
- Set kinds = new LinkedHashSet<>();
-
- if (regex == null) {
- kinds.addAll(allKinds);
- } else {
- Pattern p = Pattern.compile(regex);
- for (String kind : allKinds) {
- Matcher matcher = p.matcher(kind);
- if (matcher.matches()) {
- kinds.add(kind);
- }
- }
- }
-
- return kinds;
- }
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectKind.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectKind.java
new file mode 100644
index 000000000..6ed1763fc
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectKind.java
@@ -0,0 +1,33 @@
+package cwms.cda.data.dao.project;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * The Location Kinds that have a project as a parent.
+ */
+public enum ProjectKind {
+ EMBANKMENT, TURBINE, OUTLET, LOCK, GATE;
+
+ public static Set getMatchingKinds(String regex) {
+ Set kinds = new LinkedHashSet<>();
+
+ ProjectKind[] projectKinds = ProjectKind.values();
+ if (regex == null) {
+ kinds.addAll(Arrays.asList(projectKinds));
+ } else {
+ Pattern p = Pattern.compile(regex);
+ for (ProjectKind kind : projectKinds) {
+ Matcher matcher = p.matcher(kind.name());
+ if (matcher.matches()) {
+ kinds.add(kind);
+ }
+ }
+ }
+
+ return kinds;
+ }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dao/project/ProjectKindTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dao/project/ProjectKindTest.java
new file mode 100644
index 000000000..6431aec24
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dao/project/ProjectKindTest.java
@@ -0,0 +1,96 @@
+package cwms.cda.data.dao.project;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ProjectKindTest {
+
+ @Test
+ void test_embankment(){
+ Set matches = ProjectKind.getMatchingKinds("EMBANKMENT");
+ assertNotNull(matches);
+ assertEquals(1, matches.size());
+ assertTrue(matches.contains(ProjectKind.EMBANKMENT));
+ }
+ @Test
+ void test_turbine(){
+ Set matches = ProjectKind.getMatchingKinds("TURBINE");
+ assertNotNull(matches);
+ assertEquals(1, matches.size());
+ assertTrue(matches.contains(ProjectKind.TURBINE));
+ }
+
+ @Test
+ void test_outlet(){
+ Set matches = ProjectKind.getMatchingKinds("OUTLET");
+ assertNotNull(matches);
+ assertEquals(1, matches.size());
+ assertTrue(matches.contains(ProjectKind.OUTLET));
+ }
+
+ @Test
+ void test_lock(){
+ Set matches = ProjectKind.getMatchingKinds("LOCK");
+ assertNotNull(matches);
+ assertEquals(1, matches.size());
+ assertTrue(matches.contains(ProjectKind.LOCK));
+ }
+
+ @Test
+ void test_gate(){
+ Set matches = ProjectKind.getMatchingKinds("GATE");
+ assertNotNull(matches);
+ assertEquals(1, matches.size());
+ assertTrue(matches.contains(ProjectKind.GATE));
+ }
+
+ @Test
+ void test_null(){
+ Set matches = ProjectKind.getMatchingKinds(null);
+ assertNotNull(matches);
+ assertEquals(5, matches.size());
+ }
+
+ @Test
+ void test_empty(){
+ Set matches = ProjectKind.getMatchingKinds("");
+ assertNotNull(matches);
+ assertEquals(0, matches.size());
+ }
+
+ @Test
+ void test_star(){
+ Set matches = ProjectKind.getMatchingKinds(".*");
+ assertNotNull(matches);
+ assertEquals(5, matches.size());
+ }
+
+ @Test
+ void test_pipe(){
+ Set matches = ProjectKind.getMatchingKinds("^(EMBANKMENT|OUTLET)$");
+ assertNotNull(matches);
+ assertEquals(2, matches.size());
+ assertTrue(matches.contains(ProjectKind.EMBANKMENT));
+ assertTrue(matches.contains(ProjectKind.OUTLET));
+
+ matches = ProjectKind.getMatchingKinds("EMBANKMENT|OUTLET");
+ assertNotNull(matches);
+ assertEquals(2, matches.size());
+ assertTrue(matches.contains(ProjectKind.EMBANKMENT));
+ assertTrue(matches.contains(ProjectKind.OUTLET));
+ }
+
+ @Test
+ void test_star_t(){
+ Set matches = ProjectKind.getMatchingKinds(".*T$");
+ assertNotNull(matches);
+ assertEquals(2, matches.size());
+ assertTrue(matches.contains(ProjectKind.EMBANKMENT));
+ assertTrue(matches.contains(ProjectKind.OUTLET));
+ }
+
+
+}
\ No newline at end of file
From c1f095ac06a968a380ff0bdefa248d9a9a7487bb Mon Sep 17 00:00:00 2001
From: rma-rripken
Date: Wed, 3 Jul 2024 17:27:41 -0700
Subject: [PATCH 03/16] Switch from AV_GATE to AV_OUTLET.
---
.../data/dao/project/ProjectChildrenDao.java | 18 ++++++++----------
.../cda/data/dto/project/ProjectChildren.java | 3 +++
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
index 557879a80..74331d451 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
@@ -33,7 +33,6 @@
import org.jetbrains.annotations.Nullable;
import org.jooq.DSLContext;
import usace.cwms.db.jooq.codegen.tables.AV_EMBANKMENT;
-import usace.cwms.db.jooq.codegen.tables.AV_GATE;
import usace.cwms.db.jooq.codegen.tables.AV_LOCK;
import usace.cwms.db.jooq.codegen.tables.AV_OUTLET;
import usace.cwms.db.jooq.codegen.tables.AV_TURBINE;
@@ -117,22 +116,24 @@ private Map> getChildrenOfKind(String office, String projLi
private Map> getGateChildren(String office, @Nullable String projLike) {
Map> retval = new LinkedHashMap<>();
- AV_GATE view = AV_GATE.AV_GATE;
- dsl.selectDistinct(view.OFFICE_ID, view.GATE_ID, view.PROJECT_ID)
+ // AV_GATE is apparently not used.
+ AV_OUTLET view = AV_OUTLET.AV_OUTLET;
+ dsl.selectDistinct(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
.from(view)
- .where(view.OFFICE_ID.eq(office))
+ .where(view.OFFICE_ID.eq(office)
+ .and(view.OPENING_UNIT_EN.isNotNull().or(view.OPENING_UNIT_SI.isNotNull()))
+ )
.and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
- .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.GATE_ID)
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
CwmsId gate = new CwmsId.Builder()
.withOfficeId(row.get(view.OFFICE_ID))
- .withName(row.get(view.GATE_ID))
+ .withName(row.get(view.OUTLET_ID))
.build();
retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(gate);
});
-
return retval;
}
@@ -153,7 +154,6 @@ private Map> getLockChildren(String office, String projLike
retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(lock);
});
-
return retval;
}
@@ -174,7 +174,6 @@ private Map> getOutletChildren(String office, String projLi
retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(outlet);
});
-
return retval;
}
@@ -216,7 +215,6 @@ private Map> getEmbankmentChildren(String office, String pr
retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
});
-
return retval;
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
index b5591f721..2434ab8c2 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
@@ -38,6 +38,9 @@
import java.util.List;
import org.jetbrains.annotations.Nullable;
+/**
+ * This class holds a project and lists of the project children by kind.
+ */
@JsonDeserialize(builder = ProjectChildren.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
From 5c5fd4c4384be0b3709ff6b803e5877ca70f0046 Mon Sep 17 00:00:00 2001
From: rma-rripken
Date: Wed, 3 Jul 2024 17:41:05 -0700
Subject: [PATCH 04/16] Renamed children to childLocations
---
.../src/main/java/cwms/cda/ApiServlet.java | 4 +-
....java => ProjectChildLocationHandler.java} | 24 +++---
...nDao.java => ProjectChildLocationDao.java} | 58 +++++++--------
...ildren.java => ProjectChildLocations.java} | 14 ++--
...ava => ProjectChildLocationHandlerIT.java} | 4 +-
...st.java => ProjectChildLocationsTest.java} | 73 +++++++++----------
6 files changed, 86 insertions(+), 91 deletions(-)
rename cwms-data-api/src/main/java/cwms/cda/api/project/{ProjectChildrenHandler.java => ProjectChildLocationHandler.java} (85%)
rename cwms-data-api/src/main/java/cwms/cda/data/dao/project/{ProjectChildrenDao.java => ProjectChildLocationDao.java} (79%)
rename cwms-data-api/src/main/java/cwms/cda/data/dto/project/{ProjectChildren.java => ProjectChildLocations.java} (90%)
rename cwms-data-api/src/test/java/cwms/cda/api/project/{ProjectChildrenHandlerIT.java => ProjectChildLocationHandlerIT.java} (98%)
rename cwms-data-api/src/test/java/cwms/cda/data/dto/project/{ProjectChildrenTest.java => ProjectChildLocationsTest.java} (73%)
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 383b5bf56..04ec94b6c 100644
--- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
+++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
@@ -88,7 +88,7 @@
import cwms.cda.api.errors.JsonFieldsException;
import cwms.cda.api.errors.NotFoundException;
import cwms.cda.api.errors.RequiredQueryParameterException;
-import cwms.cda.api.project.ProjectChildrenHandler;
+import cwms.cda.api.project.ProjectChildLocationHandler;
import cwms.cda.data.dao.JooqDao;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.FormattingException;
@@ -475,7 +475,7 @@ protected void configureRoutes() {
get(forecastFilePath, new ForecastFileController(metrics));
addCacheControl(forecastFilePath, 1, TimeUnit.DAYS);
- get("/projects/children/", new ProjectChildrenHandler(metrics));
+ get("/projects/child-locations/", new ProjectChildLocationHandler(metrics));
cdaCrudCache(format("/projects/{%s}", Controllers.NAME),
new ProjectController(metrics), requiredRoles,5, TimeUnit.MINUTES);
cdaCrudCache(format("/properties/{%s}", Controllers.NAME),
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildLocationHandler.java
similarity index 85%
rename from cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
rename to cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildLocationHandler.java
index 25c5267ae..e8349bf16 100644
--- a/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
+++ b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildLocationHandler.java
@@ -37,8 +37,8 @@
import com.codahale.metrics.Timer;
import cwms.cda.api.Controllers;
import cwms.cda.data.dao.JooqDao;
-import cwms.cda.data.dao.project.ProjectChildrenDao;
-import cwms.cda.data.dto.project.ProjectChildren;
+import cwms.cda.data.dao.project.ProjectChildLocationDao;
+import cwms.cda.data.dto.project.ProjectChildLocations;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
import io.javalin.core.util.Header;
@@ -54,9 +54,9 @@
import org.jetbrains.annotations.NotNull;
-public class ProjectChildrenHandler implements Handler {
+public class ProjectChildLocationHandler implements Handler {
public static final String TAGS = "Projects";
- public static final String PATH = "/projects/children";
+ public static final String PATH = "/projects/child-locations/";
private final MetricRegistry metrics;
private final Histogram requestResultSize;
@@ -64,9 +64,9 @@ private Timer.Context markAndTime(String subject) {
return Controllers.markAndTime(metrics, getClass().getName(), subject);
}
- public ProjectChildrenHandler(MetricRegistry metrics) {
+ public ProjectChildLocationHandler(MetricRegistry metrics) {
this.metrics = metrics;
- requestResultSize = this.metrics.histogram((name(ProjectChildrenHandler.class, Controllers.RESULTS,
+ requestResultSize = this.metrics.histogram((name(ProjectChildLocationHandler.class, Controllers.RESULTS,
Controllers.SIZE)));
}
@@ -80,14 +80,14 @@ public ProjectChildrenHandler(MetricRegistry metrics) {
@OpenApiParam(name = LOCATION_KIND_LIKE, description = "Posix regular expression matching against the "
+ "location kind. The pattern will be matched against "
- + "the valid location-kinds for Project Children:"
+ + "the valid location-kinds for Project child locations:"
+ "{\"EMBANKMENT\", \"TURBINE\", \"OUTLET\", \"LOCK\", \"GATE\"}. "
+ "Multiple kinds can be matched by using Regular Expression "
+ "OR clauses. For example: \"(TURBINE|OUTLET)\"")
},
responses = {
@OpenApiResponse(status = STATUS_200, content = {
- @OpenApiContent(type = Formats.JSON, from = ProjectChildren.class, isArray = true)})
+ @OpenApiContent(type = Formats.JSON, from = ProjectChildLocations.class, isArray = true)})
},
tags = {TAGS},
path = PATH,
@@ -97,14 +97,14 @@ public ProjectChildrenHandler(MetricRegistry metrics) {
public void handle(@NotNull Context ctx) throws Exception {
String office = requiredParam(ctx, OFFICE);
try (Timer.Context ignored = markAndTime(GET_ALL)) {
- ProjectChildrenDao lockDao = new ProjectChildrenDao(JooqDao.getDslContext(ctx));
+ ProjectChildLocationDao lockDao = new ProjectChildLocationDao(JooqDao.getDslContext(ctx));
String projLike = ctx.queryParamAsClass(PROJECT_LIKE, String.class).getOrDefault(null);
String kindLike = ctx.queryParamAsClass(LOCATION_KIND_LIKE, String.class).getOrDefault(null);
- List children = lockDao.children(office, projLike, kindLike);
+ List childLocations = lockDao.retrieveProjectChildLocations(office, projLike, kindLike);
String formatHeader = ctx.header(Header.ACCEPT);
- ContentType contentType = Formats.parseHeader(formatHeader, ProjectChildren.class);
- String result = Formats.format(contentType, children, ProjectChildren.class);
+ ContentType contentType = Formats.parseHeader(formatHeader, ProjectChildLocations.class);
+ String result = Formats.format(contentType, childLocations, ProjectChildLocations.class);
ctx.result(result).contentType(contentType.toString());
requestResultSize.update(result.length());
ctx.status(HttpServletResponse.SC_OK);
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java
similarity index 79%
rename from cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
rename to cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java
index 74331d451..8d97702cf 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java
@@ -26,7 +26,7 @@
import cwms.cda.data.dao.JooqDao;
import cwms.cda.data.dto.CwmsId;
-import cwms.cda.data.dto.project.ProjectChildren;
+import cwms.cda.data.dto.project.ProjectChildLocations;
import java.util.*;
import java.util.stream.Collectors;
@@ -37,29 +37,29 @@
import usace.cwms.db.jooq.codegen.tables.AV_OUTLET;
import usace.cwms.db.jooq.codegen.tables.AV_TURBINE;
-public class ProjectChildrenDao extends JooqDao {
+public class ProjectChildLocationDao extends JooqDao {
- public ProjectChildrenDao(DSLContext dsl) {
+ public ProjectChildLocationDao(DSLContext dsl) {
super(dsl);
}
- public List children(String office, String projLike, String kindRegex) {
- return children(office, projLike, ProjectKind.getMatchingKinds(kindRegex));
+ public List retrieveProjectChildLocations(String office, String projLike, String kindRegex) {
+ return retrieveProjectChildLocations(office, projLike, ProjectKind.getMatchingKinds(kindRegex));
}
- private List children(String office, String projLike, Set kinds) {
+ private List retrieveProjectChildLocations(String office, String projLike, Set kinds) {
- Map builderMap = new LinkedHashMap<>(); // proj-id->
+ Map builderMap = new LinkedHashMap<>(); // proj-id->
for (ProjectKind kind : kinds) {
- Map> locsOfKind = getChildrenOfKind(office, projLike, kind);
+ Map> locsOfKind = getChildLocationsOfKind(office, projLike, kind);
if (locsOfKind != null) {
for (Map.Entry> entry : locsOfKind.entrySet()) {
String projId = entry.getKey();
List locs = entry.getValue();
- ProjectChildren.Builder builder = builderMap.computeIfAbsent(projId, k ->
- new ProjectChildren.Builder()
+ ProjectChildLocations.Builder builder = builderMap.computeIfAbsent(projId, k ->
+ new ProjectChildLocations.Builder()
.withProject(new CwmsId.Builder()
.withOfficeId(office)
.withName(projId)
@@ -83,38 +83,35 @@ private List children(String office, String projLike, Set> getChildrenOfKind(String office, String projLike, ProjectKind kind) {
+ private Map> getChildLocationsOfKind(String office, String projRegex, ProjectKind kind) {
switch (kind) {
-
case EMBANKMENT:
- return getEmbankmentChildren(office, projLike);
+ return getEmbankmentChildLocations(office, projRegex);
case TURBINE:
- return getTurbineChildren(office, projLike);
+ return getTurbineChildLocations(office, projRegex);
case OUTLET:
- return getOutletChildren(office, projLike);
+ return getOutletChildLocations(office, projRegex);
case LOCK:
- return getLockChildren(office, projLike);
+ return getLockChildLocations(office, projRegex);
case GATE:
- return getGateChildren(office, projLike);
+ return getGateChildLocations(office, projRegex);
default:
return null;
}
}
- private Map> getGateChildren(String office, @Nullable String projLike) {
+ private Map> getGateChildLocations(String office, @Nullable String projRegex) {
Map> retval = new LinkedHashMap<>();
// AV_GATE is apparently not used.
AV_OUTLET view = AV_OUTLET.AV_OUTLET;
@@ -123,7 +120,7 @@ private Map> getGateChildren(String office, @Nullable Strin
.where(view.OFFICE_ID.eq(office)
.and(view.OPENING_UNIT_EN.isNotNull().or(view.OPENING_UNIT_SI.isNotNull()))
)
- .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projRegex))
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
@@ -137,13 +134,13 @@ private Map> getGateChildren(String office, @Nullable Strin
return retval;
}
- private Map> getLockChildren(String office, String projLike) {
+ private Map> getLockChildLocations(String office, @Nullable String projRegex) {
Map> retval = new LinkedHashMap<>();
AV_LOCK view = AV_LOCK.AV_LOCK;
dsl.selectDistinct(view.DB_OFFICE_ID, view.LOCK_ID, view.PROJECT_ID)
.from(view)
.where(view.DB_OFFICE_ID.eq(office))
- .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projRegex))
.orderBy(view.DB_OFFICE_ID, view.PROJECT_ID, view.LOCK_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
@@ -157,13 +154,13 @@ private Map> getLockChildren(String office, String projLike
return retval;
}
- private Map> getOutletChildren(String office, String projLike) {
+ private Map> getOutletChildLocations(String office, @Nullable String projRegex) {
Map> retval = new LinkedHashMap<>();
AV_OUTLET view = AV_OUTLET.AV_OUTLET;
dsl.selectDistinct(view.OFFICE_ID, view.OUTLET_ID, view.PROJECT_ID)
.from(view)
.where(view.OFFICE_ID.eq(office))
- .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projRegex))
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
@@ -177,13 +174,13 @@ private Map> getOutletChildren(String office, String projLi
return retval;
}
- private Map> getTurbineChildren(String office, String projLike) {
+ private Map> getTurbineChildLocations(String office, @Nullable String projRegex) {
Map> retval = new LinkedHashMap<>();
AV_TURBINE view = AV_TURBINE.AV_TURBINE;
dsl.selectDistinct(view.OFFICE_ID, view.TURBINE_ID, view.PROJECT_ID)
.from(view)
.where(view.OFFICE_ID.eq(office))
- .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projRegex))
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.TURBINE_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
@@ -197,14 +194,14 @@ private Map> getTurbineChildren(String office, String projL
return retval;
}
- private Map> getEmbankmentChildren(String office, String projLike) {
+ private Map> getEmbankmentChildLocations(String office, @Nullable String projRegex) {
Map> retval = new LinkedHashMap<>();
AV_EMBANKMENT view = AV_EMBANKMENT.AV_EMBANKMENT;
dsl.selectDistinct(view.OFFICE_ID, view.EMBANKMENT_LOCATION_ID, view.PROJECT_ID)
.from(view)
.where(view.OFFICE_ID.eq(office)
.and(view.UNIT_SYSTEM.eq("SI")))
- .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projRegex))
.orderBy(view.OFFICE_ID, view.PROJECT_ID, view.EMBANKMENT_LOCATION_ID)
.forEach(row -> {
String projId = row.get(view.PROJECT_ID);
@@ -218,5 +215,4 @@ private Map> getEmbankmentChildren(String office, String pr
return retval;
}
-
}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java
similarity index 90%
rename from cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
rename to cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java
index 2434ab8c2..9c34e5a89 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java
@@ -39,13 +39,13 @@
import org.jetbrains.annotations.Nullable;
/**
- * This class holds a project and lists of the project children by kind.
+ * This class holds a project and lists of the project child locations by kind.
*/
-@JsonDeserialize(builder = ProjectChildren.Builder.class)
+@JsonDeserialize(builder = ProjectChildLocations.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
-@FormattableWith(contentType = Formats.JSON, formatter = JsonV2.class)
-public class ProjectChildren implements CwmsDTOBase {
+@FormattableWith(contentType = Formats.JSONV2, aliases = {Formats.JSON}, formatter = JsonV2.class)
+public class ProjectChildLocations implements CwmsDTOBase {
private final CwmsId project;
@@ -55,7 +55,7 @@ public class ProjectChildren implements CwmsDTOBase {
private final List turbines;
private final List gates;
- private ProjectChildren(Builder builder) {
+ private ProjectChildLocations(Builder builder) {
this.project = builder.project;
this.embankments = builder.embankments;
this.locks = builder.locks;
@@ -142,8 +142,8 @@ private static List wrapList(@Nullable List embankments) {
return retval;
}
- public ProjectChildren build() {
- return new ProjectChildren(this);
+ public ProjectChildLocations build() {
+ return new ProjectChildLocations(this);
}
}
}
diff --git a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java
similarity index 98%
rename from cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
rename to cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java
index 3f6883763..d618d56bf 100644
--- a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
+++ b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java
@@ -59,7 +59,7 @@
import org.junit.jupiter.api.Test;
@Tag("integration")
-public class ProjectChildrenHandlerIT extends DataApiTestIT {
+public class ProjectChildLocationHandlerIT extends DataApiTestIT {
public static final String OFFICE = "SPK";
@@ -121,7 +121,7 @@ void test_get_embankment() throws Exception {
.when()
.redirects().follow(true)
.redirects().max(3)
- .get("/projects/children/")
+ .get("/projects/child-locations/")
.then()
.log().ifValidationFails(LogDetail.ALL, true)
.assertThat()
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java
similarity index 73%
rename from cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
rename to cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java
index c3e8b808e..3ac794246 100644
--- a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java
@@ -40,7 +40,7 @@
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
-class ProjectChildrenTest {
+class ProjectChildLocationsTest {
public static final String OFFICE = "SPK";
@@ -66,7 +66,7 @@ void test_ctor(){
List outlets = new ArrayList<>();
outlets.add(buildId(OFFICE, "TestOutlet1"));
- ProjectChildren projectChildren = new ProjectChildren.Builder()
+ ProjectChildLocations projectChildLocations = new ProjectChildLocations.Builder()
.withProject(proj)
.withEmbankments(embanks)
.withLocks(locks)
@@ -74,20 +74,20 @@ void test_ctor(){
.withTurbines(turbines)
.withOutlets(outlets)
.build();
- assertNotNull(projectChildren);
+ assertNotNull(projectChildLocations);
- assertEquals(proj, projectChildren.getProject());
- assertEquals(embanks, projectChildren.getEmbankments());
- assertEquals(locks, projectChildren.getLocks());
+ assertEquals(proj, projectChildLocations.getProject());
+ assertEquals(embanks, projectChildLocations.getEmbankments());
+ assertEquals(locks, projectChildLocations.getLocks());
- String json = Formats.format(new ContentType(Formats.JSON), projectChildren);
+ String json = Formats.format(new ContentType(Formats.JSON), projectChildLocations);
assertNotNull(json);
}
- private ProjectChildren buildTestProjectChildren(String office, String projectName) {
+ private ProjectChildLocations buildTestProjectChildLocations(String office, String projectName) {
CwmsId proj = buildId(office, projectName);
List embanks = new ArrayList<>();
@@ -108,7 +108,7 @@ private ProjectChildren buildTestProjectChildren(String office, String projectNa
List outlets = new ArrayList<>();
outlets.add(buildId(office, "TestOutlet1"));
- return new ProjectChildren.Builder()
+ return new ProjectChildLocations.Builder()
.withProject(proj)
.withEmbankments(embanks)
.withLocks(locks)
@@ -120,12 +120,12 @@ private ProjectChildren buildTestProjectChildren(String office, String projectNa
@Test
void test_list_serialization(){
- List list = new ArrayList<>();
+ List list = new ArrayList<>();
- list.add(buildTestProjectChildren(OFFICE, "TestProject1"));
- list.add(buildTestProjectChildren(OFFICE, "TestProject2"));
+ list.add(buildTestProjectChildLocations(OFFICE, "TestProject1"));
+ list.add(buildTestProjectChildLocations(OFFICE, "TestProject2"));
- String json = Formats.format(new ContentType(Formats.JSON), list, ProjectChildren.class);
+ String json = Formats.format(new ContentType(Formats.JSON), list, ProjectChildLocations.class);
assertNotNull(json);
assertFalse(json.isEmpty());
assertTrue(json.contains("TestProject1"));
@@ -140,7 +140,7 @@ void test_list_deserialization() throws IOException {
String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
ObjectMapper om = JsonV2.buildObjectMapper();
- List list = om.readValue(input, new TypeReference>(){});
+ List list = om.readValue(input, new TypeReference>(){});
assertNotNull(list);
assertFalse(list.isEmpty());
@@ -155,15 +155,15 @@ void testDeserialize() throws IOException {
String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
ObjectMapper om = JsonV2.buildObjectMapper();
- ProjectChildren projectChildren = om.readValue(input, ProjectChildren.class);
- assertNotNull(projectChildren);
+ ProjectChildLocations projectChildLocations = om.readValue(input, ProjectChildLocations.class);
+ assertNotNull(projectChildLocations);
- CwmsId project = projectChildren.getProject();
+ CwmsId project = projectChildLocations.getProject();
assertNotNull(project);
assertEquals("SPK", project.getOfficeId());
assertEquals("TestProject", project.getName());
- List embankments = projectChildren.getEmbankments();
+ List embankments = projectChildLocations.getEmbankments();
assertNotNull(embankments);
assertEquals(2, embankments.size());
assertEquals("SPK", embankments.get(0).getOfficeId());
@@ -171,7 +171,7 @@ void testDeserialize() throws IOException {
assertEquals("SPK", embankments.get(1).getOfficeId());
assertEquals("TestEmbankment2", embankments.get(1).getName());
- List locks = projectChildren.getLocks();
+ List locks = projectChildLocations.getLocks();
assertNotNull(locks);
assertEquals(3, locks.size());
assertEquals("SPK", locks.get(0).getOfficeId());
@@ -181,19 +181,19 @@ void testDeserialize() throws IOException {
assertEquals("SPK", locks.get(2).getOfficeId());
assertEquals("TestLock3", locks.get(2).getName());
- List outlets = projectChildren.getOutlets();
+ List outlets = projectChildLocations.getOutlets();
assertNotNull(outlets);
assertEquals(1, outlets.size());
assertEquals("SPK", outlets.get(0).getOfficeId());
assertEquals("TestOutlet1", outlets.get(0).getName());
- List turbines = projectChildren.getTurbines();
+ List turbines = projectChildLocations.getTurbines();
assertNotNull(turbines);
assertEquals(1, turbines.size());
assertEquals("SPK", turbines.get(0).getOfficeId());
assertEquals("TestTurbine1", turbines.get(0).getName());
- List gates = projectChildren.getGates();
+ List gates = projectChildLocations.getGates();
assertNotNull(gates);
assertEquals(1, gates.size());
assertEquals("SPK", gates.get(0).getOfficeId());
@@ -208,25 +208,24 @@ void testRoundtrip() throws IOException {
String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
ObjectMapper om = JsonV2.buildObjectMapper();
- ProjectChildren projectChildren1 = om.readValue(input, ProjectChildren.class);
- assertNotNull(projectChildren1);
+ ProjectChildLocations projectChildLocations1 = om.readValue(input, ProjectChildLocations.class);
+ assertNotNull(projectChildLocations1);
- String json = om.writeValueAsString(projectChildren1);
- ProjectChildren projectChildren2 = om.readValue(json, ProjectChildren.class);
- assertNotNull(projectChildren2);
-
- assertProjectChildrenEqual(projectChildren1, projectChildren2);
+ String json = om.writeValueAsString(projectChildLocations1);
+ ProjectChildLocations projectChildLocations2 = om.readValue(json, ProjectChildLocations.class);
+ assertNotNull(projectChildLocations2);
+ assertProjectChildLocationsEqual(projectChildLocations1, projectChildLocations2);
}
- private static void assertProjectChildrenEqual(ProjectChildren projectChildren1, ProjectChildren projectChildren2) {
- assertAll("ProjectChildren",
- () -> assertCwmsIdEqual(projectChildren1.getProject(), projectChildren2.getProject()),
- () -> assertListEqual(projectChildren1.getEmbankments(), projectChildren2.getEmbankments()),
- () -> assertListEqual(projectChildren1.getLocks(), projectChildren2.getLocks()),
- () -> assertListEqual(projectChildren1.getGates(), projectChildren2.getGates()),
- () -> assertListEqual(projectChildren1.getTurbines(), projectChildren2.getTurbines()),
- () -> assertListEqual(projectChildren1.getOutlets(), projectChildren2.getOutlets())
+ private static void assertProjectChildLocationsEqual(ProjectChildLocations projectChildLocations1, ProjectChildLocations projectChildLocations2) {
+ assertAll("ProjectChildLocations",
+ () -> assertCwmsIdEqual(projectChildLocations1.getProject(), projectChildLocations2.getProject()),
+ () -> assertListEqual(projectChildLocations1.getEmbankments(), projectChildLocations2.getEmbankments()),
+ () -> assertListEqual(projectChildLocations1.getLocks(), projectChildLocations2.getLocks()),
+ () -> assertListEqual(projectChildLocations1.getGates(), projectChildLocations2.getGates()),
+ () -> assertListEqual(projectChildLocations1.getTurbines(), projectChildLocations2.getTurbines()),
+ () -> assertListEqual(projectChildLocations1.getOutlets(), projectChildLocations2.getOutlets())
);
}
From a7874b20fc991364cf31012d0218719e0f1aa5f2 Mon Sep 17 00:00:00 2001
From: rma-rripken <89810919+rma-rripken@users.noreply.github.com>
Date: Fri, 28 Jun 2024 09:29:31 -0700
Subject: [PATCH 05/16] Query Project children of a kind.
---
.../src/main/java/cwms/cda/ApiServlet.java | 2 +
.../main/java/cwms/cda/api/Controllers.java | 8 +-
.../api/project/ProjectChildrenHandler.java | 115 ++++++++
.../data/dao/project/ProjectChildrenDao.java | 262 ++++++++++++++++++
.../cda/data/dto/project/ProjectChildren.java | 146 ++++++++++
.../api/project/ProjectChildrenHandlerIT.java | 237 ++++++++++++++++
.../data/dto/project/ProjectChildrenTest.java | 253 +++++++++++++++++
.../cwms/cda/data/dto/project_children.json | 48 ++++
.../cda/data/dto/project_children_list.json | 18 ++
9 files changed, 1082 insertions(+), 7 deletions(-)
create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
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 bbec6d0d0..676012fe5 100644
--- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
+++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java
@@ -100,6 +100,7 @@
import cwms.cda.api.errors.JsonFieldsException;
import cwms.cda.api.errors.NotFoundException;
import cwms.cda.api.errors.RequiredQueryParameterException;
+import cwms.cda.api.project.ProjectChildrenHandler;
import cwms.cda.api.location.kind.VirtualOutletCreateController;
import cwms.cda.data.dao.JooqDao;
import cwms.cda.formatters.Formats;
@@ -519,6 +520,7 @@ protected void configureRoutes() {
cdaCrudCache(virtualOutletPath, new VirtualOutletController(metrics), requiredRoles, 1, TimeUnit.DAYS);
post(virtualOutletCreatePath, new VirtualOutletCreateController(metrics));
+ get("/projects/children/", new ProjectChildrenHandler(metrics));
cdaCrudCache(format("/projects/{%s}", Controllers.NAME),
new ProjectController(metrics), requiredRoles,5, TimeUnit.MINUTES);
cdaCrudCache(format("/properties/{%s}", Controllers.NAME),
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 c12b0fae9..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
@@ -77,9 +77,6 @@ public final class Controllers {
public static final String LOCATION_CATEGORY_LIKE = "location-category-like";
public static final String LOCATION_GROUP_LIKE = "location-group-like";
-
-
-
public static final String TIMESERIES_GROUP_LIKE = "timeseries-group-like";
public static final String ACCEPT = "Accept";
public static final String CLOB_ID = "clob-id";
@@ -145,7 +142,6 @@ public final class Controllers {
public static final String ANY_MASK = "*";
public static final String OFFICE_MASK = "office-mask";
public static final String ID_MASK = "id-mask";
- public static final String LOCATION_MASK = "location-mask";
public static final String NAME_MASK = "name-mask";
public static final String BOTTOM_MASK = "bottom-mask";
public static final String TOP_MASK = "top-mask";
@@ -164,9 +160,6 @@ public final class Controllers {
public static final String STATUS_501 = "501";
public static final String STATUS_400 = "400";
public static final String TEXT_MASK = "text-mask";
- public static final String DELETE_MODE = "delete-mode";
- public static final String MIN_ATTRIBUTE = "min-attribute";
- public static final String MAX_ATTRIBUTE = "max-attribute";
public static final String STANDARD_TEXT_ID_MASK = "standard-text-id-mask";
public static final String STANDARD_TEXT_ID = "standard-text-id";
public static final String STREAM_ID_MASK = "stream-id-mask";
@@ -189,6 +182,7 @@ public final class Controllers {
public static final String DEFAULT_VALUE = "default-value";
public static final String CATEGORY = "category";
public static final String PREFIX = "prefix";
+ public static final String PROJECT_LIKE = "project-like";
private static final String DEPRECATED_HEADER = "CWMS-DATA-Format-Deprecated";
private static final String DEPRECATED_TAB = "2024-11-01 TAB is not used often.";
diff --git a/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
new file mode 100644
index 000000000..25c5267ae
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/api/project/ProjectChildrenHandler.java
@@ -0,0 +1,115 @@
+/*
+ * 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.project;
+
+import static com.codahale.metrics.MetricRegistry.name;
+import static cwms.cda.api.Controllers.GET_ALL;
+import static cwms.cda.api.Controllers.LOCATION_KIND_LIKE;
+import static cwms.cda.api.Controllers.OFFICE;
+import static cwms.cda.api.Controllers.PROJECT_LIKE;
+import static cwms.cda.api.Controllers.STATUS_200;
+import static cwms.cda.api.Controllers.requiredParam;
+
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import cwms.cda.api.Controllers;
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dao.project.ProjectChildrenDao;
+import cwms.cda.data.dto.project.ProjectChildren;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import io.javalin.core.util.Header;
+import io.javalin.http.Context;
+import io.javalin.http.Handler;
+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;
+
+
+public class ProjectChildrenHandler implements Handler {
+ public static final String TAGS = "Projects";
+ public static final String PATH = "/projects/children";
+ private final MetricRegistry metrics;
+ private final Histogram requestResultSize;
+
+ private Timer.Context markAndTime(String subject) {
+ return Controllers.markAndTime(metrics, getClass().getName(), subject);
+ }
+
+ public ProjectChildrenHandler(MetricRegistry metrics) {
+ this.metrics = metrics;
+ requestResultSize = this.metrics.histogram((name(ProjectChildrenHandler.class, Controllers.RESULTS,
+ Controllers.SIZE)));
+ }
+
+
+ @OpenApi(
+ description = "Get a list of project child locations",
+ queryParams = {
+ @OpenApiParam(name = OFFICE, required = true),
+ @OpenApiParam(name = PROJECT_LIKE, description = "Posix regular expression matching "
+ + "against the project_id."),
+ @OpenApiParam(name = LOCATION_KIND_LIKE, description = "Posix regular expression matching against the "
+ + "location kind. The pattern will be matched against "
+ + "the valid location-kinds for Project Children:"
+ + "{\"EMBANKMENT\", \"TURBINE\", \"OUTLET\", \"LOCK\", \"GATE\"}. "
+ + "Multiple kinds can be matched by using Regular Expression "
+ + "OR clauses. For example: \"(TURBINE|OUTLET)\"")
+ },
+ responses = {
+ @OpenApiResponse(status = STATUS_200, content = {
+ @OpenApiContent(type = Formats.JSON, from = ProjectChildren.class, isArray = true)})
+ },
+ tags = {TAGS},
+ path = PATH,
+ method = HttpMethod.GET
+ )
+ @Override
+ public void handle(@NotNull Context ctx) throws Exception {
+ String office = requiredParam(ctx, OFFICE);
+ try (Timer.Context ignored = markAndTime(GET_ALL)) {
+ ProjectChildrenDao lockDao = new ProjectChildrenDao(JooqDao.getDslContext(ctx));
+ String projLike = ctx.queryParamAsClass(PROJECT_LIKE, String.class).getOrDefault(null);
+ String kindLike = ctx.queryParamAsClass(LOCATION_KIND_LIKE, String.class).getOrDefault(null);
+
+ List children = lockDao.children(office, projLike, kindLike);
+ String formatHeader = ctx.header(Header.ACCEPT);
+ ContentType contentType = Formats.parseHeader(formatHeader, ProjectChildren.class);
+ String result = Formats.format(contentType, children, ProjectChildren.class);
+ ctx.result(result).contentType(contentType.toString());
+ requestResultSize.update(result.length());
+ ctx.status(HttpServletResponse.SC_OK);
+ }
+ }
+
+
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
new file mode 100644
index 000000000..228517b6c
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
@@ -0,0 +1,262 @@
+/*
+ * 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.dao.project;
+
+import cwms.cda.data.dao.JooqDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.project.ProjectChildren;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jooq.DSLContext;
+import usace.cwms.db.jooq.codegen.tables.AV_EMBANKMENT;
+import usace.cwms.db.jooq.codegen.tables.AV_GATE;
+import usace.cwms.db.jooq.codegen.tables.AV_LOCK;
+import usace.cwms.db.jooq.codegen.tables.AV_OUTLET;
+import usace.cwms.db.jooq.codegen.tables.AV_TURBINE;
+
+public class ProjectChildrenDao extends JooqDao {
+
+ public ProjectChildrenDao(DSLContext dsl) {
+ super(dsl);
+ }
+
+
+ public List children(String office, String projLike, String kindRegex) {
+
+ List allKinds = getProjectKinds();
+
+ Set kinds = getMatchingKinds(kindRegex, allKinds);
+
+ return children(office, projLike, kinds);
+ }
+
+ private List children(String office, String projLike, Set kinds) {
+
+ Map builderMap = new LinkedHashMap<>(); // proj-id->
+
+ for (String kind : kinds) {
+ Map> locsOfKind = getChildrenOfKind(office, projLike, kind);
+ if (locsOfKind != null) {
+ for (Map.Entry> entry : locsOfKind.entrySet()) {
+ String projId = entry.getKey();
+ List locs = entry.getValue();
+ ProjectChildren.Builder builder = builderMap.computeIfAbsent(projId, k ->
+ new ProjectChildren.Builder()
+ .withProject(new CwmsId.Builder()
+ .withOfficeId(office)
+ .withName(projId)
+ .build()));
+ switch (kind) {
+ case "EMBANKMENT":
+ builder.withEmbankments(locs);
+ break;
+ case "LOCK":
+ builder.withLocks(locs);
+ break;
+ case "OUTLET":
+ builder.withOutlets(locs);
+ break;
+ case "TURBINE":
+ builder.withTurbines(locs);
+ break;
+ case "GATE":
+ builder.withGates(locs);
+ break;
+ default:
+ break;
+ }
+
+ }
+ }
+ }
+
+ return builderMap.values().stream()
+ .map(ProjectChildren.Builder::build)
+ .collect(Collectors.toList());
+
+ }
+
+ @Nullable
+ private Map> getChildrenOfKind(String office, String projLike, String kind) {
+ switch (kind) {
+
+ case "EMBANKMENT":
+ return getEmbankmentChildren(office, projLike);
+ case "TURBINE":
+ return getTurbineChildren(office, projLike);
+ case "OUTLET":
+ return getOutletChildren(office, projLike);
+ case "LOCK":
+ return getLockChildren(office, projLike);
+ case "GATE":
+ return getGateChildren(office, projLike);
+ default:
+ return null;
+ }
+
+ }
+
+ private Map> getGateChildren(String office, @Nullable String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_GATE view = AV_GATE.AV_GATE;
+ dsl.selectDistinct(view.OFFICE_ID, view.GATE_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.GATE_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.GATE_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getLockChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_LOCK view = AV_LOCK.AV_LOCK;
+ dsl.selectDistinct(view.DB_OFFICE_ID, view.LOCK_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.DB_OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.DB_OFFICE_ID, view.PROJECT_ID, view.LOCK_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.DB_OFFICE_ID))
+ .withName(row.get(view.LOCK_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getOutletChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_OUTLET view = AV_OUTLET.AV_OUTLET;
+ dsl.selectDistinct(view.OFFICE_ID, view.OUTLET_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.OUTLET_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.OUTLET_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getTurbineChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_TURBINE view = AV_TURBINE.AV_TURBINE;
+ dsl.selectDistinct(view.OFFICE_ID, view.TURBINE_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.TURBINE_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.TURBINE_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private Map> getEmbankmentChildren(String office, String projLike) {
+ Map> retval = new LinkedHashMap<>();
+ AV_EMBANKMENT view = AV_EMBANKMENT.AV_EMBANKMENT;
+ dsl.selectDistinct(view.OFFICE_ID, view.EMBANKMENT_LOCATION_ID, view.PROJECT_ID)
+ .from(view)
+ .where(view.OFFICE_ID.eq(office)
+ .and(view.UNIT_SYSTEM.eq("SI")))
+ .and(caseInsensitiveLikeRegexNullTrue(view.PROJECT_ID, projLike))
+ .orderBy(view.OFFICE_ID, view.PROJECT_ID, view.EMBANKMENT_LOCATION_ID)
+ .forEach(row -> {
+ String projId = row.get(view.PROJECT_ID);
+ CwmsId embankment = new CwmsId.Builder()
+ .withOfficeId(row.get(view.OFFICE_ID))
+ .withName(row.get(view.EMBANKMENT_LOCATION_ID))
+ .build();
+ retval.computeIfAbsent(projId, k -> new ArrayList<>()).add(embankment);
+ });
+
+
+ return retval;
+ }
+
+ private static @NotNull List getProjectKinds() {
+ // Maybe this should be an enum? Maybe just the Project types.
+ // "SITE", "EMBANKMENT", "OVERFLOW", "TURBINE", "STREAM", "PROJECT",
+ // "STREAMGAGE", "BASIN", "OUTLET", "LOCK", "GATE"
+ String [] kinds = { "EMBANKMENT", "TURBINE", "OUTLET", "LOCK", "GATE"};
+ return Arrays.asList(kinds);
+ }
+
+ private Set getMatchingKinds(String regex, List allKinds) {
+ Set kinds = new LinkedHashSet<>();
+
+ if (regex == null) {
+ kinds.addAll(allKinds);
+ } else {
+ Pattern p = Pattern.compile(regex);
+ for (String kind : allKinds) {
+ Matcher matcher = p.matcher(kind);
+ if (matcher.matches()) {
+ kinds.add(kind);
+ }
+ }
+ }
+
+ return kinds;
+ }
+}
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
new file mode 100644
index 000000000..b5591f721
--- /dev/null
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildren.java
@@ -0,0 +1,146 @@
+/*
+ * 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.project;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonNaming;
+import cwms.cda.api.errors.FieldException;
+import cwms.cda.data.dto.CwmsDTOBase;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.annotations.FormattableWith;
+import cwms.cda.formatters.json.JsonV2;
+import java.util.Collections;
+import java.util.List;
+import org.jetbrains.annotations.Nullable;
+
+@JsonDeserialize(builder = ProjectChildren.Builder.class)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
+@FormattableWith(contentType = Formats.JSON, formatter = JsonV2.class)
+public class ProjectChildren implements CwmsDTOBase {
+
+ private final CwmsId project;
+
+ private final List embankments;
+ private final List locks;
+ private final List outlets;
+ private final List turbines;
+ private final List gates;
+
+ private ProjectChildren(Builder builder) {
+ this.project = builder.project;
+ this.embankments = builder.embankments;
+ this.locks = builder.locks;
+ this.outlets = builder.outlets;
+ this.turbines = builder.turbines;
+ this.gates = builder.gates;
+ }
+
+ @Override
+ public void validate() throws FieldException {
+
+ }
+
+ public CwmsId getProject() {
+ return project;
+ }
+
+ public List getEmbankments() {
+ return embankments;
+ }
+
+ public List getLocks() {
+ return locks;
+ }
+
+ public List getOutlets() {
+ return outlets;
+ }
+
+ public List getTurbines() {
+ return turbines;
+ }
+
+ public List getGates() {
+ return gates;
+ }
+
+
+ public static class Builder {
+ private CwmsId project;
+ private List embankments;
+ private List locks;
+ private List outlets;
+ private List turbines;
+ private List gates;
+
+ public Builder withProject(CwmsId project) {
+ this.project = project;
+ return this;
+ }
+
+ public Builder withEmbankments(List embankments) {
+ this.embankments = wrapList(embankments);
+ return this;
+ }
+
+
+ public Builder withLocks(List locks) {
+ this.locks = wrapList(locks);
+ return this;
+ }
+
+ public Builder withOutlets(List outlets) {
+ this.outlets = outlets;
+ return this;
+ }
+
+ public Builder withTurbines(List turbines) {
+ this.turbines = wrapList(turbines);
+ return this;
+ }
+
+ public Builder withGates(List gates) {
+ this.gates = wrapList(gates);
+ return this;
+ }
+
+ @Nullable
+ private static List wrapList(@Nullable List embankments) {
+ List retval = null;
+ if (embankments != null) {
+ retval = Collections.unmodifiableList(embankments);
+ }
+ return retval;
+ }
+
+ public ProjectChildren build() {
+ return new ProjectChildren(this);
+ }
+ }
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
new file mode 100644
index 000000000..3f6883763
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildrenHandlerIT.java
@@ -0,0 +1,237 @@
+/*
+ * 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.project;
+
+
+import static cwms.cda.api.Controllers.LOCATION_KIND_LIKE;
+import static cwms.cda.api.Controllers.PROJECT_LIKE;
+import static cwms.cda.data.dao.JooqDao.getDslContext;
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+import static org.hamcrest.core.Is.is;
+
+import cwms.cda.api.Controllers;
+import cwms.cda.api.DataApiTestIT;
+import cwms.cda.api.enums.Nation;
+import cwms.cda.data.dao.DeleteRule;
+import cwms.cda.data.dao.LocationsDaoImpl;
+import cwms.cda.data.dao.location.kind.EmbankmentDao;
+import cwms.cda.data.dao.project.ProjectDao;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.data.dto.Location;
+import cwms.cda.data.dto.LookupType;
+import cwms.cda.data.dto.location.kind.Embankment;
+import cwms.cda.data.dto.project.Project;
+import cwms.cda.formatters.Formats;
+import fixtures.CwmsDataApiSetupCallback;
+import io.restassured.filter.log.LogDetail;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.sql.SQLException;
+import java.time.Instant;
+import java.time.ZoneId;
+import javax.servlet.http.HttpServletResponse;
+import mil.army.usace.hec.test.database.CwmsDatabaseContainer;
+import org.jooq.DSLContext;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag("integration")
+public class ProjectChildrenHandlerIT extends DataApiTestIT {
+
+ public static final String OFFICE = "SPK";
+
+ @Test
+ void test_get_embankment() throws Exception {
+ CwmsDatabaseContainer> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
+ databaseLink.connection(c -> {
+ DSLContext dsl = getDslContext(c, OFFICE);
+
+ ProjectDao prjDao = new ProjectDao(dsl);
+
+ String projectName = "projChild";
+ CwmsId projectId = new CwmsId.Builder()
+ .withName(projectName)
+ .withOfficeId(OFFICE)
+ .build();
+
+ Project testProject = buildTestProject(OFFICE, projectName);
+ prjDao.create(testProject);
+
+ String locName1 = "TEST_LOCATION1";
+ String locName2 = "TEST_LOCATION2";
+ try {
+ createLocation(locName1, true, OFFICE);
+ createLocation(locName2, true, OFFICE);
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+
+ LocationsDaoImpl locationsDao = new LocationsDaoImpl(dsl);
+ Location location1 = buildTestLocation(OFFICE, locName1);
+ try {
+ locationsDao.storeLocation(location1);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ Location location2 = buildTestLocation(OFFICE, locName2);
+ try {
+ locationsDao.storeLocation(location2);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+
+ Embankment embank = buildTestEmbankment(location1, projectId);
+ EmbankmentDao embankmentDao = new EmbankmentDao(dsl);
+ embankmentDao.storeEmbankment(embank, false);
+
+ Embankment embank2 = buildTestEmbankment(location2, projectId);
+ embankmentDao.storeEmbankment(embank2, false);
+ try {
+
+ given()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .accept(Formats.JSON)
+ .queryParam(Controllers.OFFICE, OFFICE)
+ .queryParam(PROJECT_LIKE, projectName)
+ .queryParam(LOCATION_KIND_LIKE, "(EMBANKMENT|TURBINE)")
+ .when()
+ .redirects().follow(true)
+ .redirects().max(3)
+ .get("/projects/children/")
+ .then()
+ .log().ifValidationFails(LogDetail.ALL, true)
+ .assertThat()
+ .statusCode(is(HttpServletResponse.SC_OK))
+ .body("size()", is(1))
+ .body("[0].project.office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].project.name", equalToIgnoringCase(projectName))
+ .body("[0].embankments.size()", is(2))
+ .body("[0].embankments[0].office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].embankments[0].name", equalToIgnoringCase(locName1))
+ .body("[0].embankments[1].office-id", equalToIgnoringCase(OFFICE))
+ .body("[0].embankments[1].name", equalToIgnoringCase(locName2))
+ ;
+ } finally {
+ embankmentDao.deleteEmbankment(embank.getLocation().getName(), OFFICE, DeleteRule.DELETE_ALL );
+ embankmentDao.deleteEmbankment(embank2.getLocation().getName(), OFFICE, DeleteRule.DELETE_ALL);
+ prjDao.delete(projectId.getOfficeId(), projectId.getName(), DeleteRule.DELETE_ALL);
+ }
+ });
+
+ }
+
+ private Embankment buildTestEmbankment(Location location, CwmsId projId) {
+ return new Embankment.Builder()
+ .withLocation(location)
+ .withMaxHeight(5.0)
+ .withProjectId(projId)
+ .withStructureLength(10.0)
+ .withStructureType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Rolled Earth-Filled")
+ .withTooltip("An embankment formed by compacted earth")
+ .withActive(true)
+ .build())
+ .withDownstreamProtectionType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Concrete Arch Facing")
+ .withTooltip("Protected by the faces of the concrete arches")
+ .withActive(true)
+ .build())
+ .withUpstreamProtectionType(new LookupType.Builder()
+ .withOfficeId("CWMS")
+ .withDisplayValue("Concrete Blanket")
+ .withTooltip("Protected by blanket of concrete")
+ .withActive(true)
+ .build())
+ .withUpstreamSideSlope(15.0)
+ .withLengthUnits("ft")
+ .withTopWidth(20.0)
+ .withStructureLength(25.0)
+ .withDownstreamSideSlope(90.0)
+ .build();
+ }
+
+ private Location buildTestLocation(String office, String name) {
+ return new Location.Builder(name, "EMBANKMENT", ZoneId.of("UTC"),
+ 50.0, 50.0, "NVGD29", office)
+ .withElevation(10.0)
+ .withElevationUnits("ft")
+ .withLocationType("SITE")
+ .withActive(true)
+ .withNation(Nation.US)
+ .withStateInitial("CA")
+ .withCountyName("Yolo")
+ .withBoundingOfficeId(office)
+ .withPublishedLatitude(38.54)
+ .withPublishedLongitude(-121.74)
+ .withDescription("for testing")
+ .withNearestCity("Davis")
+ .build();
+ }
+
+ public static Project buildTestProject(String office, String prjId) {
+ Location pbLoc = new Location.Builder(office,prjId + "-PB")
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+ Location ngLoc = new Location.Builder(office,prjId + "-NG")
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+
+ Location prjLoc = new Location.Builder(office, prjId)
+ .withTimeZoneName(ZoneId.of("UTC"))
+ .withActive(null)
+ .build();
+
+ return new Project.Builder()
+ .withLocation(prjLoc)
+ .withProjectOwner("Project Owner")
+ .withAuthorizingLaw("Authorizing Law")
+ .withFederalCost(BigDecimal.valueOf(100.0))
+ .withNonFederalCost(BigDecimal.valueOf(50.0))
+ .withFederalOAndMCost(BigDecimal.valueOf(10.0))
+ .withNonFederalOAndMCost(BigDecimal.valueOf(5.0))
+ .withCostYear(Instant.now())
+ .withCostUnit("$")
+ .withYieldTimeFrameEnd(Instant.now())
+ .withYieldTimeFrameStart(Instant.now())
+ .withFederalOAndMCost(BigDecimal.valueOf(10.0))
+ .withNonFederalOAndMCost(BigDecimal.valueOf(5.0))
+ .withProjectRemarks("Remarks")
+ .withPumpBackLocation(pbLoc)
+ .withNearGageLocation(ngLoc)
+ .withBankFullCapacityDesc("Bank Full Capacity Description")
+ .withDownstreamUrbanDesc("Downstream Urban Description")
+ .withHydropowerDesc("Hydropower Description")
+ .withSedimentationDesc("Sedimentation Description")
+ .build();
+
+ }
+
+}
diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
new file mode 100644
index 000000000..c3e8b808e
--- /dev/null
+++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildrenTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.project;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import cwms.cda.data.dto.CwmsId;
+import cwms.cda.formatters.ContentType;
+import cwms.cda.formatters.Formats;
+import cwms.cda.formatters.json.JsonV2;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.junit.jupiter.api.Test;
+
+class ProjectChildrenTest {
+
+ public static final String OFFICE = "SPK";
+
+ @Test
+ void test_ctor(){
+ CwmsId proj = buildId(OFFICE, "TestProject");
+
+ List embanks = new ArrayList<>();
+ embanks.add(buildId(OFFICE, "TestEmbankment1"));
+ embanks.add(buildId(OFFICE, "TestEmbankment2"));
+
+ List locks = new ArrayList<>();
+ locks.add(buildId(OFFICE, "TestLock1"));
+ locks.add(buildId(OFFICE, "TestLock2"));
+ locks.add(buildId(OFFICE, "TestLock3"));
+
+ List gates = new ArrayList<>();
+ gates.add(buildId(OFFICE, "TestGate1"));
+
+ List turbines = new ArrayList<>();
+ turbines.add(buildId(OFFICE, "TestTurbine1"));
+
+ List outlets = new ArrayList<>();
+ outlets.add(buildId(OFFICE, "TestOutlet1"));
+
+ ProjectChildren projectChildren = new ProjectChildren.Builder()
+ .withProject(proj)
+ .withEmbankments(embanks)
+ .withLocks(locks)
+ .withGates(gates)
+ .withTurbines(turbines)
+ .withOutlets(outlets)
+ .build();
+ assertNotNull(projectChildren);
+
+ assertEquals(proj, projectChildren.getProject());
+ assertEquals(embanks, projectChildren.getEmbankments());
+ assertEquals(locks, projectChildren.getLocks());
+
+ String json = Formats.format(new ContentType(Formats.JSON), projectChildren);
+
+
+ assertNotNull(json);
+
+ }
+
+ private ProjectChildren buildTestProjectChildren(String office, String projectName) {
+ CwmsId proj = buildId(office, projectName);
+
+ List embanks = new ArrayList<>();
+ embanks.add(buildId(office, "TestEmbankment1"));
+ embanks.add(buildId(office, "TestEmbankment2"));
+
+ List locks = new ArrayList<>();
+ locks.add(buildId(office, "TestLock1"));
+ locks.add(buildId(office, "TestLock2"));
+ locks.add(buildId(office, "TestLock3"));
+
+ List gates = new ArrayList<>();
+ gates.add(buildId(office, "TestGate1"));
+
+ List turbines = new ArrayList<>();
+ turbines.add(buildId(office, "TestTurbine1"));
+
+ List outlets = new ArrayList<>();
+ outlets.add(buildId(office, "TestOutlet1"));
+
+ return new ProjectChildren.Builder()
+ .withProject(proj)
+ .withEmbankments(embanks)
+ .withLocks(locks)
+ .withGates(gates)
+ .withTurbines(turbines)
+ .withOutlets(outlets)
+ .build();
+ }
+
+ @Test
+ void test_list_serialization(){
+ List list = new ArrayList<>();
+
+ list.add(buildTestProjectChildren(OFFICE, "TestProject1"));
+ list.add(buildTestProjectChildren(OFFICE, "TestProject2"));
+
+ String json = Formats.format(new ContentType(Formats.JSON), list, ProjectChildren.class);
+ assertNotNull(json);
+ assertFalse(json.isEmpty());
+ assertTrue(json.contains("TestProject1"));
+ assertTrue(json.contains("TestProject2"));
+ }
+
+ @Test
+ void test_list_deserialization() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children_list.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ List list = om.readValue(input, new TypeReference>(){});
+
+ assertNotNull(list);
+ assertFalse(list.isEmpty());
+ }
+
+
+ @Test
+ void testDeserialize() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ ProjectChildren projectChildren = om.readValue(input, ProjectChildren.class);
+ assertNotNull(projectChildren);
+
+ CwmsId project = projectChildren.getProject();
+ assertNotNull(project);
+ assertEquals("SPK", project.getOfficeId());
+ assertEquals("TestProject", project.getName());
+
+ List embankments = projectChildren.getEmbankments();
+ assertNotNull(embankments);
+ assertEquals(2, embankments.size());
+ assertEquals("SPK", embankments.get(0).getOfficeId());
+ assertEquals("TestEmbankment1", embankments.get(0).getName());
+ assertEquals("SPK", embankments.get(1).getOfficeId());
+ assertEquals("TestEmbankment2", embankments.get(1).getName());
+
+ List locks = projectChildren.getLocks();
+ assertNotNull(locks);
+ assertEquals(3, locks.size());
+ assertEquals("SPK", locks.get(0).getOfficeId());
+ assertEquals("TestLock1", locks.get(0).getName());
+ assertEquals("SPK", locks.get(1).getOfficeId());
+ assertEquals("TestLock2", locks.get(1).getName());
+ assertEquals("SPK", locks.get(2).getOfficeId());
+ assertEquals("TestLock3", locks.get(2).getName());
+
+ List outlets = projectChildren.getOutlets();
+ assertNotNull(outlets);
+ assertEquals(1, outlets.size());
+ assertEquals("SPK", outlets.get(0).getOfficeId());
+ assertEquals("TestOutlet1", outlets.get(0).getName());
+
+ List turbines = projectChildren.getTurbines();
+ assertNotNull(turbines);
+ assertEquals(1, turbines.size());
+ assertEquals("SPK", turbines.get(0).getOfficeId());
+ assertEquals("TestTurbine1", turbines.get(0).getName());
+
+ List gates = projectChildren.getGates();
+ assertNotNull(gates);
+ assertEquals(1, gates.size());
+ assertEquals("SPK", gates.get(0).getOfficeId());
+ assertEquals("TestGate1", gates.get(0).getName());
+ }
+
+ @Test
+ void testRoundtrip() throws IOException {
+ InputStream stream = ProjectTest.class.getClassLoader().getResourceAsStream(
+ "cwms/cda/data/dto/project_children.json");
+ assertNotNull(stream);
+ String input = IOUtils.toString(stream, StandardCharsets.UTF_8);
+
+ ObjectMapper om = JsonV2.buildObjectMapper();
+ ProjectChildren projectChildren1 = om.readValue(input, ProjectChildren.class);
+ assertNotNull(projectChildren1);
+
+ String json = om.writeValueAsString(projectChildren1);
+ ProjectChildren projectChildren2 = om.readValue(json, ProjectChildren.class);
+ assertNotNull(projectChildren2);
+
+ assertProjectChildrenEqual(projectChildren1, projectChildren2);
+
+ }
+
+ private static void assertProjectChildrenEqual(ProjectChildren projectChildren1, ProjectChildren projectChildren2) {
+ assertAll("ProjectChildren",
+ () -> assertCwmsIdEqual(projectChildren1.getProject(), projectChildren2.getProject()),
+ () -> assertListEqual(projectChildren1.getEmbankments(), projectChildren2.getEmbankments()),
+ () -> assertListEqual(projectChildren1.getLocks(), projectChildren2.getLocks()),
+ () -> assertListEqual(projectChildren1.getGates(), projectChildren2.getGates()),
+ () -> assertListEqual(projectChildren1.getTurbines(), projectChildren2.getTurbines()),
+ () -> assertListEqual(projectChildren1.getOutlets(), projectChildren2.getOutlets())
+ );
+ }
+
+ private static void assertCwmsIdEqual(CwmsId project1, CwmsId project2) {
+ assertAll("CwmsId",
+ () -> assertEquals(project1.getOfficeId(), project2.getOfficeId()),
+ () -> assertEquals(project1.getName(), project2.getName())
+ );
+ }
+
+ private static void assertListEqual(List locks1, List locks2) {
+ assertEquals(locks1.size(), locks2.size());
+ for(int i = 0; i < locks1.size(); i++){
+ assertCwmsIdEqual(locks1.get(i), locks2.get(i));
+ }
+ }
+
+ private static CwmsId buildId(String office, String name) {
+ return new CwmsId.Builder()
+ .withOfficeId(office)
+ .withName(name)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
new file mode 100644
index 000000000..3d677d47e
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children.json
@@ -0,0 +1,48 @@
+{
+ "project": {
+ "office-id": "SPK",
+ "name": "TestProject"
+ },
+ "embankments": [
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment2"
+ }
+ ],
+ "locks": [
+ {
+ "office-id": "SPK",
+ "name": "TestLock1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestLock2"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestLock3"
+ }
+ ],
+ "outlets": [
+ {
+ "office-id": "SPK",
+ "name": "TestOutlet1"
+ }
+ ],
+ "turbines": [
+ {
+ "office-id": "SPK",
+ "name": "TestTurbine1"
+ }
+ ],
+ "gates": [
+ {
+ "office-id": "SPK",
+ "name": "TestGate1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
new file mode 100644
index 000000000..d20a26db9
--- /dev/null
+++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/project_children_list.json
@@ -0,0 +1,18 @@
+[
+ {
+ "project": {
+ "office-id": "SPK",
+ "name": "TestProject1"
+ },
+ "embankments": [
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment1"
+ },
+ {
+ "office-id": "SPK",
+ "name": "TestEmbankment2"
+ }
+ ]
+ }
+]
\ No newline at end of file
From 412c48b48a45393195992ee79bb4172dd8694890 Mon Sep 17 00:00:00 2001
From: rma-rripken
Date: Wed, 3 Jul 2024 16:23:31 -0700
Subject: [PATCH 06/16] Added enum type and fixed copy/paste variable name.
---
.../data/dao/project/ProjectChildrenDao.java | 86 +++++------------
.../cda/data/dao/project/ProjectKind.java | 33 +++++++
.../cda/data/dao/project/ProjectKindTest.java | 96 +++++++++++++++++++
3 files changed, 153 insertions(+), 62 deletions(-)
create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectKind.java
create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dao/project/ProjectKindTest.java
diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
index 228517b6c..557879a80 100644
--- a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
+++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildrenDao.java
@@ -27,17 +27,9 @@
import cwms.cda.data.dao.JooqDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.project.ProjectChildren;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
+import java.util.*;
import java.util.stream.Collectors;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jooq.DSLContext;
import usace.cwms.db.jooq.codegen.tables.AV_EMBANKMENT;
@@ -54,19 +46,14 @@ public ProjectChildrenDao(DSLContext dsl) {
public List children(String office, String projLike, String kindRegex) {
-
- List allKinds = getProjectKinds();
-
- Set kinds = getMatchingKinds(kindRegex, allKinds);
-
- return children(office, projLike, kinds);
+ return children(office, projLike, ProjectKind.getMatchingKinds(kindRegex));
}
- private List children(String office, String projLike, Set kinds) {
+ private List children(String office, String projLike, Set kinds) {
Map builderMap = new LinkedHashMap<>(); // proj-id->
- for (String kind : kinds) {
+ for (ProjectKind kind : kinds) {
Map