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> 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 6d195d02d1100e6d7ea79ccf007af33b0e6e477e Mon Sep 17 00:00:00 2001 From: rma-rripken Date: Wed, 3 Jul 2024 17:27:41 -0700 Subject: [PATCH 07/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 842ad772c962eba68b9ba2dfcce00f8cdf2d0cfc Mon Sep 17 00:00:00 2001 From: rma-rripken Date: Wed, 3 Jul 2024 17:41:05 -0700 Subject: [PATCH 08/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 676012fe5..b1852699e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -100,7 +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.project.ProjectChildLocationHandler; import cwms.cda.api.location.kind.VirtualOutletCreateController; import cwms.cda.data.dao.JooqDao; import cwms.cda.formatters.Formats; @@ -520,7 +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)); + 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 0796caea411b3dde1364ebcb1c5e3c75dc4fc89e Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:09:47 -0700 Subject: [PATCH 09/16] Minor formatting --- .../cwms/cda/formatters/ContentTypeTest.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/formatters/ContentTypeTest.java b/cwms-data-api/src/test/java/cwms/cda/formatters/ContentTypeTest.java index 3b42193bf..136be708a 100644 --- a/cwms-data-api/src/test/java/cwms/cda/formatters/ContentTypeTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/formatters/ContentTypeTest.java @@ -14,7 +14,7 @@ void test_ctor() { ContentType ct = new ContentType("application/json"); assertEquals("application/json", ct.getType()); Map parameters = ct.getParameters(); - assertTrue(parameters==null || parameters.isEmpty()); + assertTrue(parameters == null || parameters.isEmpty()); } @Test @@ -28,7 +28,7 @@ void test_ctor_null() { void test_ctor_empty() { ContentType ct = new ContentType(""); Map parameters = ct.getParameters(); - assertTrue(parameters==null || parameters.isEmpty()); + assertTrue(parameters == null || parameters.isEmpty()); assertNull(ct.getCharset()); } @@ -36,26 +36,26 @@ void test_ctor_empty() { void test_ctor_garbage() { ContentType ct = new ContentType("qawicxqyjx"); Map parameters = ct.getParameters(); - assertTrue(parameters==null || parameters.isEmpty()); + assertTrue(parameters == null || parameters.isEmpty()); assertNull(ct.getCharset()); } @Test - void test_ctor_w_charset(){ + void test_ctor_w_charset() { ContentType ct = new ContentType("application/json;charset=UTF-8"); assertEquals("application/json", ct.getType()); Map parameters = ct.getParameters(); - assertTrue(parameters==null || parameters.isEmpty()); + assertTrue(parameters == null || parameters.isEmpty()); assertEquals("UTF-8", ct.getCharset()); } @Test - void test_ctor_w_charset_space(){ + void test_ctor_w_charset_space() { ContentType ct = new ContentType("application/json; charset=UTF-8"); assertEquals("application/json", ct.getType()); Map parameters = ct.getParameters(); - assertTrue(parameters==null || parameters.isEmpty()); + assertTrue(parameters == null || parameters.isEmpty()); assertEquals("UTF-8", ct.getCharset()); } @@ -63,9 +63,9 @@ void test_ctor_w_charset_space(){ @ParameterizedTest @CsvSource(value = { - "application/json;charset=utf-8|application/json;", - "application/json;version=2,charset=utf-8|application/json;version=2" - },delimiter='|') + "application/json;charset=utf-8|application/json;", + "application/json;version=2,charset=utf-8|application/json;version=2" + }, delimiter = '|') void test_equals_and_equivalent(String a, String b) { ContentType aCt = new ContentType(a); ContentType bCt = new ContentType(b); From b226b8cd92148466de7455151622ee0511a65e69 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:10:21 -0700 Subject: [PATCH 10/16] Minor formatting --- .../java/cwms/cda/formatters/FormatsTest.java | 416 +++++++++--------- 1 file changed, 201 insertions(+), 215 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java b/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java index d5046b14e..810ca33a8 100644 --- a/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java @@ -19,233 +19,219 @@ import org.junit.jupiter.params.provider.EnumSource; -class FormatsTest -{ +class FormatsTest { - @Test - void testParseHeaderAndQueryParmJSON(){ - ContentType contentType =Formats.parseHeaderAndQueryParm("application/json", null); + public static final String FIREFOX_HEADER = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"; - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - Map parameters = contentType.getParameters(); - assertTrue(parameters == null || parameters.isEmpty()); + @Test + void testParseHeaderAndQueryParmJson() { + ContentType contentType = Formats.parseHeaderAndQueryParm("application/json", null); - } + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + Map parameters = contentType.getParameters(); + assertTrue(parameters == null || parameters.isEmpty()); - @Test - void testParseHeaderAndQueryParmJSONv2() - { - ContentType contentType = Formats.parseHeaderAndQueryParm("application/json;version=2", null); + } - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - Map parameters = contentType.getParameters(); - assertNotNull(parameters); - assertFalse(parameters.isEmpty()); - assertTrue(parameters.containsKey("version")); - assertEquals("2", parameters.get("version")); - } + @Test + void testParseHeaderAndQueryParmJsonV2() { + ContentType contentType = Formats.parseHeaderAndQueryParm("application/json;version=2", null); - @Test - void testParseNullNull() - { - assertThrows(FormattingException.class, () -> Formats.parseHeaderAndQueryParm(null, null)); - } + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + Map parameters = contentType.getParameters(); + assertNotNull(parameters); + assertFalse(parameters.isEmpty()); + assertTrue(parameters.containsKey("version")); + assertEquals("2", parameters.get("version")); + } - @Test - void testParseEmptyHeader(){ + @Test + void testParseNullNull() { + assertThrows(FormattingException.class, () -> Formats.parseHeaderAndQueryParm(null, null)); + } - ContentType contentType =Formats.parseHeaderAndQueryParm("", "json"); + @Test + void testParseEmptyHeader() { - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - Map parameters = contentType.getParameters(); - assertTrue(parameters == null || parameters.isEmpty()); - } + ContentType contentType = Formats.parseHeaderAndQueryParm("", "json"); - @Test - void testParseNullHeader(){ + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + Map parameters = contentType.getParameters(); + assertTrue(parameters == null || parameters.isEmpty()); + } - ContentType contentType =Formats.parseHeaderAndQueryParm(null, "json"); + @Test + void testParseNullHeader() { - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - Map parameters = contentType.getParameters(); - assertTrue(parameters == null || parameters.isEmpty()); + ContentType contentType = Formats.parseHeaderAndQueryParm(null, "json"); + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + Map parameters = contentType.getParameters(); + assertTrue(parameters == null || parameters.isEmpty()); - - } - - - @Test - void testParseHeaderAndQueryParmXML(){ - assertThrows(FormattingException.class, () -> { - Formats.parseHeaderAndQueryParm(null, null); - }); - - ContentType contentType =Formats.parseHeaderAndQueryParm("application/xml", null); - - assertNotNull(contentType); - assertEquals("application/xml", contentType.getType()); - Map parameters = contentType.getParameters(); - assertTrue(parameters == null || parameters.isEmpty()); - - - contentType =Formats.parseHeaderAndQueryParm("application/xml;version=2", null); - - assertNotNull(contentType); - assertEquals("application/xml", contentType.getType()); - parameters = contentType.getParameters(); - assertNotNull(parameters); - assertFalse(parameters.isEmpty()); - assertTrue(parameters.containsKey("version")); - assertEquals("2", parameters.get("version")); - - } - - @Test - void testParseBoth(){ - assertThrows(FormattingException.class, () -> { - Formats.parseHeaderAndQueryParm("application/json", "json"); - }); - - - } - - @Test - void testParseBothv2(){ - assertThrows(FormattingException.class, () -> { - Formats.parseHeaderAndQueryParm("application/json;version=2", "json"); - }); - - - - } - - @Test - void testParseHeader(){ - ContentType contentType; - - contentType = Formats.parseHeader("application/json"); - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - - contentType = Formats.parseHeader("application/json;version=2"); - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - - assertThrows(FormattingException.class, () -> Formats.parseHeader(null)); - - assertThrows(FormattingException.class, () -> Formats.parseHeader("")); - - } - - @EnumSource(ParseQueryParamTest.class) - @ParameterizedTest - void testParseQueryParam(ParseQueryParamTest test){ - ContentType contentType = Formats.parseQueryParam(test._contentType, test._class); - assertEquals(test._expectedType, contentType); - } - - @Test - void testParseHeaderAndQueryParmJSONv2WithCharset() { - ContentType contentType = Formats.parseHeaderAndQueryParm("application/json;version=2; charset=utf-8", null); - - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - } - - @Test - void testParseHeaderJSONv2WithCharset() { - ContentType contentType = Formats.parseHeader("application/json;version=2; charset=utf-8"); - - assertNotNull(contentType); - assertEquals("application/json", contentType.getType()); - } - - @Test - void testParseHeaderFromFirefox() - { - //The following header comes from firefox - ContentType contentType = Formats.parseHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); - assertNotNull(contentType); - assertEquals(Formats.XML, contentType.toString()); - } - - @EnumSource(ParseHeaderClassAliasTest.class) - @ParameterizedTest - void testParseHeaderWithClass(ParseHeaderClassAliasTest test) - { - ContentType contentType = Formats.parseHeader(test._contentType, test._class); - assertNotNull(contentType); - assertEquals(test._expectedType, contentType.toString()); - } - - enum ParseHeaderClassAliasTest - { - COUNTY_DEFAULT(County.class, Formats.DEFAULT, Formats.JSONV2), - COUNTY_JSON(County.class, Formats.JSON, Formats.JSONV2), - COUNTY_JSONV2(County.class, Formats.JSONV2, Formats.JSONV2), - STATE_DEFAULT(State.class, Formats.DEFAULT, Formats.JSONV2), - STATE_JSON(State.class, Formats.JSON, Formats.JSONV2), - STATE_JSONV2(State.class, Formats.JSONV2, Formats.JSONV2), - OFFICE_DEFAULT(Office.class, Formats.JSONV2, Formats.JSONV2), - OFFICE_JSON(Office.class, Formats.JSONV2, Formats.JSONV2), - OFFICE_JSONV2(Office.class, Formats.JSONV2, Formats.JSONV2), - OFFICE_XML(Office.class, Formats.XML, Formats.XMLV2), - OFFICE_XMLV2(Office.class, Formats.XMLV2, Formats.XMLV2), - BLOB_DEFAULT(Blob.class, Formats.DEFAULT, Formats.JSONV2), - BLOB_JSON(Blob.class, Formats.JSON, Formats.JSONV2), - BLOB_JSONV2(Blob.class, Formats.JSONV2, Formats.JSONV2), - BLOBS_DEFAULT(Blobs.class, Formats.DEFAULT, Formats.JSONV2), - BLOBS_JSON(Blobs.class, Formats.JSON, Formats.JSONV2), - BLOBS_JSONV2(Blobs.class, Formats.JSONV2, Formats.JSONV2), - CLOB_DEFAULT(Clob.class, Formats.DEFAULT, Formats.JSONV2), - CLOB_JSON(Clob.class, Formats.JSON, Formats.JSONV2), - CLOB_JSONV1(Clob.class, Formats.JSONV1, Formats.JSONV1), - CLOB_JSONV2(Clob.class, Formats.JSONV2, Formats.JSONV2), - CLOB_XML(Clob.class, Formats.XML, Formats.XMLV2), - CLOB_XMLV2(Clob.class, Formats.XMLV2, Formats.XMLV2), - CLOBS_DEFAULT(Clobs.class, Formats.DEFAULT, Formats.JSONV2), - CLOBS_JSON(Clobs.class, Formats.JSON, Formats.JSONV2), - CLOBS_JSONV2(Clobs.class, Formats.JSONV2, Formats.JSONV2), - BASIN_DEFAULT(Basin.class, Formats.DEFAULT, Formats.NAMED_PGJSON), - BASIN_PGJSON(Basin.class, Formats.PGJSON, Formats.PGJSON), - BASIN_NAMED_PGJSON(Basin.class, Formats.NAMED_PGJSON, Formats.NAMED_PGJSON), - PROJECT_JSONV1(Project.class, Formats.JSONV1, Formats.JSONV1), - PROJECT_JSON(Project.class, Formats.JSON, Formats.JSONV1), - ; - - final Class _class; - final String _contentType; - final String _expectedType; - - ParseHeaderClassAliasTest(Class aClass, String contentType, String expectedType) - { - _class = aClass; - _contentType = contentType; - _expectedType = expectedType; - } - } - - enum ParseQueryParamTest - { - JSON(null, "json", new ContentType(Formats.JSON)), - NULL(null, null, null), - EMPTY(null, "", null), - OFFICE(Office.class, "json", new ContentType(Formats.JSONV2)), - ; - final Class _class; - final String _contentType; - final ContentType _expectedType; - - ParseQueryParamTest(Class aClass, String contentType, ContentType expectedType) - { - _class = aClass; - _contentType = contentType; - _expectedType = expectedType; - } - } + } + + + @Test + void testParseHeaderAndQueryParmXml() { + assertThrows(FormattingException.class, () -> { + Formats.parseHeaderAndQueryParm(null, null); + }); + + ContentType contentType = Formats.parseHeaderAndQueryParm("application/xml", null); + + assertNotNull(contentType); + assertEquals("application/xml", contentType.getType()); + Map parameters = contentType.getParameters(); + assertTrue(parameters == null || parameters.isEmpty()); + + + contentType = Formats.parseHeaderAndQueryParm("application/xml;version=2", null); + + assertNotNull(contentType); + assertEquals("application/xml", contentType.getType()); + parameters = contentType.getParameters(); + assertNotNull(parameters); + assertFalse(parameters.isEmpty()); + assertTrue(parameters.containsKey("version")); + assertEquals("2", parameters.get("version")); + + } + + @Test + void testParseBoth() { + assertThrows(FormattingException.class, () -> { + Formats.parseHeaderAndQueryParm("application/json", "json"); + }); + } + + @Test + void testParseBothv2() { + assertThrows(FormattingException.class, () -> { + Formats.parseHeaderAndQueryParm("application/json;version=2", "json"); + }); + + } + + @Test + void testParseHeader() { + ContentType contentType; + + contentType = Formats.parseHeader("application/json"); + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + + contentType = Formats.parseHeader("application/json;version=2"); + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + + assertThrows(FormattingException.class, () -> Formats.parseHeader(null)); + assertThrows(FormattingException.class, () -> Formats.parseHeader("")); + + } + + @EnumSource(ParseQueryParamTest.class) + @ParameterizedTest + void testParseQueryParam(ParseQueryParamTest test) { + ContentType contentType = Formats.parseQueryParam(test.contentType, test.klass); + assertEquals(test.expectedType, contentType); + } + + @Test + void testParseHeaderAndQueryParmJsonV2WithCharset() { + ContentType contentType = Formats.parseHeaderAndQueryParm("application/json;version=2; charset=utf-8", null); + + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + } + + @Test + void testParseHeaderJsonV2WithCharset() { + ContentType contentType = Formats.parseHeader("application/json;version=2; charset=utf-8"); + + assertNotNull(contentType); + assertEquals("application/json", contentType.getType()); + } + + @Test + void testParseHeaderFromFirefox() { + //The following header comes from firefox + ContentType contentType = Formats.parseHeader(FIREFOX_HEADER); + assertNotNull(contentType); + assertEquals(Formats.XML, contentType.toString()); + } + + @EnumSource(ParseHeaderClassAliasTest.class) + @ParameterizedTest + void testParseHeaderWithClass(ParseHeaderClassAliasTest test) { + ContentType contentType = Formats.parseHeader(test.contentType, test.klass); + assertNotNull(contentType); + assertEquals(test.expectedType, contentType.toString()); + } + + enum ParseHeaderClassAliasTest { + COUNTY_DEFAULT(County.class, Formats.DEFAULT, Formats.JSONV2), + COUNTY_JSON(County.class, Formats.JSON, Formats.JSONV2), + COUNTY_JSONV2(County.class, Formats.JSONV2, Formats.JSONV2), + STATE_DEFAULT(State.class, Formats.DEFAULT, Formats.JSONV2), + STATE_JSON(State.class, Formats.JSON, Formats.JSONV2), + STATE_JSONV2(State.class, Formats.JSONV2, Formats.JSONV2), + OFFICE_DEFAULT(Office.class, Formats.JSONV2, Formats.JSONV2), + OFFICE_JSON(Office.class, Formats.JSONV2, Formats.JSONV2), + OFFICE_JSONV2(Office.class, Formats.JSONV2, Formats.JSONV2), + OFFICE_XML(Office.class, Formats.XML, Formats.XMLV2), + OFFICE_XMLV2(Office.class, Formats.XMLV2, Formats.XMLV2), + BLOB_DEFAULT(Blob.class, Formats.DEFAULT, Formats.JSONV2), + BLOB_JSON(Blob.class, Formats.JSON, Formats.JSONV2), + BLOB_JSONV2(Blob.class, Formats.JSONV2, Formats.JSONV2), + BLOBS_DEFAULT(Blobs.class, Formats.DEFAULT, Formats.JSONV2), + BLOBS_JSON(Blobs.class, Formats.JSON, Formats.JSONV2), + BLOBS_JSONV2(Blobs.class, Formats.JSONV2, Formats.JSONV2), + CLOB_DEFAULT(Clob.class, Formats.DEFAULT, Formats.JSONV2), + CLOB_JSON(Clob.class, Formats.JSON, Formats.JSONV2), + CLOB_JSONV1(Clob.class, Formats.JSONV1, Formats.JSONV1), + CLOB_JSONV2(Clob.class, Formats.JSONV2, Formats.JSONV2), + CLOB_XML(Clob.class, Formats.XML, Formats.XMLV2), + CLOB_XMLV2(Clob.class, Formats.XMLV2, Formats.XMLV2), + CLOBS_DEFAULT(Clobs.class, Formats.DEFAULT, Formats.JSONV2), + CLOBS_JSON(Clobs.class, Formats.JSON, Formats.JSONV2), + CLOBS_JSONV2(Clobs.class, Formats.JSONV2, Formats.JSONV2), + BASIN_DEFAULT(Basin.class, Formats.DEFAULT, Formats.NAMED_PGJSON), + BASIN_PGJSON(Basin.class, Formats.PGJSON, Formats.PGJSON), + BASIN_NAMED_PGJSON(Basin.class, Formats.NAMED_PGJSON, Formats.NAMED_PGJSON), + PROJECT_JSONV1(Project.class, Formats.JSONV1, Formats.JSONV1), + PROJECT_JSON(Project.class, Formats.JSON, Formats.JSONV1), + ; + + final Class klass; + final String contentType; + final String expectedType; + + ParseHeaderClassAliasTest(Class theClass, String contentType, String expectedType) { + klass = theClass; + this.contentType = contentType; + this.expectedType = expectedType; + } + } + + enum ParseQueryParamTest { + JSON(null, "json", new ContentType(Formats.JSON)), + NULL(null, null, null), + EMPTY(null, "", null), + OFFICE(Office.class, "json", new ContentType(Formats.JSONV2)), + ; + final Class klass; + final String contentType; + final ContentType expectedType; + + ParseQueryParamTest(Class theClass, String contentType, ContentType expectedType) { + klass = theClass; + this.contentType = contentType; + this.expectedType = expectedType; + } + } } \ No newline at end of file From 2500b3306351499a3477b9c76d2d93e70a513761 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:11:03 -0700 Subject: [PATCH 11/16] Switching from implements to extends b/c the interface was changed. --- .../cwms/cda/data/dto/project/ProjectChildLocations.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java index 9c34e5a89..576a52788 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java @@ -28,7 +28,6 @@ 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; @@ -45,7 +44,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) @FormattableWith(contentType = Formats.JSONV2, aliases = {Formats.JSON}, formatter = JsonV2.class) -public class ProjectChildLocations implements CwmsDTOBase { +public class ProjectChildLocations extends CwmsDTOBase { private final CwmsId project; @@ -64,11 +63,6 @@ private ProjectChildLocations(Builder builder) { this.gates = builder.gates; } - @Override - public void validate() throws FieldException { - - } - public CwmsId getProject() { return project; } From 136e14a80bb84beee29a0cc0b721717028000a3f Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:14:16 -0700 Subject: [PATCH 12/16] Restored delete-mode because it started being used by Basin. --- cwms-data-api/src/main/java/cwms/cda/api/Controllers.java | 1 + 1 file changed, 1 insertion(+) 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 c65e1834f..668732f01 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 @@ -160,6 +160,7 @@ 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 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"; From 68033583a6c18cee81d34eaf97944d1c914da333 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:52:13 -0700 Subject: [PATCH 13/16] Changing url for project child locations. --- cwms-data-api/src/main/java/cwms/cda/ApiServlet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b1852699e..0309809fb 100644 --- a/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java +++ b/cwms-data-api/src/main/java/cwms/cda/ApiServlet.java @@ -520,7 +520,7 @@ protected void configureRoutes() { cdaCrudCache(virtualOutletPath, new VirtualOutletController(metrics), requiredRoles, 1, TimeUnit.DAYS); post(virtualOutletCreatePath, new VirtualOutletCreateController(metrics)); - get("/projects/child-locations/", new ProjectChildLocationHandler(metrics)); + get("/projects/locations/", new ProjectChildLocationHandler(metrics)); cdaCrudCache(format("/projects/{%s}", Controllers.NAME), new ProjectController(metrics), requiredRoles,5, TimeUnit.MINUTES); cdaCrudCache(format("/properties/{%s}", Controllers.NAME), From a61b5ce858f344bb47724b9dc4377250c5f3027b Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:57:15 -0700 Subject: [PATCH 14/16] Changed format to include ProjectKind constants --- .../dao/project/ProjectChildLocationDao.java | 30 ++-- .../dto/project/LocationsWithProjectKind.java | 65 +++++++++ .../dto/project/ProjectChildLocations.java | 129 ++++++++++++------ .../project/LocationsWithProjectKindTest.java | 51 +++++++ .../project/ProjectChildLocationsTest.java | 20 +-- .../cwms/cda/data/dto/location_with_kind.json | 12 ++ .../cwms/cda/data/dto/project_children.json | 81 ++++++----- .../cda/data/dto/project_children_list.json | 19 ++- 8 files changed, 296 insertions(+), 111 deletions(-) create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/project/LocationsWithProjectKind.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/project/LocationsWithProjectKindTest.java create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/location_with_kind.json diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java index 8d97702cf..03309d8a3 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/project/ProjectChildLocationDao.java @@ -27,8 +27,11 @@ import cwms.cda.data.dao.JooqDao; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.project.ProjectChildLocations; - -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; import org.jooq.DSLContext; @@ -48,7 +51,8 @@ public List retrieveProjectChildLocations(String office, return retrieveProjectChildLocations(office, projLike, ProjectKind.getMatchingKinds(kindRegex)); } - private List retrieveProjectChildLocations(String office, String projLike, Set kinds) { + private List retrieveProjectChildLocations( + String office, String projLike, Set kinds) { Map builderMap = new LinkedHashMap<>(); // proj-id-> @@ -64,25 +68,7 @@ private List retrieveProjectChildLocations(String office, .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; - } + builder.withLocations(kind, locs); } } } diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/LocationsWithProjectKind.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/LocationsWithProjectKind.java new file mode 100644 index 000000000..7bb190311 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/LocationsWithProjectKind.java @@ -0,0 +1,65 @@ +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 com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dao.project.ProjectKind; +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.ArrayList; +import java.util.List; + +@JsonDeserialize(builder = LocationsWithProjectKind.Builder.class) +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) +@FormattableWith(contentType = Formats.JSONV2, aliases = {Formats.JSON}, formatter = JsonV2.class) +public class LocationsWithProjectKind { + ProjectKind kind; + List locations; + + private LocationsWithProjectKind(Builder builder) { + kind = builder.kind; + locations = builder.locations; + } + + public ProjectKind getKind() { + return kind; + } + + public List getLocations() { + return locations; + } + + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) + public static class Builder { + private ProjectKind kind; + private List locations = new ArrayList<>(); + + public Builder() { + + } + + public Builder withKind(ProjectKind kind) { + this.kind = kind; + return this; + } + + public Builder withLocations(List locations) { + if (locations == null) { + this.locations = null; + } else { + this.locations = new ArrayList<>(locations); + } + return this; + } + + public LocationsWithProjectKind build() { + return new LocationsWithProjectKind(this); + } + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java index 576a52788..7c8303bf1 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/project/ProjectChildLocations.java @@ -24,18 +24,23 @@ package cwms.cda.data.dto.project; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import cwms.cda.data.dao.project.ProjectKind; 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.ArrayList; +import java.util.EnumMap; import java.util.List; -import org.jetbrains.annotations.Nullable; +import java.util.Map; /** * This class holds a project and lists of the project child locations by kind. @@ -48,92 +53,132 @@ public class ProjectChildLocations extends 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; + @JsonIgnore + private EnumMap> locationsByKind; private ProjectChildLocations(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; + + if (builder.locationsByKind != null) { + this.locationsByKind = new EnumMap<>(builder.locationsByKind); + } } public CwmsId getProject() { return project; } + @JsonProperty + public List getLocationsByKind() { + List result = null; + + if (locationsByKind != null && !locationsByKind.isEmpty()) { + result = new ArrayList<>(); + + for (Map.Entry> entry : locationsByKind.entrySet()) { + result.add(new LocationsWithProjectKind.Builder() + .withKind(entry.getKey()) + .withLocations(entry.getValue()).build()); + } + } + + return result; + } + + public List getLocations(ProjectKind kind) { + if (locationsByKind != null && !locationsByKind.isEmpty()) { + return locationsByKind.get(kind); + } + return null; + } + + @JsonIgnore public List getEmbankments() { - return embankments; + return getLocations(ProjectKind.EMBANKMENT); } + @JsonIgnore public List getLocks() { - return locks; + return getLocations(ProjectKind.LOCK); } + @JsonIgnore public List getOutlets() { - return outlets; + return getLocations(ProjectKind.OUTLET); } + @JsonIgnore public List getTurbines() { - return turbines; + return getLocations(ProjectKind.TURBINE); } + @JsonIgnore public List getGates() { - return gates; + return getLocations(ProjectKind.GATE); } - + @JsonPOJOBuilder + @JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class) public static class Builder { private CwmsId project; - private List embankments; - private List locks; - private List outlets; - private List turbines; - private List gates; + + private EnumMap> locationsByKind; public Builder withProject(CwmsId project) { this.project = project; return this; } - public Builder withEmbankments(List embankments) { - this.embankments = wrapList(embankments); + public Builder withLocationsByKind(List locationsByKind) { + Builder retval = this; + if (locationsByKind != null) { + for (LocationsWithProjectKind item : locationsByKind) { + retval = retval.withLocations(item.getKind(), item.getLocations()); + } + } + + return retval; + } + + public Builder withLocations(ProjectKind kind, List locations) { + + if (locationsByKind == null) { + locationsByKind = new EnumMap<>(ProjectKind.class); + } + + if (locations != null) { + List locOfKind = locationsByKind.computeIfAbsent(kind, k -> new ArrayList<>()); + if (!locations.isEmpty()) { + locOfKind.addAll(locations); + } + } + return this; } + @JsonIgnore + public Builder withEmbankments(List embankments) { + return withLocations(ProjectKind.EMBANKMENT, embankments); + } + @JsonIgnore public Builder withLocks(List locks) { - this.locks = wrapList(locks); - return this; + return withLocations(ProjectKind.LOCK, locks); } + @JsonIgnore public Builder withOutlets(List outlets) { - this.outlets = outlets; - return this; + return withLocations(ProjectKind.OUTLET, outlets); } + @JsonIgnore public Builder withTurbines(List turbines) { - this.turbines = wrapList(turbines); - return this; + return withLocations(ProjectKind.TURBINE, turbines); } + @JsonIgnore 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; + return withLocations(ProjectKind.GATE, gates); } public ProjectChildLocations build() { diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/LocationsWithProjectKindTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/LocationsWithProjectKindTest.java new file mode 100644 index 000000000..72492d461 --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/LocationsWithProjectKindTest.java @@ -0,0 +1,51 @@ +package cwms.cda.data.dto.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import cwms.cda.data.dao.project.ProjectKind; +import cwms.cda.data.dto.CwmsId; +import cwms.cda.formatters.json.JsonV2; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + + +public class LocationsWithProjectKindTest { + + @Test + void test_serialize() throws JsonProcessingException { + LocationsWithProjectKind.Builder builder = new LocationsWithProjectKind.Builder(); + builder.withKind(ProjectKind.EMBANKMENT); + List locs = new ArrayList<>(); + locs.add(new CwmsId.Builder().withName("Emb1").withOfficeId("SPK").build()); + locs.add(new CwmsId.Builder().withName("Emb2").withOfficeId("SPK").build()); + builder.withLocations(locs); + LocationsWithProjectKind locWithKind = builder.build(); + + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + String json = objectMapper.writeValueAsString(locWithKind); + assertNotNull(json); + + } + + @Test + void test_deserialize() throws IOException { + + InputStream stream = LocationsWithProjectKind.class.getClassLoader().getResourceAsStream( + "cwms/cda/data/dto/location_with_kind.json"); + assertNotNull(stream); + ObjectMapper objectMapper = JsonV2.buildObjectMapper(); + LocationsWithProjectKind locationsWithKind = objectMapper.readValue(stream, LocationsWithProjectKind.class); + assertNotNull(locationsWithKind); + assertEquals(ProjectKind.EMBANKMENT, locationsWithKind.getKind()); + assertEquals("TestEmbankment1", locationsWithKind.getLocations().get(0).getName()); + assertEquals("TestEmbankment2", locationsWithKind.getLocations().get(1).getName()); + + } + +} \ No newline at end of file diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java index 3ac794246..9681c1b69 100644 --- a/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/project/ProjectChildLocationsTest.java @@ -24,7 +24,11 @@ package cwms.cda.data.dto.project; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,7 +49,7 @@ class ProjectChildLocationsTest { public static final String OFFICE = "SPK"; @Test - void test_ctor(){ + void test_ctor() { CwmsId proj = buildId(OFFICE, "TestProject"); List embanks = new ArrayList<>(); @@ -82,7 +86,6 @@ void test_ctor(){ String json = Formats.format(new ContentType(Formats.JSON), projectChildLocations); - assertNotNull(json); } @@ -119,7 +122,7 @@ private ProjectChildLocations buildTestProjectChildLocations(String office, Stri } @Test - void test_list_serialization(){ + void test_list_serialization() { List list = new ArrayList<>(); list.add(buildTestProjectChildLocations(OFFICE, "TestProject1")); @@ -140,12 +143,12 @@ 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()); } - @Test void testDeserialize() throws IOException { @@ -218,7 +221,8 @@ void testRoundtrip() throws IOException { assertProjectChildLocationsEqual(projectChildLocations1, projectChildLocations2); } - private static void assertProjectChildLocationsEqual(ProjectChildLocations projectChildLocations1, ProjectChildLocations projectChildLocations2) { + private static void assertProjectChildLocationsEqual(ProjectChildLocations projectChildLocations1, + ProjectChildLocations projectChildLocations2) { assertAll("ProjectChildLocations", () -> assertCwmsIdEqual(projectChildLocations1.getProject(), projectChildLocations2.getProject()), () -> assertListEqual(projectChildLocations1.getEmbankments(), projectChildLocations2.getEmbankments()), @@ -238,7 +242,7 @@ private static void assertCwmsIdEqual(CwmsId project1, CwmsId project2) { private static void assertListEqual(List locks1, List locks2) { assertEquals(locks1.size(), locks2.size()); - for(int i = 0; i < locks1.size(); i++){ + for (int i = 0; i < locks1.size(); i++) { assertCwmsIdEqual(locks1.get(i), locks2.get(i)); } } diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/location_with_kind.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location_with_kind.json new file mode 100644 index 000000000..66bbcd981 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/location_with_kind.json @@ -0,0 +1,12 @@ +{ + "kind": "EMBANKMENT", + "locations": [ + { + "name": "TestEmbankment1" + }, + { + "office-id": "SPK", + "name": "TestEmbankment2" + } + ] +} \ 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 index 3d677d47e..93a35fb16 100644 --- 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 @@ -3,46 +3,63 @@ "office-id": "SPK", "name": "TestProject" }, - "embankments": [ + "locations-by-kind": [ { - "office-id": "SPK", - "name": "TestEmbankment1" + "kind": "EMBANKMENT", + "locations": [ + { + "office-id": "SPK", + "name": "TestEmbankment1" + }, + { + "office-id": "SPK", + "name": "TestEmbankment2" + } + ] }, { - "office-id": "SPK", - "name": "TestEmbankment2" - } - ], - "locks": [ - { - "office-id": "SPK", - "name": "TestLock1" + "kind": "LOCK", + "locations": [ + { + "office-id": "SPK", + "name": "TestLock1" + }, + { + "office-id": "SPK", + "name": "TestLock2" + }, + { + "office-id": "SPK", + "name": "TestLock3" + } + ] }, { - "office-id": "SPK", - "name": "TestLock2" + "kind": "OUTLET", + "locations": [ + { + "office-id": "SPK", + "name": "TestOutlet1" + } + ] }, { - "office-id": "SPK", - "name": "TestLock3" - } - ], - "outlets": [ - { - "office-id": "SPK", - "name": "TestOutlet1" - } - ], - "turbines": [ - { - "office-id": "SPK", - "name": "TestTurbine1" - } - ], - "gates": [ + "kind": "TURBINE", + "locations": [ + { + "office-id": "SPK", + "name": "TestTurbine1" + } + ] + }, { - "office-id": "SPK", - "name": "TestGate1" + "kind": "GATE", + "locations": [ + { + "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 index d20a26db9..d497e21b1 100644 --- 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 @@ -4,14 +4,19 @@ "office-id": "SPK", "name": "TestProject1" }, - "embankments": [ + "locations-by-kind": [ { - "office-id": "SPK", - "name": "TestEmbankment1" - }, - { - "office-id": "SPK", - "name": "TestEmbankment2" + "kind": "EMBANKMENT", + "locations": [ + { + "office-id": "SPK", + "name": "TestEmbankment1" + }, + { + "office-id": "SPK", + "name": "TestEmbankment2" + } + ] } ] } From 1ccf0d21c690e3a3653da85d3e86b9d678b80f7e Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Tue, 6 Aug 2024 13:32:34 -0700 Subject: [PATCH 15/16] Fixed missing IT --- .../cwms/cda/api/project/ProjectChildLocationHandlerIT.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java index d618d56bf..f26c5db6c 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java @@ -121,7 +121,7 @@ void test_get_embankment() throws Exception { .when() .redirects().follow(true) .redirects().max(3) - .get("/projects/child-locations/") + .get("/projects/locations/") .then() .log().ifValidationFails(LogDetail.ALL, true) .assertThat() From de5206a1ee8d26c6c1405ce2bc2be05e521a14fc Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:01:19 -0700 Subject: [PATCH 16/16] Fixed missing IT --- .../ProjectChildLocationHandlerIT.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java index f26c5db6c..73a063741 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/project/ProjectChildLocationHandlerIT.java @@ -39,6 +39,7 @@ 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.dao.project.ProjectKind; import cwms.cda.data.dto.CwmsId; import cwms.cda.data.dto.Location; import cwms.cda.data.dto.LookupType; @@ -113,30 +114,29 @@ void test_get_embankment() throws Exception { 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/locations/") - .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)) + .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/locations/") + .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].locations-by-kind.size()", is(1)) + .body("[0].locations-by-kind[0].kind", equalToIgnoringCase(String.valueOf(ProjectKind.EMBANKMENT))) + .body("[0].locations-by-kind[0].locations.size()", is(2)) + .body("[0].locations-by-kind[0].locations[0].name", equalToIgnoringCase(locName1)) ; } finally { - embankmentDao.deleteEmbankment(embank.getLocation().getName(), OFFICE, DeleteRule.DELETE_ALL ); + 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); } @@ -144,7 +144,7 @@ void test_get_embankment() throws Exception { } - private Embankment buildTestEmbankment(Location location, CwmsId projId) { + private Embankment buildTestEmbankment(Location location, CwmsId projId) { return new Embankment.Builder() .withLocation(location) .withMaxHeight(5.0) @@ -195,11 +195,11 @@ private Location buildTestLocation(String office, String name) { } public static Project buildTestProject(String office, String prjId) { - Location pbLoc = new Location.Builder(office,prjId + "-PB") + Location pbLoc = new Location.Builder(office, prjId + "-PB") .withTimeZoneName(ZoneId.of("UTC")) .withActive(null) .build(); - Location ngLoc = new Location.Builder(office,prjId + "-NG") + Location ngLoc = new Location.Builder(office, prjId + "-NG") .withTimeZoneName(ZoneId.of("UTC")) .withActive(null) .build();