Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions cwms-data-api/src/main/java/cwms/cda/api/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@
import io.javalin.core.validation.Validator;
import io.javalin.http.Context;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -174,10 +172,11 @@ public final class Controllers {
public static final String REPLACE_ASSIGNED_LOCS = "replace-assigned-locs";
public static final String REPLACE_ASSIGNED_TS = "replace-assigned-ts";
public static final String TS_IDS = "ts-ids";
public static final String IGNORE_MISSING = "ignore-missing";

public static final String EXAMPLE_DATE = "2021-06-10T13:00:00-07:00";
public static final String TIME_FORMAT_DESC = "The <a href=\"times.html\">format for this field</a> " +
"is ISO 8601 extended in UTC, e.g., 2026-06-18T19:42:00Z";
public static final String TIME_FORMAT_DESC = "The <a href=\"times.html\">format for this field</a> "
+ "is ISO 8601 extended in UTC, e.g., 2026-06-18T19:42:00Z";

public static final String INCLUDE_ASSIGNED = "include-assigned";
public static final String ANY_MASK = "*";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,7 @@
package cwms.cda.api;

import static com.codahale.metrics.MetricRegistry.name;
import static cwms.cda.api.Controllers.CASCADE_DELETE;
import static cwms.cda.api.Controllers.CATEGORY_ID;
import static cwms.cda.api.Controllers.CATEGORY_OFFICE_ID;
import static cwms.cda.api.Controllers.CREATE;
import static cwms.cda.api.Controllers.CWMS_OFFICE;
import static cwms.cda.api.Controllers.GET_ALL;
import static cwms.cda.api.Controllers.GET_ONE;
import static cwms.cda.api.Controllers.GROUP_ID;
import static cwms.cda.api.Controllers.GROUP_OFFICE_ID;
import static cwms.cda.api.Controllers.INCLUDE_ASSIGNED;
import static cwms.cda.api.Controllers.LOCATION_CATEGORY_LIKE;
import static cwms.cda.api.Controllers.OFFICE;
import static cwms.cda.api.Controllers.REPLACE_ASSIGNED_LOCS;
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.UPDATE;
import static cwms.cda.api.Controllers.*;
import static cwms.cda.api.Controllers.queryParamAsClass;
import static cwms.cda.api.Controllers.requiredParam;
import static cwms.cda.data.dao.JooqDao.getDslContext;
Expand All @@ -54,21 +38,25 @@
import com.google.common.flogger.FluentLogger;
import cwms.cda.api.errors.CdaError;
import cwms.cda.data.dao.LocationGroupDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.LocationGroup;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.csv.CsvV1LocationGroup;
import io.javalin.apibuilder.CrudHandler;
import io.javalin.core.util.Header;
import io.javalin.http.Context;
import io.javalin.http.HttpCode;
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.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.servlet.http.HttpServletResponse;
import org.geojson.FeatureCollection;
Expand Down Expand Up @@ -260,6 +248,10 @@ public void getOne(@NotNull Context ctx, @NotNull String groupId) {
@OpenApiContent(from = LocationGroup.class, type = Formats.JSON)
},
required = true),
queryParams = {
@OpenApiParam(name = IGNORE_MISSING, description = "Specifies whether to fail when attempting "
+ "to assign a location that does not exist. Default is false.", type = Boolean.class)
},
method = HttpMethod.POST,
tags = {TAG}
)
Expand All @@ -272,6 +264,7 @@ public void create(@NotNull Context ctx) {
String body = ctx.body();
ContentType contentType = Formats.parseHeader(formatHeader, LocationGroup.class);
LocationGroup deserialize = Formats.parseContent(contentType, body, LocationGroup.class);
boolean ignoreMissing = ctx.queryParamAsClass(IGNORE_MISSING, Boolean.class).getOrDefault(false);

if (!deserialize.getLocationCategory().getOfficeId().equalsIgnoreCase(CWMS_OFFICE)
&& (!deserialize.getOfficeId().equalsIgnoreCase(deserialize.getLocationCategory().getOfficeId())
Expand All @@ -281,8 +274,26 @@ public void create(@NotNull Context ctx) {
}

LocationGroupDao dao = new LocationGroupDao(dsl);
dao.create(deserialize);
ctx.status(HttpServletResponse.SC_CREATED);
List<CwmsId> missingLocations = dao.create(deserialize, ignoreMissing);
if (missingLocations.isEmpty()) {
ctx.status(HttpServletResponse.SC_CREATED);
} else {
Map<String, String> details = new HashMap<>();
StringBuilder sb = new StringBuilder();
for (CwmsId missingLocation : missingLocations) {
sb.append(missingLocation.getName());
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
details.put("missing-locations", sb.toString());
if (ignoreMissing) {
ctx.status(HttpCode.MULTI_STATUS);
} else {
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
details.put("message", "One or more locations could not be assigned to the location group.");
}
ctx.json(details);
}
}
}

Expand All @@ -306,6 +317,8 @@ public void create(@NotNull Context ctx) {
+ "office of the user making the request. This is the office that the location, group, and category "
+ "belong to. If the group and/or category belong to the CWMS office, "
+ "this only identifies the location."),
@OpenApiParam(name = IGNORE_MISSING, description = "Specifies whether to fail when attempting "
+ "to assign a location that does not exist. Default is false.", type = Boolean.class)
},
method = HttpMethod.PATCH,
tags = {TAG}
Expand All @@ -322,15 +335,28 @@ public void update(@NotNull Context ctx, @NotNull String groupId) {
LocationGroup deserialize = Formats.parseContent(contentType, body, LocationGroup.class);
boolean replaceAssignedLocs = ctx.queryParamAsClass(REPLACE_ASSIGNED_LOCS,
Boolean.class).getOrDefault(false);
boolean ignoreMissing = ctx.queryParamAsClass(IGNORE_MISSING, Boolean.class).getOrDefault(false);
LocationGroupDao locationGroupDao = new LocationGroupDao(dsl);
if (!office.equalsIgnoreCase(CWMS_OFFICE) && !groupId.equals(deserialize.getId())) {
locationGroupDao.renameLocationGroup(groupId, deserialize);
}
if (replaceAssignedLocs) {
locationGroupDao.unassignAllLocs(deserialize, office);
}
locationGroupDao.assignLocs(deserialize, office);
ctx.status(HttpServletResponse.SC_OK);
List<CwmsId> missingLocations = locationGroupDao.assignLocs(deserialize, office, ignoreMissing);
if (missingLocations.isEmpty()) {
ctx.status(HttpServletResponse.SC_OK);
} else {
Map<String, String> details = new HashMap<>();
details.put("missing_locations", Formats.format(contentType, missingLocations, CwmsId.class));
if (ignoreMissing) {
ctx.status(HttpCode.MULTI_STATUS);
} else {
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
details.put("message", "One or more locations could not be assigned to the location group.");
}
ctx.json(details);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import static cwms.cda.api.Controllers.GET_ONE;
import static cwms.cda.api.Controllers.GROUP_ID;
import static cwms.cda.api.Controllers.GROUP_OFFICE_ID;
import static cwms.cda.api.Controllers.IGNORE_MISSING;
import static cwms.cda.api.Controllers.IGNORE_NULLS;
import static cwms.cda.api.Controllers.INCLUDE_ASSIGNED;
import static cwms.cda.api.Controllers.OFFICE;
Expand All @@ -57,6 +58,7 @@
import com.google.common.flogger.FluentLogger;
import cwms.cda.api.errors.CdaError;
import cwms.cda.data.dao.TimeSeriesGroupDao;
import cwms.cda.data.dto.CwmsId;
import cwms.cda.data.dto.TimeSeriesGroup;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
Expand All @@ -71,7 +73,9 @@
import io.javalin.plugin.openapi.annotations.OpenApiRequestBody;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;
Expand Down Expand Up @@ -243,6 +247,8 @@ public void getOne(@NotNull Context ctx, @NotNull String groupId) {
queryParams = {
@OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class,
description = "Create will fail if provided ID already exists. Default: true"),
@OpenApiParam(name = IGNORE_MISSING, type = Boolean.class, description = "If true, do not fail when "
+ "attempting to assign a time series that does not exist to the group"),
@OpenApiParam(name = IGNORE_NULLS, type = Boolean.class,
description = "Ignore null values in the request body. Caution, if " + FAIL_IF_EXISTS
+ " is false and " + IGNORE_NULLS + " is false, then the create will proceed whether "
Expand Down Expand Up @@ -276,9 +282,31 @@ public void create(@NotNull Context ctx) {

boolean ignoreNulls = ctx.queryParamAsClass(IGNORE_NULLS, Boolean.class).getOrDefault(true);
boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true);
boolean ignoreMissing = ctx.queryParamAsClass(IGNORE_MISSING, Boolean.class).getOrDefault(false);
TimeSeriesGroupDao dao = new TimeSeriesGroupDao(dsl);
dao.create(deserialize, failIfExists, ignoreNulls);
ctx.status(HttpServletResponse.SC_CREATED);
List<CwmsId> missingTimeSeries = dao.create(deserialize, failIfExists, ignoreNulls, ignoreMissing);
if (missingTimeSeries.isEmpty()) {
ctx.status(HttpServletResponse.SC_CREATED);
} else {
Map<String, String> detailsMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
for (CwmsId cwmsId : missingTimeSeries) {
sb.append(cwmsId.getName());
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
detailsMap.put("missing-time-series", sb.toString());
if (ignoreMissing) {
ctx.status(HttpCode.MULTI_STATUS);

} else {
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
detailsMap.put("message",
"One or more time series were not found and could not be assigned to the group");
}
ctx.json(detailsMap);
}

}
}

Expand All @@ -298,6 +326,8 @@ public void create(@NotNull Context ctx) {
@OpenApiParam(name = REPLACE_ASSIGNED_TS, type = Boolean.class, description = "Specifies whether to "
+ "unassign all existing time series before assigning new time series specified in the content body "
+ "Default: false"),
@OpenApiParam(name = IGNORE_MISSING, type = Boolean.class, description = "If true, do not fail when "
+ "time series to assign does not exist. Default is false"),
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ "office of the user making the request. This is the office that the timeseries, group, and category "
+ "belong to. If the group and/or category belong to the CWMS office, "
Expand Down Expand Up @@ -331,8 +361,28 @@ public void update(@NotNull Context ctx, @NotNull String oldGroupId) {
timeSeriesGroupDao.unassignForOffice(group.getTimeSeriesCategory().getId(), group.getId(),
group.getOfficeId(), office);
}
timeSeriesGroupDao.assignTs(group, office);
ctx.status(HttpServletResponse.SC_OK);
boolean ignoreMissing = ctx.queryParamAsClass(IGNORE_MISSING, Boolean.class).getOrDefault(false);
List<CwmsId> missingTimeSeries = timeSeriesGroupDao.assignTs(group, office, ignoreMissing);
if (missingTimeSeries.isEmpty()) {
ctx.status(HttpServletResponse.SC_OK);
} else {
Map<String, String> detailsMap = new HashMap<>();
StringBuilder sb = new StringBuilder();
for (CwmsId cwmsId : missingTimeSeries) {
sb.append(cwmsId.getName());
sb.append(", ");
}
sb.delete(sb.length() - 2, sb.length());
detailsMap.put("missing-timeseries", sb.toString());
if (ignoreMissing) {
ctx.status(HttpCode.MULTI_STATUS);
} else {
ctx.status(HttpServletResponse.SC_BAD_REQUEST);
detailsMap.put("message",
"One or more time series were not found and could not be assigned to the group");
}
ctx.json(detailsMap);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package cwms.cda.api.errors;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import javax.servlet.http.HttpServletResponse;

Expand All @@ -18,6 +20,10 @@ public NotFoundException(String message, Throwable cause) {
LOG_LEVEL, new HashMap<>(), cause);
}

public NotFoundException(String message, Map<String, Serializable> details, Throwable cause) {
super(message, DATABASE_SOURCE, message, HttpServletResponse.SC_NOT_FOUND, LOG_LEVEL, details, cause);
}

public NotFoundException(Throwable cause) {
super(NOT_FOUND, DATABASE_SOURCE, NOT_FOUND, HttpServletResponse.SC_NOT_FOUND,
LOG_LEVEL, new HashMap<>(), cause);
Expand Down
33 changes: 32 additions & 1 deletion cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.javalin.http.BadRequestResponse;
import io.javalin.http.Context;
import io.javalin.http.HandlerType;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSetMetaData;
Expand Down Expand Up @@ -521,7 +522,37 @@ static NotFoundException buildNotFound(RuntimeException input) {

NotFoundException exception;
if (input.getMessage().contains("ASSIGN_LOC_GROUPS")) {
exception = new NotFoundException("Location group contains assigned locations that do not exist.", cause);
Map<String, Serializable> errorDetails = new HashMap<>();
String localizedMessage = cause.getLocalizedMessage();
if (localizedMessage != null) {
String[] parts = localizedMessage.split("\n");
if (parts.length > 1) {
localizedMessage = parts[0];
if (localizedMessage.startsWith("ORA-")) {
localizedMessage = localizedMessage
.substring(localizedMessage.indexOf("LOCATION_ID_NOT_FOUND:") + 23);
}
}
}
errorDetails.put("missing-locations", localizedMessage);
exception = new NotFoundException("Location group contains assigned locations that do not exist.",
errorDetails, cause);
} else if (input.getMessage().contains("ASSIGN_TS_GROUPS")) {
Map<String, Serializable> errorDetails = new HashMap<>();
String localizedMessage = cause.getLocalizedMessage();
if (localizedMessage != null) {
String[] parts = localizedMessage.split("\n");
if (parts.length > 1) {
localizedMessage = parts[0];
if (localizedMessage.startsWith("ORA-")) {
localizedMessage = localizedMessage
.substring(localizedMessage.indexOf("TS_ID_NOT_FOUND:") + 17);
}
}
}
errorDetails.put("missing-time-series", localizedMessage);
exception = new NotFoundException("Time series group contains assigned time series that do not exist.",
errorDetails, cause);
} else {
exception = new NotFoundException(cause);

Expand Down
Loading
Loading