From df86a851a57b071397ed4e259568b4b3db5a3a41 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:47:39 -0700 Subject: [PATCH 1/8] Implementing Project controller and dao --- .../java/cwms/cda/api/ProjectController.java | 303 +++++++++++++++++ .../java/cwms/cda/data/dao/ProjectDao.java | 310 ++++++++++++++++++ .../main/java/cwms/cda/data/dto/Projects.java | 122 +++++++ .../cwms/cda/api/ProjectControllerIT.java | 249 ++++++++++++++ .../java/cwms/cda/data/dto/ProjectsTest.java | 123 +++++++ .../test/resources/cwms/cda/api/project.json | 23 ++ .../resources/cwms/cda/api/project_new.json | 5 + 7 files changed, 1135 insertions(+) create mode 100644 cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java create mode 100644 cwms-data-api/src/main/java/cwms/cda/data/dto/Projects.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java create mode 100644 cwms-data-api/src/test/java/cwms/cda/data/dto/ProjectsTest.java create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/project.json create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/project_new.json diff --git a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java new file mode 100644 index 000000000..c45447eee --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java @@ -0,0 +1,303 @@ +package cwms.cda.api; + +import static com.codahale.metrics.MetricRegistry.name; +import static cwms.cda.api.Controllers.CREATE; +import static cwms.cda.api.Controllers.DELETE; +import static cwms.cda.api.Controllers.GET_ALL; +import static cwms.cda.api.Controllers.GET_ONE; +import static cwms.cda.api.Controllers.ID_MASK; +import static cwms.cda.api.Controllers.METHOD; +import static cwms.cda.api.Controllers.NAME; +import static cwms.cda.api.Controllers.OFFICE; +import static cwms.cda.api.Controllers.PAGE; +import static cwms.cda.api.Controllers.PAGE_SIZE; +import static cwms.cda.api.Controllers.RESULTS; +import static cwms.cda.api.Controllers.SIZE; +import static cwms.cda.api.Controllers.STATUS_200; +import static cwms.cda.api.Controllers.STATUS_404; +import static cwms.cda.api.Controllers.STATUS_501; +import static cwms.cda.api.Controllers.UPDATE; +import static cwms.cda.api.Controllers.queryParamAsClass; +import static cwms.cda.api.Controllers.requiredParam; +import static cwms.cda.data.dao.JooqDao.getDslContext; + +import com.codahale.metrics.Histogram; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import cwms.cda.api.errors.CdaError; +import cwms.cda.data.dao.DeleteRule; +import cwms.cda.data.dao.JooqDao; +import cwms.cda.data.dao.ProjectDao; +import cwms.cda.data.dto.Project; +import cwms.cda.data.dto.Projects; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.FormattingException; +import io.javalin.apibuilder.CrudHandler; +import io.javalin.core.util.Header; +import io.javalin.http.Context; +import io.javalin.plugin.openapi.annotations.HttpMethod; +import io.javalin.plugin.openapi.annotations.OpenApi; +import io.javalin.plugin.openapi.annotations.OpenApiContent; +import io.javalin.plugin.openapi.annotations.OpenApiParam; +import io.javalin.plugin.openapi.annotations.OpenApiRequestBody; +import io.javalin.plugin.openapi.annotations.OpenApiResponse; +import java.util.logging.Logger; +import javax.servlet.http.HttpServletResponse; +import org.jetbrains.annotations.NotNull; +import org.jooq.DSLContext; + +public class ProjectController implements CrudHandler { + public static final Logger logger = Logger.getLogger(ProjectController.class.getName()); + private static final int DEFAULT_PAGE_SIZE = 100; + public static final String TAG = "Projects"; + + private final MetricRegistry metrics; + + private final Histogram requestResultSize; + + public ProjectController(MetricRegistry metrics) { + this.metrics = metrics; + String className = this.getClass().getName(); + + requestResultSize = this.metrics.histogram((name(className, RESULTS, SIZE))); + } + + private Timer.Context markAndTime(String subject) { + return Controllers.markAndTime(metrics, getClass().getName(), subject); + } + + @OpenApi(queryParams = { + @OpenApiParam(name = OFFICE, description = "Specifies the owning office of the data" + + " in the response. If this field is not specified, matching items from all" + + " offices shall be returned."), + @OpenApiParam(name = ID_MASK, description = "Project Id mask."), + @OpenApiParam(name = PAGE, + description = "This end point can return a lot of data, this identifies where" + + " in the request you are. This is an opaque value, and can be" + + " obtained from the 'next-page' value in the response." + ), + @OpenApiParam(name = PAGE_SIZE, type = Integer.class, + description = "How many entries per page returned. " + + "Default " + DEFAULT_PAGE_SIZE + "." + ),}, + responses = { + @OpenApiResponse(status = STATUS_200, content = { + @OpenApiContent(type = Formats.JSON, from = Projects.class)}), + @OpenApiResponse(status = STATUS_404, description = "Based on the combination of" + + " inputs provided the projects were not found."), + @OpenApiResponse(status = STATUS_501, description = "request format is not" + + " implemented")}, + description = "Returns Projects Data", + tags = {TAG}) + @Override + public void getAll(@NotNull Context ctx) { + try (final Timer.Context ignored = markAndTime(GET_ALL)) { + DSLContext dsl = getDslContext(ctx); + + ProjectDao dao = new ProjectDao(dsl); + String office = ctx.queryParam(OFFICE); + + String projectIdMask = ctx.queryParam(ID_MASK); + + String cursor = queryParamAsClass(ctx, new String[]{PAGE}, + String.class, "", metrics, name(ProjectController.class.getName(), + GET_ALL)); + + int pageSize = queryParamAsClass(ctx, new String[]{PAGE_SIZE}, + Integer.class, DEFAULT_PAGE_SIZE, metrics, + name(ProjectController.class.getName(), GET_ALL)); + + Projects projects = dao.retrieveProjects(cursor, pageSize, projectIdMask, office); + + ContentType contentType = getContentType(ctx); + ctx.contentType(contentType.toString()); + String serialized = Formats.format(contentType, projects); + ctx.result(serialized); + ctx.status(HttpServletResponse.SC_OK); + requestResultSize.update(serialized.length()); + + } + + } + + private static @NotNull ContentType getContentType(Context ctx) { + String formatHeader = ctx.header(Header.ACCEPT) != null ? ctx.header(Header.ACCEPT) : Formats.JSON; + ContentType contentType = Formats.parseHeader(formatHeader); + if (contentType == null) { + throw new FormattingException("Format header could not be parsed"); + } + return contentType; + } + + @OpenApi( + pathParams = { + @OpenApiParam(name = NAME, required = true, description = "Specifies the" + + " project to be included in the response."), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the" + + " owning office of the Pool whose data is to be included in the" + + " response."), + }, + responses = { + @OpenApiResponse(status = STATUS_200, + content = { + @OpenApiContent(from = Project.class, type = Formats.JSON) + } + ), + @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " + + "inputs provided the Project was not found."), + @OpenApiResponse(status = STATUS_501, description = "request format is not " + + "implemented")}, + description = "Retrieves requested Project", tags = {"Projects"}) + @Override + public void getOne(@NotNull Context ctx, @NotNull String name) { + try (final Timer.Context ignored = markAndTime(GET_ONE)) { + DSLContext dsl = getDslContext(ctx); + + ProjectDao dao = new ProjectDao(dsl); + + // These are required + String office = requiredParam(ctx, OFFICE); + + Project project = dao.retrieveProject(office, name); + + if (project == null) { + CdaError re = new CdaError("Unable to find Project based on parameters given"); + logger.info(() -> { + String fullUrl = ctx.fullUrl(); + return re + System.lineSeparator() + "for request " + fullUrl; + }); + ctx.status(HttpServletResponse.SC_NOT_FOUND).json(re); + } else { + String formatHeader = ctx.header(Header.ACCEPT); + ContentType contentType = Formats.parseHeaderAndQueryParm(formatHeader, ""); + ctx.contentType(contentType.toString()); + + String result = Formats.format(contentType, project); + + ctx.result(result); + requestResultSize.update(result.length()); + + ctx.status(HttpServletResponse.SC_OK); + } + } + } + + @OpenApi( + description = "Create new Project", + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = Project.class, type = Formats.JSON) + }, + required = true + ), + method = HttpMethod.POST, + tags = {TAG} + ) + @Override + public void create(@NotNull Context ctx) { + try (Timer.Context ignored = markAndTime(CREATE)) { + DSLContext dsl = getDslContext(ctx); + + String reqContentType = ctx.req.getContentType(); + String formatHeader = reqContentType != null ? reqContentType : Formats.JSON; + ContentType contentType = Formats.parseHeader(formatHeader); + if (contentType == null) { + throw new FormattingException("Format header could not be parsed"); + } + Project project = Formats.parseContent(contentType, ctx.body(), Project.class); + project.validate(); + + ProjectDao dao = new ProjectDao(dsl); + + dao.create(project); + ctx.status(HttpServletResponse.SC_CREATED); + } + } + + @OpenApi( + description = "Updates a text timeseries", + pathParams = { + @OpenApiParam(name = NAME, description = "The id of the text timeseries to be updated"), + }, + requestBody = @OpenApiRequestBody( + content = { + @OpenApiContent(from = Project.class, type = Formats.JSON), + }, + required = true + ), + method = HttpMethod.PATCH, + tags = {TAG} + ) + @Override + public void update(@NotNull Context ctx, @NotNull String name) { + + try (Timer.Context ignored = markAndTime(UPDATE)) { + String reqContentType = ctx.req.getContentType(); + String formatHeader = reqContentType != null ? reqContentType : Formats.JSON; + ContentType contentType = Formats.parseHeader(formatHeader); + if (contentType == null) { + throw new FormattingException("Format header could not be parsed"); + } + Project project = Formats.parseContent(contentType, ctx.body(), Project.class); + project.validate(); + DSLContext dsl = getDslContext(ctx); + + ProjectDao dao = new ProjectDao(dsl); + dao.update(project); + } + } + + + @OpenApi( + description = "Deletes requested text timeseries id", + pathParams = { + @OpenApiParam(name = NAME, description = "The project identifier to be deleted"), + }, + queryParams = { + @OpenApiParam(name = OFFICE, required = true, description = "Specifies the " + + "owning office of the timeseries identifier to be deleted"), + @OpenApiParam(name = METHOD, type = JooqDao.DeleteMethod.class, + description = "Specifies the delete method used. " + + "Defaults to \"DELETE_KEY\"") + }, + method = HttpMethod.DELETE, + tags = {TAG} + ) + @Override + public void delete(@NotNull Context ctx, @NotNull String name) { + try (Timer.Context ignored = markAndTime(DELETE)) { + DSLContext dsl = getDslContext(ctx); + String office = requiredParam(ctx, OFFICE); + + JooqDao.DeleteMethod deleteMethod = ctx.queryParamAsClass(METHOD, JooqDao.DeleteMethod.class) + .getOrDefault(JooqDao.DeleteMethod.DELETE_KEY); + + ProjectDao dao = new ProjectDao(dsl); + dao.delete(office, name, getDeleteRule(deleteMethod)); + + ctx.status(HttpServletResponse.SC_NO_CONTENT); + } + } + + private static @NotNull DeleteRule getDeleteRule(JooqDao.DeleteMethod deleteMethod) { + DeleteRule deleteRule; + switch (deleteMethod) { + case DELETE_ALL: + deleteRule = DeleteRule.DELETE_ALL; + break; + case DELETE_DATA: + deleteRule = DeleteRule.DELETE_DATA; + break; + case DELETE_KEY: + deleteRule = DeleteRule.DELETE_KEY; + break; + default: + throw new IllegalArgumentException("Delete Method provided does not match accepted rule constants: " + + deleteMethod); + } + return deleteRule; + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java new file mode 100644 index 000000000..018cc5b43 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java @@ -0,0 +1,310 @@ +package cwms.cda.data.dao; + +import static org.jooq.impl.DSL.asterisk; +import static org.jooq.impl.DSL.count; +import static org.jooq.impl.DSL.noCondition; + +import cwms.cda.api.errors.NotFoundException; +import cwms.cda.data.dto.CwmsDTOPaginated; +import cwms.cda.data.dto.Project; +import cwms.cda.data.dto.Projects; +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jooq.Condition; +import org.jooq.DSLContext; +import org.jooq.Record1; +import org.jooq.SelectConditionStep; +import usace.cwms.db.dao.ifc.loc.LocationRefType; +import usace.cwms.db.dao.ifc.loc.LocationType; +import usace.cwms.db.dao.util.OracleTypeMap; +import usace.cwms.db.jooq.codegen.packages.CWMS_PROJECT_PACKAGE; +import usace.cwms.db.jooq.codegen.tables.AV_PROJECT; +import usace.cwms.db.jooq.codegen.udt.records.LOCATION_OBJ_T; +import usace.cwms.db.jooq.codegen.udt.records.LOCATION_REF_T; +import usace.cwms.db.jooq.codegen.udt.records.PROJECT_OBJ_T; +import usace.cwms.db.jooq.dao.util.LocationTypeUtil; + +public class ProjectDao extends JooqDao { + public ProjectDao(DSLContext dsl) { + super(dsl); + } + + public Project retrieveProject(String office, String projectId) { + + PROJECT_OBJ_T projectObjT = connectionResult(dsl, + c -> CWMS_PROJECT_PACKAGE.call_RETRIEVE_PROJECT( + getDslContext(c, office).configuration(), projectId, office) + ); + + return projectObjT == null ? null : buildProject(projectObjT); + } + + private Project buildProject(PROJECT_OBJ_T projectObjT) { + LOCATION_OBJ_T projectLocation = projectObjT.getPROJECT_LOCATION(); + LOCATION_REF_T locRef = projectLocation.getLOCATION_REF(); + String office = locRef.getOFFICE_ID(); + String id = getLocationId(locRef.getBASE_LOCATION_ID(), locRef.getSUB_LOCATION_ID()); + + String authorizingLaw = projectObjT.getAUTHORIZING_LAW(); + String bankFullCapacityDescription = projectObjT.getBANK_FULL_CAPACITY_DESCRIPTION(); + Timestamp costYear = projectObjT.getCOST_YEAR(); + String downstreamUrbanDescription = projectObjT.getDOWNSTREAM_URBAN_DESCRIPTION(); + BigDecimal federalCost = projectObjT.getFEDERAL_COST(); + String costUnitsId = projectObjT.getCOST_UNITS_ID(); + BigDecimal federalOandMCost = projectObjT.getFEDERAL_OM_COST(); + String hydropowerDescription = projectObjT.getHYDROPOWER_DESCRIPTION(); + LocationType neargageLocationType = + LocationTypeUtil.toLocationType(projectObjT.getNEAR_GAGE_LOCATION()); + LocationRefType nearLocRef = null; + if (neargageLocationType != null) { + nearLocRef = neargageLocationType.getLocationRef(); + } + BigDecimal nonFederalCost = projectObjT.getNONFEDERAL_COST(); + BigDecimal nonFederalOandMCost = projectObjT.getNONFEDERAL_OM_COST(); + String projectOwner = projectObjT.getPROJECT_OWNER(); + LocationType pumpbackLocationType = + LocationTypeUtil.toLocationType(projectObjT.getPUMP_BACK_LOCATION()); + LocationRefType pumpbackLocRef = null; + if (pumpbackLocationType != null) { + pumpbackLocRef = pumpbackLocationType.getLocationRef(); + } + String remarks = projectObjT.getREMARKS(); + String sedimentationDescription = projectObjT.getSEDIMENTATION_DESCRIPTION(); + Timestamp yieldTimeFrameEnd = projectObjT.getYIELD_TIME_FRAME_END(); + Timestamp yieldTimeFrameStart = projectObjT.getYIELD_TIME_FRAME_START(); + + Project.Builder builder = new Project.Builder() + .withOfficeId(office) + .withName(id) + .withAuthorizingLaw(authorizingLaw) + .withBankFullCapacityDesc(bankFullCapacityDescription) + .withHydropowerDesc(hydropowerDescription) + .withProjectRemarks(remarks) + .withSedimentationDesc(sedimentationDescription) + .withProjectOwner(projectOwner) + .withDownstreamUrbanDesc(downstreamUrbanDescription) + .withCostUnit(costUnitsId) + ; + + if (costYear != null) { + builder = builder.withCostYear(costYear.toInstant()); + } + + if (federalCost != null) { + builder = builder.withFederalCost(federalCost.doubleValue()); + } + + if (federalOandMCost != null) { + builder = builder.withFederalOAndMCost(federalOandMCost.doubleValue()); + } + + if (nearLocRef != null) { + builder = builder.withNearGageLocationId(nearLocRef.getOfficeId()) + .withNearGageLocationId(getLocationId(nearLocRef.getBaseLocationId(), nearLocRef.getSubLocationId())); + } + if (nonFederalCost != null) { + builder = builder.withNonFederalCost(nonFederalCost.doubleValue()); + } + + if (nonFederalOandMCost != null) { + builder = builder.withNonFederalOAndMCost(nonFederalOandMCost.doubleValue()); + } + + if (pumpbackLocRef != null) { + builder = builder.withPumpBackOfficeId(pumpbackLocRef.getOfficeId()) + .withPumpBackLocationId(getLocationId(pumpbackLocRef.getBaseLocationId(), pumpbackLocRef.getSubLocationId())); + } + + if (yieldTimeFrameEnd != null) { + builder = builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); + } + + if (yieldTimeFrameStart != null) { + builder = builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); + } + + return builder.build(); + + } + + private static Project buildProject(usace.cwms.db.jooq.codegen.tables.records.AV_PROJECT r) { + Project.Builder builder = new Project.Builder() + .withOfficeId(r.getOFFICE_ID()) + .withName(r.getPROJECT_ID()); + builder.withPumpBackLocationId(r.getPUMP_BACK_LOCATION_ID()); + builder.withNearGageLocationId(r.getNEAR_GAGE_LOCATION_ID()); + builder.withAuthorizingLaw(r.getAUTHORIZING_LAW()); + builder.withFederalCost(r.getFEDERAL_COST().doubleValue()); + builder.withNonFederalCost(r.getNONFEDERAL_COST().doubleValue()); + // TODO: This isn't fully built out yet. need to figure out where + // some of these fields come from +// builder.withFederalOAndMCost(r.getFEDERAL_OM_COST().doubleValue()); +// builder.withNonFederalOAndMCost(r.getNONFEDERAL_OM_COST().doubleValue()); + + return builder.build(); + } + + public static String getLocationId(String base, String sub) { + boolean hasSub = sub != null && !sub.isEmpty(); + return hasSub ? base + "-" + sub : base; + } + + public Projects retrieveProjects(String cursor, int pageSize, String projectIdMask, String office) { + + Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); + if (office != null) { + whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); + } + + String cursorOffice = null; + String cursorProjectId = null; + int total = 0; + if (cursor == null || cursor.isEmpty()) { + SelectConditionStep> count = + dsl.select(count(asterisk())) + .from(AV_PROJECT.AV_PROJECT) + .where(whereClause); + + total = count.fetchOne().value1(); + } else { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length == 4) { + cursorOffice = parts[0]; + cursorProjectId = parts[1]; + pageSize = Integer.parseInt(parts[2]); + total = Integer.parseInt(parts[3]); + } + } + + Condition pagingCondition = noCondition(); + if (cursorOffice != null || cursorProjectId != null) { + Condition inSameOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(cursorOffice) + .and(AV_PROJECT.AV_PROJECT.PROJECT_ID.gt(cursorProjectId)); + Condition nextOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.gt(cursorOffice); + pagingCondition = inSameOffice.or(nextOffice); + } + + List projs = dsl.selectFrom(AV_PROJECT.AV_PROJECT) + .where(whereClause.and(pagingCondition)) + .orderBy(AV_PROJECT.AV_PROJECT.OFFICE_ID, AV_PROJECT.AV_PROJECT.PROJECT_ID) + .limit(pageSize) + .fetch() + .map(r -> buildProject(r)); + + Projects.Builder builder = new Projects.Builder(cursor, pageSize, total); + builder.addAll(projs); + return builder.build(); + } + + public void create(Project project) { + boolean failIfExists = true; + String office = project.getOfficeId(); + + PROJECT_OBJ_T projectT = toProjectT(project); + connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + projectT, OracleTypeMap.formatBool(failIfExists))); + } + + public static LOCATION_REF_T toLocationRefT(String base, String sub, String office) { + return new LOCATION_REF_T(base, sub, office); + } + + + private static @NotNull LOCATION_REF_T getLocationRefT(String locationId, String office) { + String base; + String sub; + if (locationId == null) { + base = null; + sub = null; + } else { + int fieldIndex = locationId.indexOf("-"); + if (fieldIndex == -1) { + base = locationId; + sub = null; + } else { + base = locationId.substring(0, fieldIndex); + sub = locationId.substring(fieldIndex + 1); + } + } + + return toLocationRefT(base, sub, office); + } + + + public void store(Project project, boolean failIfExists) { + String office = project.getOfficeId(); + + PROJECT_OBJ_T projectT = toProjectT(project); + connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + projectT, OracleTypeMap.formatBool(failIfExists))); + + } + + public void update(Project project) { + String office = project.getOfficeId(); + Project existingProject = retrieveProject(office, project.getName()); + if (existingProject == null) { + throw new NotFoundException("Could not find project to update."); + } + + PROJECT_OBJ_T projectT = toProjectT(project); + connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + projectT, OracleTypeMap.formatBool(false))); + + } + + private PROJECT_OBJ_T toProjectT(Project project) { + LOCATION_OBJ_T projectLocation = new LOCATION_OBJ_T(); + projectLocation.setLOCATION_REF(getLocationRefT(project.getName(), project.getOfficeId())); + + LOCATION_OBJ_T pumpBackLocation = null; + if (project.getPumpBackLocationId() != null && project.getPumpBackOfficeId() != null) { + pumpBackLocation = new LOCATION_OBJ_T(); + pumpBackLocation.setLOCATION_REF(getLocationRefT(project.getPumpBackLocationId(), + project.getPumpBackOfficeId())); + } + + + LOCATION_OBJ_T nearGageLocation = null; + if (project.getNearGageLocationId() != null && project.getNearGageOfficeId() != null) { + nearGageLocation = new LOCATION_OBJ_T(); + nearGageLocation.setLOCATION_REF(getLocationRefT(project.getNearGageLocationId(), + project.getNearGageOfficeId())); + } + + String authorizingLaw = project.getAuthorizingLaw(); + Timestamp costYear = project.getCostYear() == null ? null : Timestamp.from(project.getCostYear()); + BigDecimal federalCost = project.getFederalCost() == null ? null : BigDecimal.valueOf(project.getFederalCost()); + + BigDecimal nonFederalCost = (project.getNonFederalCost() != null) ? BigDecimal.valueOf(project.getNonFederalCost()) : null; + BigDecimal federalOandMCost = (project.getFederalOAndMCost() != null) ? BigDecimal.valueOf(project.getFederalOAndMCost()) : null; + BigDecimal nonFederalOandMCost = (project.getNonFederalOAndMCost() != null) ? BigDecimal.valueOf(project.getNonFederalOAndMCost()) : null; + String costUnitsId = project.getCostUnit(); + String remarks = project.getProjectRemarks(); + String projectOwner = project.getProjectOwner(); + String hydropowerDescription = project.getHydropowerDesc(); + String sedimentationDescription = project.getSedimentationDesc(); + String downstreamUrbanDescription = project.getDownstreamUrbanDesc(); + String bankFullCapacityDescription = project.getBankFullCapacityDesc(); + Timestamp yieldTimeFrameStartTimestamp = (project.getYieldTimeFrameStart() != null) ? Timestamp.from(project.getYieldTimeFrameStart()) : null; + Timestamp yieldTimeFrameEndTimestamp = (project.getYieldTimeFrameEnd() != null) ? Timestamp.from(project.getYieldTimeFrameEnd()) : null; + return new PROJECT_OBJ_T(projectLocation, pumpBackLocation, nearGageLocation, + authorizingLaw, costYear, federalCost, nonFederalCost, federalOandMCost, + nonFederalOandMCost, costUnitsId, remarks, projectOwner, hydropowerDescription, + sedimentationDescription, downstreamUrbanDescription, bankFullCapacityDescription, + yieldTimeFrameStartTimestamp, yieldTimeFrameEndTimestamp); + } + + public void delete(String office, String id, DeleteRule deleteRule) { +// Project project = retrieveProject(office, id); +// if (project == null) { +// throw new NotFoundException("Could not find project to delete."); +// } + + connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(getDslContext(c, office).configuration(), + id, deleteRule.getRule(), office + )); + } +} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dto/Projects.java b/cwms-data-api/src/main/java/cwms/cda/data/dto/Projects.java new file mode 100644 index 000000000..61a1eef6d --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/data/dto/Projects.java @@ -0,0 +1,122 @@ +package cwms.cda.data.dto; + +import cwms.cda.api.errors.FieldException; +import cwms.cda.formatters.Formats; +import cwms.cda.formatters.annotations.FormattableWith; +import cwms.cda.formatters.json.JsonV2; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + + +@FormattableWith(contentType = Formats.JSON, formatter = JsonV2.class) +public class Projects extends CwmsDTOPaginated { + + List projects; + + private Projects() { + } + + public Projects(String page, int pageSize, Integer total) { + super(page, pageSize, total); + projects = new ArrayList<>(); + } + + public List getProjects() { + return Collections.unmodifiableList(projects); + } + + + /** + * Extract the office from the cursor. + * + * @param cursor the cursor + * @return office + */ + public static String getOffice(String cursor) { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length > 1) { + String[] idAndOffice = CwmsDTOPaginated.decodeCursor(parts[0]); + if (idAndOffice.length > 0) { + return idAndOffice[0]; + } + } + return null; + } + + /** + * Extract the id from the cursor. + * + * @param cursor the cursor + * @return id + */ + public static String getId(String cursor) { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length > 1) { + String[] idAndOffice = CwmsDTOPaginated.decodeCursor(parts[0]); + if (idAndOffice.length > 1) { + return idAndOffice[1]; + } + } + return null; + } + + public static int getPageSize(String cursor) { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length > 2) { + return Integer.parseInt(parts[2]); + } + return 0; + } + + public static int getTotal(String cursor) { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length > 1) { + return Integer.parseInt(parts[1]); + } + return 0; + } + + + public static class Builder { + private Projects workingProjects; + + public Builder(String currentPage, int pageSize, Integer total) { + workingProjects = new Projects(currentPage, pageSize, total); + } + + public Projects build() { + if (this.workingProjects.projects.size() == this.workingProjects.pageSize) { + Project last = + this.workingProjects.projects.get(this.workingProjects.projects.size() - 1); + String cursor = encodeCursor(CwmsDTOPaginated.delimiter, last.getOfficeId(), + last.getName()); + this.workingProjects.nextPage = encodeCursor(cursor, + this.workingProjects.pageSize, this.workingProjects.total); + } else { + this.workingProjects.nextPage = null; + } + return workingProjects; + } + + public Builder add(Project project) { + this.workingProjects.projects.add(project); + return this; + } + + public Builder addAll(Collection projects) { + this.workingProjects.projects.addAll(projects); + return this; + } + } + + + @Override + public void validate() throws FieldException { + // TODO Auto-generated method stub + + } + + +} diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java new file mode 100644 index 000000000..ead6c2c0d --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java @@ -0,0 +1,249 @@ +/* + * 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; + +import static cwms.cda.security.KeyAccessManager.AUTH_HEADER; +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import cwms.cda.data.dto.Project; +import cwms.cda.formatters.ContentType; +import cwms.cda.formatters.Formats; +import fixtures.TestAccounts; +import io.restassured.filter.log.LogDetail; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +@Tag("integration") +final class ProjectControllerIT extends DataApiTestIT { + + + @Test + void test_get_create_delete() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/project.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + Project project = Formats.parseContent(new ContentType(Formats.JSON), json, Project.class); + + // Structure of test: + // 1)Create the Project + // 2)Retrieve the Project and assert that it exists + // 3)Delete the Project + // 4)Retrieve the Project and assert that it does not exist + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + //Create the project + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(json) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + ; + + String office = project.getOfficeId(); + // Retrieve the project and assert that it exists + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/" + project.getName()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("office-id", equalTo(office)) + .body("name", equalTo(project.getName())) + ; + + // Delete a Project + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("projects/" + project.getName()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + + // Retrieve a Project and assert that it does not exist + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/" + project.getName()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("value", nullValue()) + ; + } + + @Test + void test_update_does_not_exist() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/project_new.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + Project project = Formats.parseContent(new ContentType(Formats.JSON), json, Project.class); + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + //Try to update the project - should fail b/c it does not exist + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(json) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .patch("/projects/" + project.getName()) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)) + ; + + } + + @Test + void test_delete_does_not_exist() { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + // Delete a Project + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, user.getOperatingOffice()) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("projects/" + "blah" + Math.random()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)) + ; + } + + @Test + void test_get_all() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/project.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + Project project = Formats.parseContent(new ContentType(Formats.JSON), json, Project.class); + + // Structure of test: + // 1)Create the Project + // 2)Retrieve the Project with getAll and assert that it exists + // 3)Delete the Project + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + //Create the project + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(json) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)) + ; + String office = project.getOfficeId(); + + // Retrieve the project and assert that it exists + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .queryParam(Controllers.ID_MASK, "^" + project.getName() + "$") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("[0].office-id", equalTo(office)) + .body("[0].name", equalTo(project.getName())) + ; + + // Delete a Project + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("projects/" + project.getName()) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) + ; + } +} diff --git a/cwms-data-api/src/test/java/cwms/cda/data/dto/ProjectsTest.java b/cwms-data-api/src/test/java/cwms/cda/data/dto/ProjectsTest.java new file mode 100644 index 000000000..98fc775ac --- /dev/null +++ b/cwms-data-api/src/test/java/cwms/cda/data/dto/ProjectsTest.java @@ -0,0 +1,123 @@ +package cwms.cda.data.dto; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ProjectsTest { + + public static final int PAGE_SIZE = 10; + public static final int TOTAL = 37; + public static final String OFFICE = "SPK"; + + @Test + void testPaging() { + + List projList = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Project project = buildProject(i); + projList.add(project); + } + + Projects first = new Projects.Builder(null, PAGE_SIZE, TOTAL) + .addAll(projList) + .build(); + + List listInObj = first.getProjects(); + assertNotNull(listInObj); + assertEquals(10, listInObj.size()); + + List pages = new ArrayList<>(); + + pages.add(first.getPage()); + + String nextPage = first.getNextPage(); + + String id = Projects.getId(nextPage); + assertEquals("Test Project9", id, "Expected last project to be 9"); + String office = Projects.getOffice(nextPage); + assertEquals(OFFICE, office, "Expected office to be SPK"); + int pageSize = Projects.getPageSize(nextPage); + assertEquals(PAGE_SIZE, pageSize, "Expected page size to be " + PAGE_SIZE); + int total = Projects.getTotal(nextPage); + assertEquals(TOTAL, total, "Expected total to be " + TOTAL); + + pages.add(nextPage); + String currentPage = nextPage; + projList.clear(); + + for (int i = 10; i < 20; i++) { + Project project = buildProject(i); + projList.add(project); + } + + Projects second = new Projects.Builder(currentPage, PAGE_SIZE, TOTAL) + .addAll(projList) + .build(); + + nextPage = second.getNextPage(); + + id = Projects.getId(nextPage); + assertEquals("Test Project19", id, "Expected last project to be 19"); + office = Projects.getOffice(nextPage); + assertEquals(OFFICE, office, "Expected office to be SPK"); + pageSize = Projects.getPageSize(nextPage); + assertEquals(PAGE_SIZE, pageSize, "Expected page size to be " + PAGE_SIZE); + total = Projects.getTotal(nextPage); + assertEquals(TOTAL, total, "Expected total to be " + TOTAL); + + pages.add(nextPage); + currentPage = nextPage; + projList.clear(); + + for (int i = 20; i < 30; i++) { + Project project = buildProject(i); + projList.add(project); + } + + Projects third = new Projects.Builder(currentPage, PAGE_SIZE, TOTAL) + .addAll(projList) + .build(); + + + nextPage = third.getNextPage(); + + id = Projects.getId(nextPage); + assertEquals("Test Project29", id, "Expected last project to be 29"); + office = Projects.getOffice(nextPage); + assertEquals(OFFICE, office, "Expected office to be " + OFFICE); + pageSize = Projects.getPageSize(nextPage); + assertEquals(PAGE_SIZE, pageSize, "Expected page size to be " + PAGE_SIZE); + total = Projects.getTotal(nextPage); + assertEquals(TOTAL, total, "Expected total to be " + TOTAL); + + pages.add(nextPage); + currentPage = nextPage; + projList.clear(); + + for (int i = 30; i < 37; i++) { + Project project = buildProject(i); + projList.add(project); + } + + Projects fourth = new Projects.Builder(currentPage, PAGE_SIZE, 37) + .addAll(projList) + .build(); + + nextPage = fourth.getNextPage(); + + assertNull(nextPage, "Expected no next page"); + } + + private static Project buildProject(int i) { + Project project = new Project.Builder() + .withOfficeId(OFFICE) + .withName("Test Project" + i) + .build(); + return project; + } +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project.json b/cwms-data-api/src/test/resources/cwms/cda/api/project.json new file mode 100644 index 000000000..4d1d0f842 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project.json @@ -0,0 +1,23 @@ +{ + "office-id": "SPK", + "name" : "test_get_create_delete", + "federal-cost" : 100.0, + "non-federal-cost" : 50.0, + "cost-year" : 1717199914902, + "cost-unit" : "$", + "federal-o-and-m-cost" : 10.0, + "non-federal-o-and-m-cost" : 5.0, + "authorizing-law" : "Authorizing Law", + "project-owner" : "Project Owner", + "hydropower-desc" : "Hydropower Description", + "sedimentation-desc" : "Sedimentation Description", + "downstream-urban-desc" : "Downstream Urban Description", + "bank-full-capacity-desc" : "Bank Full Capacity Description", + "pump-back-location-id" : "Pumpback Location Id", + "pump-back-office-id" : "SPK", + "near-gage-location-id" : "Near Gage Location Id", + "near-gage-office-id" : "SPK", + "yield-time-frame-start" : 1717199914902, + "yield-time-frame-end" : 1717199914902, + "project-remarks" : "Remarks" +} \ No newline at end of file diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project_new.json b/cwms-data-api/src/test/resources/cwms/cda/api/project_new.json new file mode 100644 index 000000000..c81fa8a2f --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project_new.json @@ -0,0 +1,5 @@ +{ + "office-id": "SPK", + "name" : "test_update_not_exist", + "project-remarks" : "this shouldn't exist and the client is going to try and update it." +} \ No newline at end of file From 570887cc93362a30c513bd7f225225ac64324f8d Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:18:17 -0700 Subject: [PATCH 2/8] Added method to query table instead of view. --- .../java/cwms/cda/api/ProjectController.java | 2 +- .../java/cwms/cda/data/dao/ProjectDao.java | 257 ++++++++++++++++-- .../cwms/cda/api/ProjectControllerIT.java | 35 ++- .../test/resources/cwms/cda/api/project.json | 6 +- 4 files changed, 274 insertions(+), 26 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java index c45447eee..89da11d8b 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java @@ -108,7 +108,7 @@ public void getAll(@NotNull Context ctx) { Integer.class, DEFAULT_PAGE_SIZE, metrics, name(ProjectController.class.getName(), GET_ALL)); - Projects projects = dao.retrieveProjects(cursor, pageSize, projectIdMask, office); + Projects projects = dao.retrieveProjectsFromTable(cursor, pageSize, projectIdMask, office); ContentType contentType = getContentType(ctx); ctx.contentType(contentType.toString()); diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java index 018cc5b43..7fadccd1e 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java @@ -9,13 +9,21 @@ import cwms.cda.data.dto.Project; import cwms.cda.data.dto.Projects; import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; import java.util.List; +import java.util.TimeZone; +import java.util.logging.Logger; import org.jetbrains.annotations.NotNull; import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.Record1; import org.jooq.SelectConditionStep; +import org.jooq.SelectLimitPercentStep; +import org.jooq.conf.ParamType; import usace.cwms.db.dao.ifc.loc.LocationRefType; import usace.cwms.db.dao.ifc.loc.LocationType; import usace.cwms.db.dao.util.OracleTypeMap; @@ -27,6 +35,90 @@ import usace.cwms.db.jooq.dao.util.LocationTypeUtil; public class ProjectDao extends JooqDao { + private static final Logger logger = Logger.getLogger(TimeSeriesDaoImpl.class.getName()); + + private final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + + private final String firstQuery = "select project.office_id,\n" + + " project.location_id as project_id,\n" + + " project.COST_YEAR,\n" + + " project.federal_cost,\n" + + " project.nonfederal_cost,\n" + + " project.FEDERAL_OM_COST,\n" + + " project.NONFEDERAL_OM_COST,\n" + + " project.authorizing_law,\n" + + " project.project_owner,\n" + + " project.hydropower_description,\n" + + " project.sedimentation_description,\n" + + " project.downstream_urban_description,\n" + + " project.bank_full_capacity_description,\n" + + " pumpback.location_id as pump_back_location_id,\n" + + " pumpback.p_office_id as pump_back_office_id,\n" + + " neargage.location_id as near_gage_location_id,\n" + + " neargage.n_office_id as near_gage_office_id,\n" + + " project.yield_time_frame_start,\n" + + " project.yield_time_frame_end,\n" + + " project.project_remarks\n" + + "from ( select o.office_id as office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id,\n" + + " p.COST_YEAR,\n" + + " p.federal_cost,\n" + + " p.nonfederal_cost,\n" + + " p.FEDERAL_OM_COST,\n" + + " p.NONFEDERAL_OM_COST,\n" + + " p.authorizing_law,\n" + + " p.project_owner,\n" + + " p.hydropower_description,\n" + + " p.sedimentation_description,\n" + + " p.downstream_urban_description,\n" + + " p.bank_full_capacity_description,\n" + + " p.pump_back_location_code,\n" + + " p.near_gage_location_code,\n" + + " p.yield_time_frame_start,\n" + + " p.yield_time_frame_end,\n" + + " p.project_remarks\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) project\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as p_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as n_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) neargage on neargage.location_code = project.near_gage_location_code\n" + + "where ((regexp_like(project.location_id, ?, 'i')) and\n" + + " office_id = ?)\n" + + "order by office_id, project_id fetch next ? rows only"; + public ProjectDao(DSLContext dsl) { super(dsl); } @@ -126,22 +218,43 @@ private Project buildProject(PROJECT_OBJ_T projectObjT) { } return builder.build(); - } private static Project buildProject(usace.cwms.db.jooq.codegen.tables.records.AV_PROJECT r) { - Project.Builder builder = new Project.Builder() - .withOfficeId(r.getOFFICE_ID()) - .withName(r.getPROJECT_ID()); + Project.Builder builder = new Project.Builder(); + builder.withOfficeId(r.getOFFICE_ID()); + builder.withName(r.getPROJECT_ID()); + //builder.withPumpBackOfficeId(r.getPUMP_BACK_OFFICE_ID()); + builder.withPumpBackOfficeId(r.getOFFICE_ID()); // Can we assume same office? builder.withPumpBackLocationId(r.getPUMP_BACK_LOCATION_ID()); + //builder.withNearGageOfficeId(r.getNEAR_GAGE_OFFICE_ID()); + builder.withNearGageOfficeId(r.getOFFICE_ID()); // Can we assume same office? builder.withNearGageLocationId(r.getNEAR_GAGE_LOCATION_ID()); builder.withAuthorizingLaw(r.getAUTHORIZING_LAW()); - builder.withFederalCost(r.getFEDERAL_COST().doubleValue()); - builder.withNonFederalCost(r.getNONFEDERAL_COST().doubleValue()); - // TODO: This isn't fully built out yet. need to figure out where - // some of these fields come from -// builder.withFederalOAndMCost(r.getFEDERAL_OM_COST().doubleValue()); -// builder.withNonFederalOAndMCost(r.getNONFEDERAL_OM_COST().doubleValue()); + builder.withProjectRemarks(r.getPROJECT_REMARKS()); + builder.withProjectOwner(r.getPROJECT_OWNER()); + builder.withHydropowerDesc(r.getHYDROPOWER_DESCRIPTION()); + builder.withSedimentationDesc(r.getSEDIMENTATION_DESCRIPTION()); + builder.withDownstreamUrbanDesc(r.getDOWNSTREAM_URBAN_DESCRIPTION()); + builder.withBankFullCapacityDesc(r.getBANK_FULL_CAPACITY_DESCRIPTION()); + BigDecimal federalCost = r.getFEDERAL_COST(); + if(federalCost != null) { + builder.withFederalCost(federalCost.doubleValue()); + } + BigDecimal nonfederalCost = r.getNONFEDERAL_COST(); + if(nonfederalCost != null) { + builder.withNonFederalCost(nonfederalCost.doubleValue()); + } + Timestamp yieldTimeFrameStart = r.getYIELD_TIME_FRAME_START(); + if(yieldTimeFrameStart != null) { + builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); + } + Timestamp yieldTimeFrameEnd = r.getYIELD_TIME_FRAME_END(); + if(yieldTimeFrameEnd != null){ + builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); + } + + // The view is missing cost-year, fed_om_cat and nonfed_om_cost and the pump office and near gage office. return builder.build(); } @@ -151,7 +264,7 @@ public static String getLocationId(String base, String sub) { return hasSub ? base + "-" + sub : base; } - public Projects retrieveProjects(String cursor, int pageSize, String projectIdMask, String office) { + public Projects retrieveProjectsFromView(String cursor, int pageSize, String projectIdMask, String office) { Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); if (office != null) { @@ -162,11 +275,12 @@ public Projects retrieveProjects(String cursor, int pageSize, String projectIdMa String cursorProjectId = null; int total = 0; if (cursor == null || cursor.isEmpty()) { + logger.info("where like:" + whereClause.toString()); SelectConditionStep> count = dsl.select(count(asterisk())) .from(AV_PROJECT.AV_PROJECT) .where(whereClause); - + logger.info(() -> count.getSQL(ParamType.INLINED)); total = count.fetchOne().value1(); } else { String[] parts = CwmsDTOPaginated.decodeCursor(cursor); @@ -186,12 +300,123 @@ public Projects retrieveProjects(String cursor, int pageSize, String projectIdMa pagingCondition = inSameOffice.or(nextOffice); } - List projs = dsl.selectFrom(AV_PROJECT.AV_PROJECT) + SelectLimitPercentStep querry = dsl.selectFrom(AV_PROJECT.AV_PROJECT) .where(whereClause.and(pagingCondition)) .orderBy(AV_PROJECT.AV_PROJECT.OFFICE_ID, AV_PROJECT.AV_PROJECT.PROJECT_ID) - .limit(pageSize) - .fetch() - .map(r -> buildProject(r)); + .limit(pageSize); + + logger.info(() -> querry.getSQL(ParamType.INLINED)); + + List projs = querry.fetch().map(r -> buildProject(r)); + + Projects.Builder builder = new Projects.Builder(cursor, pageSize, total); + builder.addAll(projs); + return builder.build(); + } + + public Projects retrieveProjectsFromTable(String cursor, int pageSize, String projectIdMask, String office) { + + + Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); + if (office != null) { + whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); + } + + String cursorOffice = null; + String cursorProjectId = null; + int total = 0; + if (cursor == null || cursor.isEmpty()) { + + SelectConditionStep> count = + dsl.select(count(asterisk())) + .from(AV_PROJECT.AV_PROJECT) + .where(whereClause); + + total = count.fetchOne().value1(); + } else { + String[] parts = CwmsDTOPaginated.decodeCursor(cursor); + if (parts.length == 4) { + cursorOffice = parts[0]; + cursorProjectId = parts[1]; + pageSize = Integer.parseInt(parts[2]); + total = Integer.parseInt(parts[3]); + } + } + + Condition pagingCondition = noCondition(); + if (cursorOffice != null || cursorProjectId != null) { + Condition inSameOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(cursorOffice) + .and(AV_PROJECT.AV_PROJECT.PROJECT_ID.gt(cursorProjectId)); + Condition nextOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.gt(cursorOffice); + pagingCondition = inSameOffice.or(nextOffice); + + // lets log this so we can see whhat it looks like + logger.info(whereClause.and(pagingCondition).toString()); + } + + int finalPageSize = pageSize; + List projs = connectionResult(dsl, c -> { + List projects = null; + try (PreparedStatement ps = c.prepareStatement(firstQuery)) { + ps.setString(1, projectIdMask); + ps.setString(2, office); + ps.setInt(3, finalPageSize); + + try (ResultSet resultSet = ps.executeQuery()) { + projects = new ArrayList<>(); + while (resultSet.next()) { + Project.Builder builder = new Project.Builder(); + builder.withOfficeId(resultSet.getString("office_id")); + builder.withName(resultSet.getString("project_id")); + Timestamp costStamp = resultSet.getTimestamp("COST_YEAR", UTC_CALENDAR); + if(costStamp != null) { + builder.withCostYear(costStamp.toInstant()); + } + BigDecimal federalCost = resultSet.getBigDecimal("federal_cost"); + if(federalCost != null) { + builder.withFederalCost(federalCost.doubleValue()); + } + + BigDecimal nonfederalCost = resultSet.getBigDecimal("nonfederal_cost"); + if(nonfederalCost != null) { + builder.withNonFederalCost(nonfederalCost.doubleValue()); + } + + BigDecimal federalOmCost = resultSet.getBigDecimal("FEDERAL_OM_COST"); + if(federalOmCost != null) { + builder.withFederalOAndMCost(federalOmCost.doubleValue()); + } + BigDecimal nonfederalOmCost = resultSet.getBigDecimal("NONFEDERAL_OM_COST"); + if(nonfederalOmCost != null) { + builder.withNonFederalOAndMCost(nonfederalOmCost.doubleValue()); + } + builder.withAuthorizingLaw(resultSet.getString("authorizing_law")); + builder.withProjectOwner(resultSet.getString("project_owner")); + builder.withHydropowerDesc(resultSet.getString("hydropower_description")); + builder.withSedimentationDesc(resultSet.getString("sedimentation_description")); + builder.withDownstreamUrbanDesc(resultSet.getString("downstream_urban_description")); + builder.withBankFullCapacityDesc(resultSet.getString("bank_full_capacity_description")); + builder.withPumpBackLocationId(resultSet.getString("pump_back_location_id")); + builder.withPumpBackOfficeId(resultSet.getString("pump_back_office_id")); + builder.withNearGageLocationId(resultSet.getString("near_gage_location_id")); + builder.withNearGageOfficeId(resultSet.getString("near_gage_office_id")); + Timestamp yieldTimeFrameStart = resultSet.getTimestamp("yield_time_frame_start", UTC_CALENDAR); + if (yieldTimeFrameStart != null) { + builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); + } + Timestamp yieldTimeFrameEnd = resultSet.getTimestamp("yield_time_frame_end", UTC_CALENDAR); + if(yieldTimeFrameEnd != null) { + builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); + } + builder.withProjectRemarks(resultSet.getString("project_remarks")); + + projects.add(builder.build()); + } + } + } + return projects; + }); + Projects.Builder builder = new Projects.Builder(cursor, pageSize, total); builder.addAll(projs); diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java index ead6c2c0d..a8cfd0bc9 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java @@ -28,7 +28,6 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertNotNull; import cwms.cda.data.dto.Project; @@ -119,7 +118,6 @@ void test_get_create_delete() throws IOException { .log().ifValidationFails(LogDetail.ALL,true) .accept(Formats.JSON) .queryParam(Controllers.OFFICE, office) - .when() .redirects().follow(true) .redirects().max(3) @@ -127,8 +125,7 @@ void test_get_create_delete() throws IOException { .then() .log().ifValidationFails(LogDetail.ALL,true) .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)) - .body("value", nullValue()) + .statusCode(is(HttpServletResponse.SC_NOT_FOUND)) ; } @@ -210,9 +207,15 @@ void test_get_all() throws IOException { .assertThat() .statusCode(is(HttpServletResponse.SC_CREATED)) ; + String office = project.getOfficeId(); + long expectedCostYear = 1717282800000L; + // Retrieve the project and assert that it exists + long expectedStart = 1717282800000L; + long expectedEnd = 1717308000000L; + given() .log().ifValidationFails(LogDetail.ALL,true) .accept(Formats.JSON) @@ -226,8 +229,28 @@ void test_get_all() throws IOException { .log().ifValidationFails(LogDetail.ALL,true) .assertThat() .statusCode(is(HttpServletResponse.SC_OK)) - .body("[0].office-id", equalTo(office)) - .body("[0].name", equalTo(project.getName())) + .body("projects[0].office-id", equalTo(office)) + .body("projects[0].name", equalTo(project.getName())) + .body("projects[0].federal-cost", equalTo(100.0f)) + .body("projects[0].non-federal-cost", equalTo(50.0f)) + .body("projects[0].federal-o-and-m-cost", equalTo(10.0f)) + .body("projects[0].non-federal-o-and-m-cost", equalTo(5.0f)) + .body("projects[0].authorizing-law", equalTo("Authorizing Law")) + .body("projects[0].project-owner", equalTo("Project Owner")) + .body("projects[0].hydropower-desc", equalTo("Hydropower Description")) + .body("projects[0].sedimentation-desc", equalTo("Sedimentation Description")) + .body("projects[0].downstream-urban-desc", equalTo("Downstream Urban Description")) + .body("projects[0].bank-full-capacity-desc", equalTo("Bank Full Capacity Description")) + .body("projects[0].project-remarks", equalTo("Remarks")) + .body("projects[0].yield-time-frame-start", equalTo(expectedStart)) + .body("projects[0].yield-time-frame-end", equalTo(expectedEnd)) + .body("projects[0].cost-year", equalTo(expectedCostYear)) +// TODO: .body("projects[0].pump-back-location-id", equalTo("Pumpback Location Id")) +// TODO: .body("projects[0].pump-back-office-id", equalTo("SPK")) +// TODO: .body("projects[0].near-gage-location-id", equalTo("Near Gage Location Id")) +// TODO: .body("projects[0].near-gage-office-id", equalTo("SPK")) +// TODO: .body("projects[0].cost-unit", equalTo("$")) + ; // Delete a Project diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project.json b/cwms-data-api/src/test/resources/cwms/cda/api/project.json index 4d1d0f842..bdf23508c 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/project.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project.json @@ -3,7 +3,7 @@ "name" : "test_get_create_delete", "federal-cost" : 100.0, "non-federal-cost" : 50.0, - "cost-year" : 1717199914902, + "cost-year" : 1717282800000, "cost-unit" : "$", "federal-o-and-m-cost" : 10.0, "non-federal-o-and-m-cost" : 5.0, @@ -17,7 +17,7 @@ "pump-back-office-id" : "SPK", "near-gage-location-id" : "Near Gage Location Id", "near-gage-office-id" : "SPK", - "yield-time-frame-start" : 1717199914902, - "yield-time-frame-end" : 1717199914902, + "yield-time-frame-start" : 1717282800000, + "yield-time-frame-end" : 1717308000000, "project-remarks" : "Remarks" } \ No newline at end of file From e4a6e01f95ab8e5ad9a8fe000fb4754a3aecbaa5 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Wed, 5 Jun 2024 18:25:44 -0700 Subject: [PATCH 3/8] Updated table queries and added a paging test. --- .../java/cwms/cda/api/ProjectController.java | 31 +- .../java/cwms/cda/data/dao/ProjectDao.java | 489 ++++++++++-------- .../cwms/cda/api/ProjectControllerIT.java | 116 +++++ 3 files changed, 401 insertions(+), 235 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java index 89da11d8b..cd7792838 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java @@ -75,12 +75,11 @@ private Timer.Context markAndTime(String subject) { @OpenApiParam(name = PAGE, description = "This end point can return a lot of data, this identifies where" + " in the request you are. This is an opaque value, and can be" - + " obtained from the 'next-page' value in the response." - ), + + " obtained from the 'next-page' value in the response."), @OpenApiParam(name = PAGE_SIZE, type = Integer.class, description = "How many entries per page returned. " - + "Default " + DEFAULT_PAGE_SIZE + "." - ),}, + + "Default " + DEFAULT_PAGE_SIZE + ".") + }, responses = { @OpenApiResponse(status = STATUS_200, content = { @OpenApiContent(type = Formats.JSON, from = Projects.class)}), @@ -141,11 +140,8 @@ public void getAll(@NotNull Context ctx) { + " response."), }, responses = { - @OpenApiResponse(status = STATUS_200, - content = { - @OpenApiContent(from = Project.class, type = Formats.JSON) - } - ), + @OpenApiResponse(status = STATUS_200, content = { + @OpenApiContent(from = Project.class, type = Formats.JSON)}), @OpenApiResponse(status = STATUS_404, description = "Based on the combination of " + "inputs provided the Project was not found."), @OpenApiResponse(status = STATUS_501, description = "request format is not " @@ -187,11 +183,8 @@ public void getOne(@NotNull Context ctx, @NotNull String name) { @OpenApi( description = "Create new Project", - requestBody = @OpenApiRequestBody( - content = { - @OpenApiContent(from = Project.class, type = Formats.JSON) - }, - required = true + requestBody = @OpenApiRequestBody(required = true, + content = {@OpenApiContent(from = Project.class, type = Formats.JSON)} ), method = HttpMethod.POST, tags = {TAG} @@ -220,13 +213,13 @@ public void create(@NotNull Context ctx) { @OpenApi( description = "Updates a text timeseries", pathParams = { - @OpenApiParam(name = NAME, description = "The id of the text timeseries to be updated"), + @OpenApiParam(name = NAME, description = "The id of the text timeseries to be updated"), }, requestBody = @OpenApiRequestBody( - content = { - @OpenApiContent(from = Project.class, type = Formats.JSON), - }, - required = true + content = { + @OpenApiContent(from = Project.class, type = Formats.JSON), + }, + required = true ), method = HttpMethod.PATCH, tags = {TAG} diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java index 7fadccd1e..be274e380 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java @@ -11,6 +11,7 @@ import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.SQLException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; @@ -18,12 +19,12 @@ import java.util.TimeZone; import java.util.logging.Logger; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jooq.Condition; import org.jooq.DSLContext; import org.jooq.Record1; import org.jooq.SelectConditionStep; import org.jooq.SelectLimitPercentStep; -import org.jooq.conf.ParamType; import usace.cwms.db.dao.ifc.loc.LocationRefType; import usace.cwms.db.dao.ifc.loc.LocationType; import usace.cwms.db.dao.util.OracleTypeMap; @@ -35,89 +36,87 @@ import usace.cwms.db.jooq.dao.util.LocationTypeUtil; public class ProjectDao extends JooqDao { - private static final Logger logger = Logger.getLogger(TimeSeriesDaoImpl.class.getName()); + private static final Logger logger = Logger.getLogger(ProjectDao.class.getName()); private final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - private final String firstQuery = "select project.office_id,\n" - + " project.location_id as project_id,\n" - + " project.COST_YEAR,\n" - + " project.federal_cost,\n" - + " project.nonfederal_cost,\n" - + " project.FEDERAL_OM_COST,\n" - + " project.NONFEDERAL_OM_COST,\n" - + " project.authorizing_law,\n" - + " project.project_owner,\n" - + " project.hydropower_description,\n" - + " project.sedimentation_description,\n" - + " project.downstream_urban_description,\n" - + " project.bank_full_capacity_description,\n" - + " pumpback.location_id as pump_back_location_id,\n" - + " pumpback.p_office_id as pump_back_office_id,\n" - + " neargage.location_id as near_gage_location_id,\n" - + " neargage.n_office_id as near_gage_office_id,\n" - + " project.yield_time_frame_start,\n" - + " project.yield_time_frame_end,\n" - + " project.project_remarks\n" - + "from ( select o.office_id as office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id,\n" - + " p.COST_YEAR,\n" - + " p.federal_cost,\n" - + " p.nonfederal_cost,\n" - + " p.FEDERAL_OM_COST,\n" - + " p.NONFEDERAL_OM_COST,\n" - + " p.authorizing_law,\n" - + " p.project_owner,\n" - + " p.hydropower_description,\n" - + " p.sedimentation_description,\n" - + " p.downstream_urban_description,\n" - + " p.bank_full_capacity_description,\n" - + " p.pump_back_location_code,\n" - + " p.near_gage_location_code,\n" - + " p.yield_time_frame_start,\n" - + " p.yield_time_frame_end,\n" - + " p.project_remarks\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) project\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as p_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as n_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) neargage on neargage.location_code = project.near_gage_location_code\n" - + "where ((regexp_like(project.location_id, ?, 'i')) and\n" - + " office_id = ?)\n" - + "order by office_id, project_id fetch next ? rows only"; + public static final String SELECT_PART = "select project.office_id,\n" + + " project.location_id as project_id,\n" + + " project.COST_YEAR,\n" + + " project.federal_cost,\n" + + " project.nonfederal_cost,\n" + + " project.FEDERAL_OM_COST,\n" + + " project.NONFEDERAL_OM_COST,\n" + + " project.authorizing_law,\n" + + " project.project_owner,\n" + + " project.hydropower_description,\n" + + " project.sedimentation_description,\n" + + " project.downstream_urban_description,\n" + + " project.bank_full_capacity_description,\n" + + " pumpback.location_id as pump_back_location_id,\n" + + " pumpback.p_office_id as pump_back_office_id,\n" + + " neargage.location_id as near_gage_location_id,\n" + + " neargage.n_office_id as near_gage_office_id,\n" + + " project.yield_time_frame_start,\n" + + " project.yield_time_frame_end,\n" + + " project.project_remarks\n" + + "from ( select o.office_id as office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id,\n" + + " p.COST_YEAR,\n" + + " p.federal_cost,\n" + + " p.nonfederal_cost,\n" + + " p.FEDERAL_OM_COST,\n" + + " p.NONFEDERAL_OM_COST,\n" + + " p.authorizing_law,\n" + + " p.project_owner,\n" + + " p.hydropower_description,\n" + + " p.sedimentation_description,\n" + + " p.downstream_urban_description,\n" + + " p.bank_full_capacity_description,\n" + + " p.pump_back_location_code,\n" + + " p.near_gage_location_code,\n" + + " p.yield_time_frame_start,\n" + + " p.yield_time_frame_end,\n" + + " p.project_remarks\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) project\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as p_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as n_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) neargage on neargage.location_code = project.near_gage_location_code\n"; + public ProjectDao(DSLContext dsl) { super(dsl); @@ -134,85 +133,93 @@ public Project retrieveProject(String office, String projectId) { } private Project buildProject(PROJECT_OBJ_T projectObjT) { + Project.Builder builder = new Project.Builder(); + LOCATION_OBJ_T projectLocation = projectObjT.getPROJECT_LOCATION(); LOCATION_REF_T locRef = projectLocation.getLOCATION_REF(); String office = locRef.getOFFICE_ID(); + builder.withOfficeId(office); + String id = getLocationId(locRef.getBASE_LOCATION_ID(), locRef.getSUB_LOCATION_ID()); + builder.withName(id); String authorizingLaw = projectObjT.getAUTHORIZING_LAW(); + builder.withAuthorizingLaw(authorizingLaw); + String bankFullCapacityDescription = projectObjT.getBANK_FULL_CAPACITY_DESCRIPTION(); - Timestamp costYear = projectObjT.getCOST_YEAR(); + builder.withBankFullCapacityDesc(bankFullCapacityDescription); + String downstreamUrbanDescription = projectObjT.getDOWNSTREAM_URBAN_DESCRIPTION(); - BigDecimal federalCost = projectObjT.getFEDERAL_COST(); + builder.withDownstreamUrbanDesc(downstreamUrbanDescription); + String costUnitsId = projectObjT.getCOST_UNITS_ID(); - BigDecimal federalOandMCost = projectObjT.getFEDERAL_OM_COST(); + builder.withCostUnit(costUnitsId); + + String projectOwner = projectObjT.getPROJECT_OWNER(); + builder.withProjectOwner(projectOwner); + String hydropowerDescription = projectObjT.getHYDROPOWER_DESCRIPTION(); + builder.withHydropowerDesc(hydropowerDescription); + + String remarks = projectObjT.getREMARKS(); + builder.withProjectRemarks(remarks); + + String sedimentationDescription = projectObjT.getSEDIMENTATION_DESCRIPTION(); + builder.withSedimentationDesc(sedimentationDescription); + LocationType neargageLocationType = LocationTypeUtil.toLocationType(projectObjT.getNEAR_GAGE_LOCATION()); LocationRefType nearLocRef = null; if (neargageLocationType != null) { nearLocRef = neargageLocationType.getLocationRef(); } - BigDecimal nonFederalCost = projectObjT.getNONFEDERAL_COST(); - BigDecimal nonFederalOandMCost = projectObjT.getNONFEDERAL_OM_COST(); - String projectOwner = projectObjT.getPROJECT_OWNER(); + if (nearLocRef != null) { + builder = builder.withNearGageLocationId(nearLocRef.getOfficeId()) + .withNearGageLocationId(getLocationId(nearLocRef.getBaseLocationId(), nearLocRef.getSubLocationId())); + } + LocationType pumpbackLocationType = LocationTypeUtil.toLocationType(projectObjT.getPUMP_BACK_LOCATION()); LocationRefType pumpbackLocRef = null; if (pumpbackLocationType != null) { pumpbackLocRef = pumpbackLocationType.getLocationRef(); } - String remarks = projectObjT.getREMARKS(); - String sedimentationDescription = projectObjT.getSEDIMENTATION_DESCRIPTION(); - Timestamp yieldTimeFrameEnd = projectObjT.getYIELD_TIME_FRAME_END(); - Timestamp yieldTimeFrameStart = projectObjT.getYIELD_TIME_FRAME_START(); - - Project.Builder builder = new Project.Builder() - .withOfficeId(office) - .withName(id) - .withAuthorizingLaw(authorizingLaw) - .withBankFullCapacityDesc(bankFullCapacityDescription) - .withHydropowerDesc(hydropowerDescription) - .withProjectRemarks(remarks) - .withSedimentationDesc(sedimentationDescription) - .withProjectOwner(projectOwner) - .withDownstreamUrbanDesc(downstreamUrbanDescription) - .withCostUnit(costUnitsId) - ; - - if (costYear != null) { - builder = builder.withCostYear(costYear.toInstant()); + if (pumpbackLocRef != null) { + builder = builder.withPumpBackOfficeId(pumpbackLocRef.getOfficeId()) + .withPumpBackLocationId(getLocationId(pumpbackLocRef.getBaseLocationId(), pumpbackLocRef.getSubLocationId())); } + BigDecimal federalCost = projectObjT.getFEDERAL_COST(); if (federalCost != null) { builder = builder.withFederalCost(federalCost.doubleValue()); } + BigDecimal federalOandMCost = projectObjT.getFEDERAL_OM_COST(); if (federalOandMCost != null) { builder = builder.withFederalOAndMCost(federalOandMCost.doubleValue()); } - if (nearLocRef != null) { - builder = builder.withNearGageLocationId(nearLocRef.getOfficeId()) - .withNearGageLocationId(getLocationId(nearLocRef.getBaseLocationId(), nearLocRef.getSubLocationId())); - } + BigDecimal nonFederalCost = projectObjT.getNONFEDERAL_COST(); if (nonFederalCost != null) { builder = builder.withNonFederalCost(nonFederalCost.doubleValue()); } + BigDecimal nonFederalOandMCost = projectObjT.getNONFEDERAL_OM_COST(); if (nonFederalOandMCost != null) { builder = builder.withNonFederalOAndMCost(nonFederalOandMCost.doubleValue()); } - if (pumpbackLocRef != null) { - builder = builder.withPumpBackOfficeId(pumpbackLocRef.getOfficeId()) - .withPumpBackLocationId(getLocationId(pumpbackLocRef.getBaseLocationId(), pumpbackLocRef.getSubLocationId())); + Timestamp costYear = projectObjT.getCOST_YEAR(); + if (costYear != null) { + builder = builder.withCostYear(costYear.toInstant()); } + Timestamp yieldTimeFrameEnd = projectObjT.getYIELD_TIME_FRAME_END(); if (yieldTimeFrameEnd != null) { builder = builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); } + Timestamp yieldTimeFrameStart = projectObjT.getYIELD_TIME_FRAME_START(); if (yieldTimeFrameStart != null) { builder = builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); } @@ -238,19 +245,19 @@ private static Project buildProject(usace.cwms.db.jooq.codegen.tables.records.AV builder.withDownstreamUrbanDesc(r.getDOWNSTREAM_URBAN_DESCRIPTION()); builder.withBankFullCapacityDesc(r.getBANK_FULL_CAPACITY_DESCRIPTION()); BigDecimal federalCost = r.getFEDERAL_COST(); - if(federalCost != null) { + if (federalCost != null) { builder.withFederalCost(federalCost.doubleValue()); } BigDecimal nonfederalCost = r.getNONFEDERAL_COST(); - if(nonfederalCost != null) { + if (nonfederalCost != null) { builder.withNonFederalCost(nonfederalCost.doubleValue()); } Timestamp yieldTimeFrameStart = r.getYIELD_TIME_FRAME_START(); - if(yieldTimeFrameStart != null) { + if (yieldTimeFrameStart != null) { builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); } Timestamp yieldTimeFrameEnd = r.getYIELD_TIME_FRAME_END(); - if(yieldTimeFrameEnd != null){ + if (yieldTimeFrameEnd != null) { builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); } @@ -275,12 +282,10 @@ public Projects retrieveProjectsFromView(String cursor, int pageSize, String pro String cursorProjectId = null; int total = 0; if (cursor == null || cursor.isEmpty()) { - logger.info("where like:" + whereClause.toString()); SelectConditionStep> count = dsl.select(count(asterisk())) .from(AV_PROJECT.AV_PROJECT) .where(whereClause); - logger.info(() -> count.getSQL(ParamType.INLINED)); total = count.fetchOne().value1(); } else { String[] parts = CwmsDTOPaginated.decodeCursor(cursor); @@ -300,32 +305,32 @@ public Projects retrieveProjectsFromView(String cursor, int pageSize, String pro pagingCondition = inSameOffice.or(nextOffice); } - SelectLimitPercentStep querry = dsl.selectFrom(AV_PROJECT.AV_PROJECT) + SelectLimitPercentStep query = + dsl.selectFrom(AV_PROJECT.AV_PROJECT) .where(whereClause.and(pagingCondition)) .orderBy(AV_PROJECT.AV_PROJECT.OFFICE_ID, AV_PROJECT.AV_PROJECT.PROJECT_ID) .limit(pageSize); - logger.info(() -> querry.getSQL(ParamType.INLINED)); - - List projs = querry.fetch().map(r -> buildProject(r)); + List projs = query.fetch().map(ProjectDao::buildProject); Projects.Builder builder = new Projects.Builder(cursor, pageSize, total); builder.addAll(projs); return builder.build(); } - public Projects retrieveProjectsFromTable(String cursor, int pageSize, String projectIdMask, String office) { - - - Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); - if (office != null) { - whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); - } - - String cursorOffice = null; - String cursorProjectId = null; - int total = 0; + public Projects retrieveProjectsFromTable(String cursor, int pageSize, + @Nullable String projectIdMask, @Nullable String office) { + final String cursorOffice; + final String cursorProjectId; + int total; if (cursor == null || cursor.isEmpty()) { + cursorOffice = null; + cursorProjectId = null; + + Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); + if (office != null) { + whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); + } SelectConditionStep> count = dsl.select(count(asterisk())) @@ -334,95 +339,146 @@ public Projects retrieveProjectsFromTable(String cursor, int pageSize, String pr total = count.fetchOne().value1(); } else { - String[] parts = CwmsDTOPaginated.decodeCursor(cursor); - if (parts.length == 4) { - cursorOffice = parts[0]; - cursorProjectId = parts[1]; - pageSize = Integer.parseInt(parts[2]); - total = Integer.parseInt(parts[3]); - } + cursorOffice = Projects.getOffice(cursor); + cursorProjectId = Projects.getId(cursor); + pageSize = Projects.getPageSize(cursor); + total = Projects.getTotal(cursor); } - Condition pagingCondition = noCondition(); - if (cursorOffice != null || cursorProjectId != null) { - Condition inSameOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(cursorOffice) - .and(AV_PROJECT.AV_PROJECT.PROJECT_ID.gt(cursorProjectId)); - Condition nextOffice = AV_PROJECT.AV_PROJECT.OFFICE_ID.gt(cursorOffice); - pagingCondition = inSameOffice.or(nextOffice); - - // lets log this so we can see whhat it looks like - logger.info(whereClause.and(pagingCondition).toString()); - } + // There are lots of ways the variables can be null or not so we need to build the query + // based on the parameters. + String query = buildTableQuery(projectIdMask, office, cursorOffice, cursorProjectId); int finalPageSize = pageSize; List projs = connectionResult(dsl, c -> { List projects = null; - try (PreparedStatement ps = c.prepareStatement(firstQuery)) { - ps.setString(1, projectIdMask); - ps.setString(2, office); - ps.setInt(3, finalPageSize); + try (PreparedStatement ps = c.prepareStatement(query)) { + fillTableQueryParameters(ps, projectIdMask, office, cursorOffice, cursorProjectId, finalPageSize); try (ResultSet resultSet = ps.executeQuery()) { projects = new ArrayList<>(); while (resultSet.next()) { - Project.Builder builder = new Project.Builder(); - builder.withOfficeId(resultSet.getString("office_id")); - builder.withName(resultSet.getString("project_id")); - Timestamp costStamp = resultSet.getTimestamp("COST_YEAR", UTC_CALENDAR); - if(costStamp != null) { - builder.withCostYear(costStamp.toInstant()); - } - BigDecimal federalCost = resultSet.getBigDecimal("federal_cost"); - if(federalCost != null) { - builder.withFederalCost(federalCost.doubleValue()); - } - - BigDecimal nonfederalCost = resultSet.getBigDecimal("nonfederal_cost"); - if(nonfederalCost != null) { - builder.withNonFederalCost(nonfederalCost.doubleValue()); - } - - BigDecimal federalOmCost = resultSet.getBigDecimal("FEDERAL_OM_COST"); - if(federalOmCost != null) { - builder.withFederalOAndMCost(federalOmCost.doubleValue()); - } - BigDecimal nonfederalOmCost = resultSet.getBigDecimal("NONFEDERAL_OM_COST"); - if(nonfederalOmCost != null) { - builder.withNonFederalOAndMCost(nonfederalOmCost.doubleValue()); - } - builder.withAuthorizingLaw(resultSet.getString("authorizing_law")); - builder.withProjectOwner(resultSet.getString("project_owner")); - builder.withHydropowerDesc(resultSet.getString("hydropower_description")); - builder.withSedimentationDesc(resultSet.getString("sedimentation_description")); - builder.withDownstreamUrbanDesc(resultSet.getString("downstream_urban_description")); - builder.withBankFullCapacityDesc(resultSet.getString("bank_full_capacity_description")); - builder.withPumpBackLocationId(resultSet.getString("pump_back_location_id")); - builder.withPumpBackOfficeId(resultSet.getString("pump_back_office_id")); - builder.withNearGageLocationId(resultSet.getString("near_gage_location_id")); - builder.withNearGageOfficeId(resultSet.getString("near_gage_office_id")); - Timestamp yieldTimeFrameStart = resultSet.getTimestamp("yield_time_frame_start", UTC_CALENDAR); - if (yieldTimeFrameStart != null) { - builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); - } - Timestamp yieldTimeFrameEnd = resultSet.getTimestamp("yield_time_frame_end", UTC_CALENDAR); - if(yieldTimeFrameEnd != null) { - builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); - } - builder.withProjectRemarks(resultSet.getString("project_remarks")); - - projects.add(builder.build()); + Project built = buildProjectFromTableRow(resultSet); + projects.add(built); } } } return projects; }); - Projects.Builder builder = new Projects.Builder(cursor, pageSize, total); builder.addAll(projs); return builder.build(); } + private Project buildProjectFromTableRow(ResultSet resultSet) throws SQLException { + Project.Builder builder = new Project.Builder(); + builder.withOfficeId(resultSet.getString("office_id")); + builder.withName(resultSet.getString("project_id")); + builder.withAuthorizingLaw(resultSet.getString("authorizing_law")); + builder.withProjectOwner(resultSet.getString("project_owner")); + builder.withHydropowerDesc(resultSet.getString("hydropower_description")); + builder.withSedimentationDesc(resultSet.getString("sedimentation_description")); + builder.withDownstreamUrbanDesc(resultSet.getString("downstream_urban_description")); + builder.withBankFullCapacityDesc(resultSet.getString("bank_full_capacity_description")); + builder.withPumpBackLocationId(resultSet.getString("pump_back_location_id")); + builder.withPumpBackOfficeId(resultSet.getString("pump_back_office_id")); + builder.withNearGageLocationId(resultSet.getString("near_gage_location_id")); + builder.withNearGageOfficeId(resultSet.getString("near_gage_office_id")); + builder.withProjectRemarks(resultSet.getString("project_remarks")); + + BigDecimal federalCost = resultSet.getBigDecimal("federal_cost"); + if (federalCost != null) { + builder.withFederalCost(federalCost.doubleValue()); + } + + BigDecimal nonfederalCost = resultSet.getBigDecimal("nonfederal_cost"); + if (nonfederalCost != null) { + builder.withNonFederalCost(nonfederalCost.doubleValue()); + } + + BigDecimal federalOmCost = resultSet.getBigDecimal("FEDERAL_OM_COST"); + if (federalOmCost != null) { + builder.withFederalOAndMCost(federalOmCost.doubleValue()); + } + BigDecimal nonfederalOmCost = resultSet.getBigDecimal("NONFEDERAL_OM_COST"); + if (nonfederalOmCost != null) { + builder.withNonFederalOAndMCost(nonfederalOmCost.doubleValue()); + } + + Timestamp costStamp = resultSet.getTimestamp("COST_YEAR", UTC_CALENDAR); + if (costStamp != null) { + builder.withCostYear(costStamp.toInstant()); + } + + Timestamp yieldTimeFrameStart = resultSet.getTimestamp("yield_time_frame_start", + UTC_CALENDAR); + if (yieldTimeFrameStart != null) { + builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); + } + Timestamp yieldTimeFrameEnd = resultSet.getTimestamp("yield_time_frame_end", UTC_CALENDAR); + if (yieldTimeFrameEnd != null) { + builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); + } + + return builder.build(); + } + + private void fillTableQueryParameters(PreparedStatement ps, String projectIdMask, String office, + String cursorOffice, String cursorProjectId, + int finalPageSize) throws SQLException { + int index = 1; + if (projectIdMask != null) { + ps.setString(index++, projectIdMask); + } + + if (office != null) { + ps.setString(index++, office); + } + + if (cursorOffice != null) { + ps.setString(index++, cursorOffice); + ps.setString(index++, cursorProjectId); + ps.setString(index++, cursorOffice); // its in there twice.... + } + + ps.setInt(index, finalPageSize); + } + + private static String buildTableQuery(@Nullable String projectIdMask, @Nullable String office, + String cursorOffice, String cursorProjectId) { + String sql = SELECT_PART; + + if (projectIdMask != null || office != null || cursorOffice != null || cursorProjectId != null) { + sql += "where ("; + + if (projectIdMask != null && office != null) { + sql += "(regexp_like(project.location_id, ?, 'i'))\n" // projectIdMask + + " and office_id = ?\n"; + } else if (projectIdMask != null) { + sql += "(regexp_like(project.location_id, ?, 'i'))\n"; // projectIdMask + } else if (office != null) { + sql += "office_id = ?\n"; // office + } + + if (cursorOffice != null || cursorProjectId != null) { + sql += " and (\n" + + " (\n" + + " OFFICE_ID = ?\n" // cursorOffice + + " and project.location_id > ?\n" //cursorProjectId + + " )\n" + + " or OFFICE_ID > ?\n" // cursorOffice + + " )\n"; + } + + sql += ")\n"; + } + + sql += "order by office_id, project_id fetch next ? rows only"; // pageSize + + return sql; + } + public void create(Project project) { boolean failIfExists = true; String office = project.getOfficeId(); @@ -491,7 +547,6 @@ private PROJECT_OBJ_T toProjectT(Project project) { project.getPumpBackOfficeId())); } - LOCATION_OBJ_T nearGageLocation = null; if (project.getNearGageLocationId() != null && project.getNearGageOfficeId() != null) { nearGageLocation = new LOCATION_OBJ_T(); @@ -500,12 +555,16 @@ private PROJECT_OBJ_T toProjectT(Project project) { } String authorizingLaw = project.getAuthorizingLaw(); - Timestamp costYear = project.getCostYear() == null ? null : Timestamp.from(project.getCostYear()); - BigDecimal federalCost = project.getFederalCost() == null ? null : BigDecimal.valueOf(project.getFederalCost()); - - BigDecimal nonFederalCost = (project.getNonFederalCost() != null) ? BigDecimal.valueOf(project.getNonFederalCost()) : null; - BigDecimal federalOandMCost = (project.getFederalOAndMCost() != null) ? BigDecimal.valueOf(project.getFederalOAndMCost()) : null; - BigDecimal nonFederalOandMCost = (project.getNonFederalOAndMCost() != null) ? BigDecimal.valueOf(project.getNonFederalOAndMCost()) : null; + Timestamp costYear = project.getCostYear() != null + ? Timestamp.from(project.getCostYear()) : null; + BigDecimal federalCost = project.getFederalCost() != null + ? BigDecimal.valueOf(project.getFederalCost()) : null; + BigDecimal nonFederalCost = (project.getNonFederalCost() != null) + ? BigDecimal.valueOf(project.getNonFederalCost()) : null; + BigDecimal federalOandMCost = (project.getFederalOAndMCost() != null) + ? BigDecimal.valueOf(project.getFederalOAndMCost()) : null; + BigDecimal nonFederalOandMCost = (project.getNonFederalOAndMCost() != null) + ? BigDecimal.valueOf(project.getNonFederalOAndMCost()) : null; String costUnitsId = project.getCostUnit(); String remarks = project.getProjectRemarks(); String projectOwner = project.getProjectOwner(); @@ -513,8 +572,10 @@ private PROJECT_OBJ_T toProjectT(Project project) { String sedimentationDescription = project.getSedimentationDesc(); String downstreamUrbanDescription = project.getDownstreamUrbanDesc(); String bankFullCapacityDescription = project.getBankFullCapacityDesc(); - Timestamp yieldTimeFrameStartTimestamp = (project.getYieldTimeFrameStart() != null) ? Timestamp.from(project.getYieldTimeFrameStart()) : null; - Timestamp yieldTimeFrameEndTimestamp = (project.getYieldTimeFrameEnd() != null) ? Timestamp.from(project.getYieldTimeFrameEnd()) : null; + Timestamp yieldTimeFrameStartTimestamp = (project.getYieldTimeFrameStart() != null) + ? Timestamp.from(project.getYieldTimeFrameStart()) : null; + Timestamp yieldTimeFrameEndTimestamp = (project.getYieldTimeFrameEnd() != null) + ? Timestamp.from(project.getYieldTimeFrameEnd()) : null; return new PROJECT_OBJ_T(projectLocation, pumpBackLocation, nearGageLocation, authorizingLaw, costYear, federalCost, nonFederalCost, federalOandMCost, nonFederalOandMCost, costUnitsId, remarks, projectOwner, hydropowerDescription, @@ -523,10 +584,6 @@ private PROJECT_OBJ_T toProjectT(Project project) { } public void delete(String office, String id, DeleteRule deleteRule) { -// Project project = retrieveProject(office, id); -// if (project == null) { -// throw new NotFoundException("Could not find project to delete."); -// } connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(getDslContext(c, office).configuration(), id, deleteRule.getRule(), office diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java index a8cfd0bc9..8e354089e 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java @@ -28,13 +28,18 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.fasterxml.jackson.databind.ObjectMapper; import cwms.cda.data.dto.Project; import cwms.cda.formatters.ContentType; import cwms.cda.formatters.Formats; +import cwms.cda.formatters.json.JsonV2; import fixtures.TestAccounts; import io.restassured.filter.log.LogDetail; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -269,4 +274,115 @@ void test_get_all() throws IOException { .statusCode(is(HttpServletResponse.SC_NO_CONTENT)) ; } + + @Test + void test_get_all_paged() throws IOException { + InputStream resource = this.getClass().getResourceAsStream("/cwms/cda/api/project.json"); + assertNotNull(resource); + String json = IOUtils.toString(resource, StandardCharsets.UTF_8); + assertNotNull(json); + Project project = Formats.parseContent(new ContentType(Formats.JSON), json, Project.class); + + // Structure of test: + // 1)Create Projects + // 2)Retrieve the Project with getAll and assert that it exists + // 3)Delete the Projects + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + + ObjectMapper om = JsonV2.buildObjectMapper(); + + for (int i = 0; i < 15; i++) { + Project.Builder builder = new Project.Builder(); + builder.from(project) + .withName(String.format("PageTest%2d", i)); + Project build = builder.build(); + String projJson = om.writeValueAsString(build); + + //Create the project + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(projJson) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + } + + String office = project.getOfficeId(); + try { + ExtractableResponse extractableResponse = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .queryParam(Controllers.PAGE_SIZE, 5) + .queryParam(Controllers.ID_MASK, "^PageTest.*$") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("projects[0].office-id", equalTo(office)) + .body("projects[0].name", equalTo("PageTest 0")) + .body("projects[1].name", equalTo("PageTest 1")) + .body("projects[2].name", equalTo("PageTest 2")) + .body("projects[3].name", equalTo("PageTest 3")) + .body("projects[4].name", equalTo("PageTest 4")) + .extract(); + + String next = extractableResponse.path("next-page"); + assertNotNull(next); + assertFalse(next.isEmpty()); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .queryParam(Controllers.PAGE, next) + .queryParam(Controllers.PAGE_SIZE, 5) + .queryParam(Controllers.ID_MASK, "^PageTest.*$") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("projects[0].office-id", equalTo(office)) + .body("projects[0].name", equalTo("PageTest 5")) + .body("projects[1].name", equalTo("PageTest 6")) + .body("projects[2].name", equalTo("PageTest 7")) + .body("projects[3].name", equalTo("PageTest 8")) + .body("projects[4].name", equalTo("PageTest 9")) + .extract(); + } finally { + for (int i = 0; i < 15; i++) { + // Delete the Projects + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete(String.format("projects/PageTest%2d", i)) + .then() + .log().ifValidationFails(LogDetail.ALL, true) + ; + + } + } + } + } From f544382f407669fb02bce0476e192b908c3c0b3b Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:08:11 -0700 Subject: [PATCH 4/8] Fixed comments. --- .../src/main/java/cwms/cda/api/ProjectController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java index cd7792838..64f170dc0 100644 --- a/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java +++ b/cwms-data-api/src/main/java/cwms/cda/api/ProjectController.java @@ -136,7 +136,7 @@ public void getAll(@NotNull Context ctx) { }, queryParams = { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the" - + " owning office of the Pool whose data is to be included in the" + + " owning office of the Project whose data is to be included in the" + " response."), }, responses = { @@ -211,9 +211,9 @@ public void create(@NotNull Context ctx) { } @OpenApi( - description = "Updates a text timeseries", + description = "Updates a project", pathParams = { - @OpenApiParam(name = NAME, description = "The id of the text timeseries to be updated"), + @OpenApiParam(name = NAME, description = "The id of the project to be updated"), }, requestBody = @OpenApiRequestBody( content = { @@ -245,13 +245,13 @@ public void update(@NotNull Context ctx, @NotNull String name) { @OpenApi( - description = "Deletes requested text timeseries id", + description = "Deletes requested reservoir project", pathParams = { @OpenApiParam(name = NAME, description = "The project identifier to be deleted"), }, queryParams = { @OpenApiParam(name = OFFICE, required = true, description = "Specifies the " - + "owning office of the timeseries identifier to be deleted"), + + "owning office of the project to be deleted"), @OpenApiParam(name = METHOD, type = JooqDao.DeleteMethod.class, description = "Specifies the delete method used. " + "Defaults to \"DELETE_KEY\"") From 8684bfcda1dea1b486062c989f9a0b92a2ecdfc6 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Mon, 10 Jun 2024 10:56:37 -0700 Subject: [PATCH 5/8] Project DTO changed to hold a Location. --- .../java/cwms/cda/data/dao/ProjectDao.java | 250 ++++++++++-------- 1 file changed, 141 insertions(+), 109 deletions(-) diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java index be274e380..2d3be3a3a 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java @@ -6,6 +6,7 @@ import cwms.cda.api.errors.NotFoundException; import cwms.cda.data.dto.CwmsDTOPaginated; +import cwms.cda.data.dto.Location; import cwms.cda.data.dto.Project; import cwms.cda.data.dto.Projects; import java.math.BigDecimal; @@ -41,81 +42,81 @@ public class ProjectDao extends JooqDao { private final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); public static final String SELECT_PART = "select project.office_id,\n" - + " project.location_id as project_id,\n" - + " project.COST_YEAR,\n" - + " project.federal_cost,\n" - + " project.nonfederal_cost,\n" - + " project.FEDERAL_OM_COST,\n" - + " project.NONFEDERAL_OM_COST,\n" - + " project.authorizing_law,\n" - + " project.project_owner,\n" - + " project.hydropower_description,\n" - + " project.sedimentation_description,\n" - + " project.downstream_urban_description,\n" - + " project.bank_full_capacity_description,\n" - + " pumpback.location_id as pump_back_location_id,\n" - + " pumpback.p_office_id as pump_back_office_id,\n" - + " neargage.location_id as near_gage_location_id,\n" - + " neargage.n_office_id as near_gage_office_id,\n" - + " project.yield_time_frame_start,\n" - + " project.yield_time_frame_end,\n" - + " project.project_remarks\n" - + "from ( select o.office_id as office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id,\n" - + " p.COST_YEAR,\n" - + " p.federal_cost,\n" - + " p.nonfederal_cost,\n" - + " p.FEDERAL_OM_COST,\n" - + " p.NONFEDERAL_OM_COST,\n" - + " p.authorizing_law,\n" - + " p.project_owner,\n" - + " p.hydropower_description,\n" - + " p.sedimentation_description,\n" - + " p.downstream_urban_description,\n" - + " p.bank_full_capacity_description,\n" - + " p.pump_back_location_code,\n" - + " p.near_gage_location_code,\n" - + " p.yield_time_frame_start,\n" - + " p.yield_time_frame_end,\n" - + " p.project_remarks\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) project\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as p_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as n_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) neargage on neargage.location_code = project.near_gage_location_code\n"; + + " project.location_id as project_id,\n" + + " project.COST_YEAR,\n" + + " project.federal_cost,\n" + + " project.nonfederal_cost,\n" + + " project.FEDERAL_OM_COST,\n" + + " project.NONFEDERAL_OM_COST,\n" + + " project.authorizing_law,\n" + + " project.project_owner,\n" + + " project.hydropower_description,\n" + + " project.sedimentation_description,\n" + + " project.downstream_urban_description,\n" + + " project.bank_full_capacity_description,\n" + + " pumpback.location_id as pump_back_location_id,\n" + + " pumpback.p_office_id as pump_back_office_id,\n" + + " neargage.location_id as near_gage_location_id,\n" + + " neargage.n_office_id as near_gage_office_id,\n" + + " project.yield_time_frame_start,\n" + + " project.yield_time_frame_end,\n" + + " project.project_remarks\n" + + "from ( select o.office_id as office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id,\n" + + " p.COST_YEAR,\n" + + " p.federal_cost,\n" + + " p.nonfederal_cost,\n" + + " p.FEDERAL_OM_COST,\n" + + " p.NONFEDERAL_OM_COST,\n" + + " p.authorizing_law,\n" + + " p.project_owner,\n" + + " p.hydropower_description,\n" + + " p.sedimentation_description,\n" + + " p.downstream_urban_description,\n" + + " p.bank_full_capacity_description,\n" + + " p.pump_back_location_code,\n" + + " p.near_gage_location_code,\n" + + " p.yield_time_frame_start,\n" + + " p.yield_time_frame_end,\n" + + " p.project_remarks\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) project\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as p_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" + + " left outer join\n" + + " ( select pl.location_code,\n" + + " o.office_id as n_office_id,\n" + + " bl.base_location_id\n" + + " ||substr('-', 1, length(pl.sub_location_id))\n" + + " ||pl.sub_location_id as location_id\n" + + " from cwms_20.cwms_office o,\n" + + " cwms_20.at_base_location bl,\n" + + " cwms_20.at_physical_location pl,\n" + + " cwms_20.at_project p\n" + + " where bl.db_office_code = o.office_code\n" + + " and pl.base_location_code = bl.base_location_code\n" + + " and p.project_location_code = pl.location_code\n" + + " ) neargage on neargage.location_code = project.near_gage_location_code\n"; public ProjectDao(DSLContext dsl) { @@ -174,8 +175,11 @@ private Project buildProject(PROJECT_OBJ_T projectObjT) { nearLocRef = neargageLocationType.getLocationRef(); } if (nearLocRef != null) { - builder = builder.withNearGageLocationId(nearLocRef.getOfficeId()) - .withNearGageLocationId(getLocationId(nearLocRef.getBaseLocationId(), nearLocRef.getSubLocationId())); + builder = builder.withNearGageLocation(new Location.Builder(nearLocRef.getOfficeId(), + getLocationId(nearLocRef.getBaseLocationId(), nearLocRef.getSubLocationId())) + .withActive(null) + .build() + ); } LocationType pumpbackLocationType = @@ -185,8 +189,11 @@ private Project buildProject(PROJECT_OBJ_T projectObjT) { pumpbackLocRef = pumpbackLocationType.getLocationRef(); } if (pumpbackLocRef != null) { - builder = builder.withPumpBackOfficeId(pumpbackLocRef.getOfficeId()) - .withPumpBackLocationId(getLocationId(pumpbackLocRef.getBaseLocationId(), pumpbackLocRef.getSubLocationId())); + builder = builder.withPumpBackLocation(new Location.Builder(pumpbackLocRef.getOfficeId(), + getLocationId(pumpbackLocRef.getBaseLocationId(), pumpbackLocRef.getSubLocationId())) + .withActive(null) + .build() + ); } BigDecimal federalCost = projectObjT.getFEDERAL_COST(); @@ -227,16 +234,21 @@ private Project buildProject(PROJECT_OBJ_T projectObjT) { return builder.build(); } + + private static Project buildProject(usace.cwms.db.jooq.codegen.tables.records.AV_PROJECT r) { Project.Builder builder = new Project.Builder(); builder.withOfficeId(r.getOFFICE_ID()); builder.withName(r.getPROJECT_ID()); - //builder.withPumpBackOfficeId(r.getPUMP_BACK_OFFICE_ID()); - builder.withPumpBackOfficeId(r.getOFFICE_ID()); // Can we assume same office? - builder.withPumpBackLocationId(r.getPUMP_BACK_LOCATION_ID()); - //builder.withNearGageOfficeId(r.getNEAR_GAGE_OFFICE_ID()); - builder.withNearGageOfficeId(r.getOFFICE_ID()); // Can we assume same office? - builder.withNearGageLocationId(r.getNEAR_GAGE_LOCATION_ID()); + builder.withPumpBackLocation(new Location.Builder(r.getOFFICE_ID(), r.getPUMP_BACK_LOCATION_ID()) + .withActive(null) + .build() + ); // Can we assume same office? + builder.withNearGageLocation(new Location.Builder(r.getOFFICE_ID(), r.getNEAR_GAGE_LOCATION_ID()) + .withActive(null) + .build() + ); // Can we assume same office? + builder.withAuthorizingLaw(r.getAUTHORIZING_LAW()); builder.withProjectRemarks(r.getPROJECT_REMARKS()); builder.withProjectOwner(r.getPROJECT_OWNER()); @@ -261,7 +273,8 @@ private static Project buildProject(usace.cwms.db.jooq.codegen.tables.records.AV builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); } - // The view is missing cost-year, fed_om_cat and nonfed_om_cost and the pump office and near gage office. + // The view is missing cost-year, fed_om_cat and nonfed_om_cost and the pump office and + // near gage office. return builder.build(); } @@ -271,9 +284,12 @@ public static String getLocationId(String base, String sub) { return hasSub ? base + "-" + sub : base; } - public Projects retrieveProjectsFromView(String cursor, int pageSize, String projectIdMask, String office) { + public Projects retrieveProjectsFromView(String cursor, int pageSize, String projectIdMask, + String office) { - Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); + Condition whereClause = + JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, + projectIdMask); if (office != null) { whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); } @@ -307,9 +323,9 @@ public Projects retrieveProjectsFromView(String cursor, int pageSize, String pro SelectLimitPercentStep query = dsl.selectFrom(AV_PROJECT.AV_PROJECT) - .where(whereClause.and(pagingCondition)) - .orderBy(AV_PROJECT.AV_PROJECT.OFFICE_ID, AV_PROJECT.AV_PROJECT.PROJECT_ID) - .limit(pageSize); + .where(whereClause.and(pagingCondition)) + .orderBy(AV_PROJECT.AV_PROJECT.OFFICE_ID, AV_PROJECT.AV_PROJECT.PROJECT_ID) + .limit(pageSize); List projs = query.fetch().map(ProjectDao::buildProject); @@ -319,7 +335,8 @@ public Projects retrieveProjectsFromView(String cursor, int pageSize, String pro } public Projects retrieveProjectsFromTable(String cursor, int pageSize, - @Nullable String projectIdMask, @Nullable String office) { + @Nullable String projectIdMask, + @Nullable String office) { final String cursorOffice; final String cursorProjectId; int total; @@ -327,7 +344,9 @@ public Projects retrieveProjectsFromTable(String cursor, int pageSize, cursorOffice = null; cursorProjectId = null; - Condition whereClause = JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, projectIdMask); + Condition whereClause = + JooqDao.caseInsensitiveLikeRegexNullTrue(AV_PROJECT.AV_PROJECT.PROJECT_ID, + projectIdMask); if (office != null) { whereClause = whereClause.and(AV_PROJECT.AV_PROJECT.OFFICE_ID.eq(office)); } @@ -351,9 +370,10 @@ public Projects retrieveProjectsFromTable(String cursor, int pageSize, int finalPageSize = pageSize; List projs = connectionResult(dsl, c -> { - List projects = null; + List projects; try (PreparedStatement ps = c.prepareStatement(query)) { - fillTableQueryParameters(ps, projectIdMask, office, cursorOffice, cursorProjectId, finalPageSize); + fillTableQueryParameters(ps, projectIdMask, office, cursorOffice, cursorProjectId + , finalPageSize); try (ResultSet resultSet = ps.executeQuery()) { projects = new ArrayList<>(); @@ -381,10 +401,18 @@ private Project buildProjectFromTableRow(ResultSet resultSet) throws SQLExceptio builder.withSedimentationDesc(resultSet.getString("sedimentation_description")); builder.withDownstreamUrbanDesc(resultSet.getString("downstream_urban_description")); builder.withBankFullCapacityDesc(resultSet.getString("bank_full_capacity_description")); - builder.withPumpBackLocationId(resultSet.getString("pump_back_location_id")); - builder.withPumpBackOfficeId(resultSet.getString("pump_back_office_id")); - builder.withNearGageLocationId(resultSet.getString("near_gage_location_id")); - builder.withNearGageOfficeId(resultSet.getString("near_gage_office_id")); + builder.withPumpBackLocation( + new Location.Builder(resultSet.getString("pump_back_office_id"), + resultSet.getString("pump_back_location_id")) + .build() + ); + + builder.withNearGageLocation( + new Location.Builder(resultSet.getString("near_gage_office_id"), + resultSet.getString("near_gage_location_id")) + .build() + ); + builder.withProjectRemarks(resultSet.getString("project_remarks")); BigDecimal federalCost = resultSet.getBigDecimal("federal_cost"); @@ -484,7 +512,8 @@ public void create(Project project) { String office = project.getOfficeId(); PROJECT_OBJ_T projectT = toProjectT(project); - connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + connection(dsl, + c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), projectT, OracleTypeMap.formatBool(failIfExists))); } @@ -518,7 +547,8 @@ public void store(Project project, boolean failIfExists) { String office = project.getOfficeId(); PROJECT_OBJ_T projectT = toProjectT(project); - connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + connection(dsl, + c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), projectT, OracleTypeMap.formatBool(failIfExists))); } @@ -531,7 +561,8 @@ public void update(Project project) { } PROJECT_OBJ_T projectT = toProjectT(project); - connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), + connection(dsl, + c -> CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(getDslContext(c, office).configuration(), projectT, OracleTypeMap.formatBool(false))); } @@ -541,17 +572,17 @@ private PROJECT_OBJ_T toProjectT(Project project) { projectLocation.setLOCATION_REF(getLocationRefT(project.getName(), project.getOfficeId())); LOCATION_OBJ_T pumpBackLocation = null; - if (project.getPumpBackLocationId() != null && project.getPumpBackOfficeId() != null) { + Location pb = project.getPumpBackLocation(); + if (pb != null) { pumpBackLocation = new LOCATION_OBJ_T(); - pumpBackLocation.setLOCATION_REF(getLocationRefT(project.getPumpBackLocationId(), - project.getPumpBackOfficeId())); + pumpBackLocation.setLOCATION_REF(getLocationRefT(pb.getName(), pb.getOfficeId())); } LOCATION_OBJ_T nearGageLocation = null; - if (project.getNearGageLocationId() != null && project.getNearGageOfficeId() != null) { + Location ng = project.getNearGageLocation(); + if (ng != null) { nearGageLocation = new LOCATION_OBJ_T(); - nearGageLocation.setLOCATION_REF(getLocationRefT(project.getNearGageLocationId(), - project.getNearGageOfficeId())); + nearGageLocation.setLOCATION_REF(getLocationRefT(ng.getName(), ng.getOfficeId())); } String authorizingLaw = project.getAuthorizingLaw(); @@ -585,7 +616,8 @@ private PROJECT_OBJ_T toProjectT(Project project) { public void delete(String office, String id, DeleteRule deleteRule) { - connection(dsl, c -> CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(getDslContext(c, office).configuration(), + connection(dsl, + c -> CWMS_PROJECT_PACKAGE.call_DELETE_PROJECT(getDslContext(c, office).configuration(), id, deleteRule.getRule(), office )); } From 7931f919ef1c8ed35922a211953ed42ffeae3be0 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Mon, 10 Jun 2024 11:08:54 -0700 Subject: [PATCH 6/8] Project DTO changed to hold a Location. --- .../src/test/resources/cwms/cda/api/project.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/project.json b/cwms-data-api/src/test/resources/cwms/cda/api/project.json index bdf23508c..2b7221c92 100644 --- a/cwms-data-api/src/test/resources/cwms/cda/api/project.json +++ b/cwms-data-api/src/test/resources/cwms/cda/api/project.json @@ -13,10 +13,14 @@ "sedimentation-desc" : "Sedimentation Description", "downstream-urban-desc" : "Downstream Urban Description", "bank-full-capacity-desc" : "Bank Full Capacity Description", - "pump-back-location-id" : "Pumpback Location Id", - "pump-back-office-id" : "SPK", - "near-gage-location-id" : "Near Gage Location Id", - "near-gage-office-id" : "SPK", + "pump-back-location" : { + "office-id" : "SPK", + "name" : "Pumpback Location Id" + }, + "near-gage-location" : { + "office-id" : "SPK", + "name" : "Near Gage Location Id" + }, "yield-time-frame-start" : 1717282800000, "yield-time-frame-end" : 1717308000000, "project-remarks" : "Remarks" From 667f6047822e7f20535d49af54f11944f7a0ceee Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Mon, 10 Jun 2024 11:09:44 -0700 Subject: [PATCH 7/8] Indentation tweak. --- .../cwms/cda/api/ProjectControllerIT.java | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java index 8e354089e..8d8823e20 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/ProjectControllerIT.java @@ -300,19 +300,19 @@ void test_get_all_paged() throws IOException { //Create the project given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .contentType(Formats.JSON) - .body(projJson) - .header(AUTH_HEADER, user.toHeaderValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .post("/projects/") - .then() - .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_CREATED)); + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .contentType(Formats.JSON) + .body(projJson) + .header(AUTH_HEADER, user.toHeaderValue()) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); } String office = project.getOfficeId(); @@ -323,13 +323,13 @@ void test_get_all_paged() throws IOException { .queryParam(Controllers.OFFICE, office) .queryParam(Controllers.PAGE_SIZE, 5) .queryParam(Controllers.ID_MASK, "^PageTest.*$") - .when() + .when() .redirects().follow(true) .redirects().max(3) .get("projects/") - .then() + .then() .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() + .assertThat() .statusCode(is(HttpServletResponse.SC_OK)) .body("projects[0].office-id", equalTo(office)) .body("projects[0].name", equalTo("PageTest 0")) @@ -344,41 +344,41 @@ void test_get_all_paged() throws IOException { assertFalse(next.isEmpty()); given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(Formats.JSON) + .queryParam(Controllers.OFFICE, office) + .queryParam(Controllers.PAGE, next) + .queryParam(Controllers.PAGE_SIZE, 5) + .queryParam(Controllers.ID_MASK, "^PageTest.*$") + .when() + .redirects().follow(true) + .redirects().max(3) + .get("projects/") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("projects[0].office-id", equalTo(office)) + .body("projects[0].name", equalTo("PageTest 5")) + .body("projects[1].name", equalTo("PageTest 6")) + .body("projects[2].name", equalTo("PageTest 7")) + .body("projects[3].name", equalTo("PageTest 8")) + .body("projects[4].name", equalTo("PageTest 9")) + ; + } finally { + for (int i = 0; i < 15; i++) { + // Delete the Projects + given() .log().ifValidationFails(LogDetail.ALL, true) .accept(Formats.JSON) .queryParam(Controllers.OFFICE, office) - .queryParam(Controllers.PAGE, next) - .queryParam(Controllers.PAGE_SIZE, 5) - .queryParam(Controllers.ID_MASK, "^PageTest.*$") - .when() + .header(AUTH_HEADER, user.toHeaderValue()) + .when() .redirects().follow(true) .redirects().max(3) - .get("projects/") - .then() + .delete(String.format("projects/PageTest%2d", i)) + .then() .log().ifValidationFails(LogDetail.ALL, true) - .assertThat() - .statusCode(is(HttpServletResponse.SC_OK)) - .body("projects[0].office-id", equalTo(office)) - .body("projects[0].name", equalTo("PageTest 5")) - .body("projects[1].name", equalTo("PageTest 6")) - .body("projects[2].name", equalTo("PageTest 7")) - .body("projects[3].name", equalTo("PageTest 8")) - .body("projects[4].name", equalTo("PageTest 9")) - .extract(); - } finally { - for (int i = 0; i < 15; i++) { - // Delete the Projects - given() - .log().ifValidationFails(LogDetail.ALL, true) - .accept(Formats.JSON) - .queryParam(Controllers.OFFICE, office) - .header(AUTH_HEADER, user.toHeaderValue()) - .when() - .redirects().follow(true) - .redirects().max(3) - .delete(String.format("projects/PageTest%2d", i)) - .then() - .log().ifValidationFails(LogDetail.ALL, true) ; } From 8c8ad007aab8f5299ea70ad13fdef34bb2988ad6 Mon Sep 17 00:00:00 2001 From: rma-rripken <89810919+rma-rripken@users.noreply.github.com> Date: Mon, 10 Jun 2024 13:18:35 -0700 Subject: [PATCH 8/8] Moved query into resource. Extracted some constants. --- .../java/cwms/cda/data/dao/ProjectDao.java | 139 ++++++------------ .../cwms/data/sql/project/project_select.sql | 76 ++++++++++ 2 files changed, 119 insertions(+), 96 deletions(-) create mode 100644 cwms-data-api/src/main/resources/cwms/data/sql/project/project_select.sql diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java index 2d3be3a3a..955c9f461 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/ProjectDao.java @@ -9,6 +9,7 @@ import cwms.cda.data.dto.Location; import cwms.cda.data.dto.Project; import cwms.cda.data.dto.Projects; +import cwms.cda.helpers.ResourceHelper; import java.math.BigDecimal; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -38,85 +39,31 @@ public class ProjectDao extends JooqDao { private static final Logger logger = Logger.getLogger(ProjectDao.class.getName()); + public static final String OFFICE_ID = "office_id"; + public static final String PROJECT_ID = "project_id"; + public static final String AUTHORIZING_LAW = "authorizing_law"; + public static final String PROJECT_OWNER = "project_owner"; + public static final String HYDROPOWER_DESCRIPTION = "hydropower_description"; + public static final String SEDIMENTATION_DESCRIPTION = "sedimentation_description"; + public static final String DOWNSTREAM_URBAN_DESCRIPTION = "downstream_urban_description"; + public static final String BANK_FULL_CAPACITY_DESCRIPTION = "bank_full_capacity_description"; + public static final String PUMP_BACK_OFFICE_ID = "pump_back_office_id"; + public static final String PUMP_BACK_LOCATION_ID = "pump_back_location_id"; + public static final String NEAR_GAGE_OFFICE_ID = "near_gage_office_id"; + public static final String NEAR_GAGE_LOCATION_ID = "near_gage_location_id"; + public static final String PROJECT_REMARKS = "project_remarks"; + public static final String FEDERAL_COST = "federal_cost"; + public static final String NONFEDERAL_COST = "nonfederal_cost"; + public static final String FEDERAL_OM_COST = "FEDERAL_OM_COST"; + public static final String NONFEDERAL_OM_COST = "NONFEDERAL_OM_COST"; + public static final String COST_YEAR = "COST_YEAR"; + public static final String YIELD_TIME_FRAME_START = "yield_time_frame_start"; + public static final String YIELD_TIME_FRAME_END = "yield_time_frame_end"; private final Calendar UTC_CALENDAR = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - public static final String SELECT_PART = "select project.office_id,\n" - + " project.location_id as project_id,\n" - + " project.COST_YEAR,\n" - + " project.federal_cost,\n" - + " project.nonfederal_cost,\n" - + " project.FEDERAL_OM_COST,\n" - + " project.NONFEDERAL_OM_COST,\n" - + " project.authorizing_law,\n" - + " project.project_owner,\n" - + " project.hydropower_description,\n" - + " project.sedimentation_description,\n" - + " project.downstream_urban_description,\n" - + " project.bank_full_capacity_description,\n" - + " pumpback.location_id as pump_back_location_id,\n" - + " pumpback.p_office_id as pump_back_office_id,\n" - + " neargage.location_id as near_gage_location_id,\n" - + " neargage.n_office_id as near_gage_office_id,\n" - + " project.yield_time_frame_start,\n" - + " project.yield_time_frame_end,\n" - + " project.project_remarks\n" - + "from ( select o.office_id as office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id,\n" - + " p.COST_YEAR,\n" - + " p.federal_cost,\n" - + " p.nonfederal_cost,\n" - + " p.FEDERAL_OM_COST,\n" - + " p.NONFEDERAL_OM_COST,\n" - + " p.authorizing_law,\n" - + " p.project_owner,\n" - + " p.hydropower_description,\n" - + " p.sedimentation_description,\n" - + " p.downstream_urban_description,\n" - + " p.bank_full_capacity_description,\n" - + " p.pump_back_location_code,\n" - + " p.near_gage_location_code,\n" - + " p.yield_time_frame_start,\n" - + " p.yield_time_frame_end,\n" - + " p.project_remarks\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) project\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as p_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) pumpback on pumpback.location_code = project.pump_back_location_code\n" - + " left outer join\n" - + " ( select pl.location_code,\n" - + " o.office_id as n_office_id,\n" - + " bl.base_location_id\n" - + " ||substr('-', 1, length(pl.sub_location_id))\n" - + " ||pl.sub_location_id as location_id\n" - + " from cwms_20.cwms_office o,\n" - + " cwms_20.at_base_location bl,\n" - + " cwms_20.at_physical_location pl,\n" - + " cwms_20.at_project p\n" - + " where bl.db_office_code = o.office_code\n" - + " and pl.base_location_code = bl.base_location_code\n" - + " and p.project_location_code = pl.location_code\n" - + " ) neargage on neargage.location_code = project.near_gage_location_code\n"; + private static final String SELECT_PART = + ResourceHelper.getResourceAsString("/cwms/data/sql/project/project_select.sql", ProjectDao.class); public ProjectDao(DSLContext dsl) { @@ -393,58 +340,58 @@ public Projects retrieveProjectsFromTable(String cursor, int pageSize, private Project buildProjectFromTableRow(ResultSet resultSet) throws SQLException { Project.Builder builder = new Project.Builder(); - builder.withOfficeId(resultSet.getString("office_id")); - builder.withName(resultSet.getString("project_id")); - builder.withAuthorizingLaw(resultSet.getString("authorizing_law")); - builder.withProjectOwner(resultSet.getString("project_owner")); - builder.withHydropowerDesc(resultSet.getString("hydropower_description")); - builder.withSedimentationDesc(resultSet.getString("sedimentation_description")); - builder.withDownstreamUrbanDesc(resultSet.getString("downstream_urban_description")); - builder.withBankFullCapacityDesc(resultSet.getString("bank_full_capacity_description")); + builder.withOfficeId(resultSet.getString(OFFICE_ID)); + builder.withName(resultSet.getString(PROJECT_ID)); + builder.withAuthorizingLaw(resultSet.getString(AUTHORIZING_LAW)); + builder.withProjectOwner(resultSet.getString(PROJECT_OWNER)); + builder.withHydropowerDesc(resultSet.getString(HYDROPOWER_DESCRIPTION)); + builder.withSedimentationDesc(resultSet.getString(SEDIMENTATION_DESCRIPTION)); + builder.withDownstreamUrbanDesc(resultSet.getString(DOWNSTREAM_URBAN_DESCRIPTION)); + builder.withBankFullCapacityDesc(resultSet.getString(BANK_FULL_CAPACITY_DESCRIPTION)); builder.withPumpBackLocation( - new Location.Builder(resultSet.getString("pump_back_office_id"), - resultSet.getString("pump_back_location_id")) + new Location.Builder(resultSet.getString(PUMP_BACK_OFFICE_ID), + resultSet.getString(PUMP_BACK_LOCATION_ID)) .build() ); builder.withNearGageLocation( - new Location.Builder(resultSet.getString("near_gage_office_id"), - resultSet.getString("near_gage_location_id")) + new Location.Builder(resultSet.getString(NEAR_GAGE_OFFICE_ID), + resultSet.getString(NEAR_GAGE_LOCATION_ID)) .build() ); - builder.withProjectRemarks(resultSet.getString("project_remarks")); + builder.withProjectRemarks(resultSet.getString(PROJECT_REMARKS)); - BigDecimal federalCost = resultSet.getBigDecimal("federal_cost"); + BigDecimal federalCost = resultSet.getBigDecimal(FEDERAL_COST); if (federalCost != null) { builder.withFederalCost(federalCost.doubleValue()); } - BigDecimal nonfederalCost = resultSet.getBigDecimal("nonfederal_cost"); + BigDecimal nonfederalCost = resultSet.getBigDecimal(NONFEDERAL_COST); if (nonfederalCost != null) { builder.withNonFederalCost(nonfederalCost.doubleValue()); } - BigDecimal federalOmCost = resultSet.getBigDecimal("FEDERAL_OM_COST"); + BigDecimal federalOmCost = resultSet.getBigDecimal(FEDERAL_OM_COST); if (federalOmCost != null) { builder.withFederalOAndMCost(federalOmCost.doubleValue()); } - BigDecimal nonfederalOmCost = resultSet.getBigDecimal("NONFEDERAL_OM_COST"); + BigDecimal nonfederalOmCost = resultSet.getBigDecimal(NONFEDERAL_OM_COST); if (nonfederalOmCost != null) { builder.withNonFederalOAndMCost(nonfederalOmCost.doubleValue()); } - Timestamp costStamp = resultSet.getTimestamp("COST_YEAR", UTC_CALENDAR); + Timestamp costStamp = resultSet.getTimestamp(COST_YEAR, UTC_CALENDAR); if (costStamp != null) { builder.withCostYear(costStamp.toInstant()); } - Timestamp yieldTimeFrameStart = resultSet.getTimestamp("yield_time_frame_start", + Timestamp yieldTimeFrameStart = resultSet.getTimestamp(YIELD_TIME_FRAME_START, UTC_CALENDAR); if (yieldTimeFrameStart != null) { builder.withYieldTimeFrameStart(yieldTimeFrameStart.toInstant()); } - Timestamp yieldTimeFrameEnd = resultSet.getTimestamp("yield_time_frame_end", UTC_CALENDAR); + Timestamp yieldTimeFrameEnd = resultSet.getTimestamp(YIELD_TIME_FRAME_END, UTC_CALENDAR); if (yieldTimeFrameEnd != null) { builder.withYieldTimeFrameEnd(yieldTimeFrameEnd.toInstant()); } diff --git a/cwms-data-api/src/main/resources/cwms/data/sql/project/project_select.sql b/cwms-data-api/src/main/resources/cwms/data/sql/project/project_select.sql new file mode 100644 index 000000000..0dbf5be4b --- /dev/null +++ b/cwms-data-api/src/main/resources/cwms/data/sql/project/project_select.sql @@ -0,0 +1,76 @@ +select project.office_id, + project.location_id as project_id, + project.COST_YEAR, + project.federal_cost, + project.nonfederal_cost, + project.FEDERAL_OM_COST, + project.NONFEDERAL_OM_COST, + project.authorizing_law, + project.project_owner, + project.hydropower_description, + project.sedimentation_description, + project.downstream_urban_description, + project.bank_full_capacity_description, + pumpback.location_id as pump_back_location_id, + pumpback.p_office_id as pump_back_office_id, + neargage.location_id as near_gage_location_id, + neargage.n_office_id as near_gage_office_id, + project.yield_time_frame_start, + project.yield_time_frame_end, + project.project_remarks +from ( select o.office_id as office_id, + bl.base_location_id + ||substr('-', 1, length(pl.sub_location_id)) + ||pl.sub_location_id as location_id, + p.COST_YEAR, + p.federal_cost, + p.nonfederal_cost, + p.FEDERAL_OM_COST, + p.NONFEDERAL_OM_COST, + p.authorizing_law, + p.project_owner, + p.hydropower_description, + p.sedimentation_description, + p.downstream_urban_description, + p.bank_full_capacity_description, + p.pump_back_location_code, + p.near_gage_location_code, + p.yield_time_frame_start, + p.yield_time_frame_end, + p.project_remarks + from cwms_20.cwms_office o, + cwms_20.at_base_location bl, + cwms_20.at_physical_location pl, + cwms_20.at_project p + where bl.db_office_code = o.office_code + and pl.base_location_code = bl.base_location_code + and p.project_location_code = pl.location_code + ) project + left outer join + ( select pl.location_code, + o.office_id as p_office_id, + bl.base_location_id + ||substr('-', 1, length(pl.sub_location_id)) + ||pl.sub_location_id as location_id + from cwms_20.cwms_office o, + cwms_20.at_base_location bl, + cwms_20.at_physical_location pl, + cwms_20.at_project p + where bl.db_office_code = o.office_code + and pl.base_location_code = bl.base_location_code + and p.project_location_code = pl.location_code + ) pumpback on pumpback.location_code = project.pump_back_location_code + left outer join + ( select pl.location_code, + o.office_id as n_office_id, + bl.base_location_id + ||substr('-', 1, length(pl.sub_location_id)) + ||pl.sub_location_id as location_id + from cwms_20.cwms_office o, + cwms_20.at_base_location bl, + cwms_20.at_physical_location pl, + cwms_20.at_project p + where bl.db_office_code = o.office_code + and pl.base_location_code = bl.base_location_code + and p.project_location_code = pl.location_code + ) neargage on neargage.location_code = project.near_gage_location_code