Skip to content

Commit

Permalink
Merge branch 'develop' into feature/TimeSeriesProfileController
Browse files Browse the repository at this point in the history
  • Loading branch information
zack-rma authored Nov 18, 2024
2 parents e37aaff + b5d4356 commit d82b008
Show file tree
Hide file tree
Showing 20 changed files with 1,651 additions and 152 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ jobs:
thewar: ${{steps.thebuild.outputs.WARFILE}}
steps:
- name: checkout code
uses: actions/[email protected].1
uses: actions/[email protected].2
- name: setup java
uses: actions/setup-java@v4.4.0
uses: actions/setup-java@v4.5.0
with:
distribution: 'temurin'
java-version: '8'
Expand All @@ -44,9 +44,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout code
uses: actions/[email protected].1
uses: actions/[email protected].2
- name: setup java
uses: actions/setup-java@v4.4.0
uses: actions/setup-java@v4.5.0
with:
distribution: 'temurin'
java-version: '8'
Expand All @@ -63,7 +63,7 @@ jobs:
run: echo ${VERSION}
- name: Create Release
id: create_release
uses: softprops/[email protected].8
uses: softprops/[email protected].9
with:
files: warfile/${{ needs.build.outputs.thewar}}
tag_name: ${{env.VERSION}}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/[email protected].1
uses: actions/[email protected].2

- name: Initialize CodeQL
uses: github/codeql-action/init@v3.26.13
uses: github/codeql-action/init@v3.27.0
with:
languages: 'java'
- name: setup java
uses: actions/setup-java@v4.4.0
uses: actions/setup-java@v4.5.0
with:
java-version: '8'
java-package: jdk
Expand All @@ -34,4 +34,4 @@ jobs:
id: build
run: ./gradlew build --info --init-script init.gradle
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.26.13
uses: github/codeql-action/analyze@v3.27.0
2 changes: 2 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/api/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public final class Controllers {
public static final String SIZE = "size";

public static final String OFFICE = "office";
public static final String CATEGORY_OFFICE_ID = "category-office-id";
public static final String GROUP_OFFICE_ID = "group-office-id";
public static final String UNIT = "unit";
public static final String COUNT = "count";
public static final String TIME = "time";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,15 +135,20 @@ Boolean.class, false, metrics, name(LocationGroupController.class.getName(),
@OpenApi(
pathParams = {
@OpenApiParam(name = GROUP_ID, required = true, description = "Specifies "
+ "the location_group whose data is to be included in the response")
+ "the location_group whose data is to be included in the response")
},
queryParams = {
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ "owning office of the location group whose data is to be included "
+ "in the response."),
+ "owning office of the location group whose data is to be included "
+ "in the response."),
@OpenApiParam(name = GROUP_OFFICE_ID, required = true, description = "Specifies the "
+ "owning office of the location group whose data is to be included in the response."),
@OpenApiParam(name = CATEGORY_OFFICE_ID, required = true, description = "Specifies the "
+ "owning office of the category the location group belongs to "
+ "whose data is to be included in the response."),
@OpenApiParam(name = CATEGORY_ID, required = true, description = "Specifies"
+ " the category containing the location group whose data is to be "
+ "included in the response."),
+ " the category containing the location group whose data is to be "
+ "included in the response."),
},
responses = {
@OpenApiResponse(status = STATUS_200, content = {
Expand All @@ -161,14 +166,15 @@ public void getOne(@NotNull Context ctx, @NotNull String groupId) {
LocationGroupDao cdm = new LocationGroupDao(dsl);
String office = requiredParam(ctx, OFFICE);
String categoryId = requiredParam(ctx, CATEGORY_ID);

String groupOfficeId = requiredParam(ctx, GROUP_OFFICE_ID);
String categoryOfficeId = requiredParam(ctx, CATEGORY_OFFICE_ID);
String formatHeader = ctx.header(Header.ACCEPT);
String result;
ContentType contentType;
if (formatHeader != null && formatHeader.contains(Formats.GEOJSON)) {
contentType = new ContentType(Formats.GEOJSON);
FeatureCollection fc = cdm.buildFeatureCollectionForLocationGroup(office,
categoryId, groupId, "EN");
groupOfficeId, categoryOfficeId, categoryId, groupId, "EN");
ObjectMapper mapper = ctx.appAttribute("ObjectMapper");
result = mapper.writeValueAsString(fc);
} else {
Expand Down Expand Up @@ -217,6 +223,14 @@ 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);

if (!deserialize.getLocationCategory().getOfficeId().equalsIgnoreCase(CWMS_OFFICE)
&& (!deserialize.getOfficeId().equalsIgnoreCase(deserialize.getLocationCategory().getOfficeId())
|| deserialize.getOfficeId().equalsIgnoreCase(CWMS_OFFICE))) {
throw new IllegalArgumentException("Location Group office ID cannot be CWMS and must match the "
+ "Location Category office ID");
}

LocationGroupDao dao = new LocationGroupDao(dsl);
dao.create(deserialize);
ctx.status(HttpServletResponse.SC_CREATED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,28 @@
package cwms.cda.api;

import static com.codahale.metrics.MetricRegistry.name;
import static cwms.cda.api.Controllers.*;
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.FAIL_IF_EXISTS;
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.OFFICE;
import static cwms.cda.api.Controllers.REPLACE_ASSIGNED_TS;
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.TIMESERIES_CATEGORY_LIKE;
import static cwms.cda.api.Controllers.TIMESERIES_GROUP_LIKE;
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;
Expand Down Expand Up @@ -82,6 +103,10 @@ private Timer.Context markAndTime(String subject) {
+ " the assigned timeseries in the returned timeseries groups. (default: true)"),
@OpenApiParam(name = TIMESERIES_CATEGORY_LIKE, description = "Posix <a href=\"regexp.html\">regular expression</a> "
+ "matching against the timeseries category id"),
@OpenApiParam(name = CATEGORY_OFFICE_ID, description = "Specifies the owning office of the "
+ "timeseries group category"),
@OpenApiParam(name = GROUP_OFFICE_ID, description = "Specifies the owning office of the "
+ "timeseries group"),
@OpenApiParam(name = TIMESERIES_GROUP_LIKE, description = "Posix <a href=\"regexp.html\">regular expression</a> "
+ "matching against the timeseries group id")
},
Expand All @@ -102,6 +127,8 @@ public void getAll(@NotNull Context ctx) {

TimeSeriesGroupDao dao = new TimeSeriesGroupDao(dsl);
String office = ctx.queryParam(OFFICE);
String categoryOffice = ctx.queryParam(CATEGORY_OFFICE_ID);
String groupOffice = ctx.queryParam(GROUP_OFFICE_ID);

boolean includeAssigned = queryParamAsClass(ctx, new String[]{INCLUDE_ASSIGNED},
Boolean.class, true, metrics, name(TimeSeriesGroupController.class.getName(),
Expand All @@ -111,7 +138,8 @@ Boolean.class, true, metrics, name(TimeSeriesGroupController.class.getName(),
String tsGroupLike = queryParamAsClass(ctx, new String[]{TIMESERIES_GROUP_LIKE},
String.class, null, metrics, name(TimeSeriesGroupController.class.getName(), GET_ALL));

List<TimeSeriesGroup> grps = dao.getTimeSeriesGroups(office, includeAssigned, tsCategoryLike, tsGroupLike);
List<TimeSeriesGroup> grps = dao.getTimeSeriesGroups(office, categoryOffice, groupOffice,
includeAssigned, tsCategoryLike, tsGroupLike);
if (grps.isEmpty()) {
CdaError re = new CdaError("No data found for The provided office");
logger.info(() -> re + " for request " + ctx.fullUrl());
Expand Down Expand Up @@ -140,6 +168,10 @@ Boolean.class, true, metrics, name(TimeSeriesGroupController.class.getName(),
@OpenApiParam(name = OFFICE, required = true, description = "Specifies the "
+ "owning office of the timeseries group whose data is to be included"
+ " in the response."),
@OpenApiParam(name = CATEGORY_OFFICE_ID, description = "Specifies the owning office of the "
+ "timeseries group category"),
@OpenApiParam(name = GROUP_OFFICE_ID, description = "Specifies the owning office of the "
+ "timeseries group"),
@OpenApiParam(name = CATEGORY_ID, required = true, description = "Specifies"
+ " the category containing the timeseries group whose data is to be "
+ "included in the response."),
Expand All @@ -158,13 +190,15 @@ public void getOne(@NotNull Context ctx, @NotNull String groupId) {
TimeSeriesGroupDao dao = new TimeSeriesGroupDao(dsl);
String office = ctx.queryParam(OFFICE);
String categoryId = ctx.queryParam(CATEGORY_ID);
String groupOffice = ctx.queryParam(GROUP_OFFICE_ID);
String categoryOffice = ctx.queryParam(CATEGORY_OFFICE_ID);

String formatHeader = ctx.header(Header.ACCEPT);
ContentType contentType = Formats.parseHeader(formatHeader, TimeSeriesGroup.class);

TimeSeriesGroup group = null;
List<TimeSeriesGroup> timeSeriesGroups = dao.getTimeSeriesGroups(office, categoryId,
groupId);
List<TimeSeriesGroup> timeSeriesGroups = dao.getTimeSeriesGroups(office, groupOffice, categoryOffice,
categoryId, groupId);
if (timeSeriesGroups != null && !timeSeriesGroups.isEmpty()) {
if (timeSeriesGroups.size() == 1) {
group = timeSeriesGroups.get(0);
Expand Down Expand Up @@ -220,6 +254,14 @@ public void create(@NotNull Context ctx) {
String body = ctx.body();
ContentType contentType = Formats.parseHeader(formatHeader, TimeSeriesGroup.class);
TimeSeriesGroup deserialize = Formats.parseContent(contentType, body, TimeSeriesGroup.class);

if (!deserialize.getTimeSeriesCategory().getOfficeId().equalsIgnoreCase(CWMS_OFFICE)
&& (!deserialize.getOfficeId().equalsIgnoreCase(deserialize.getTimeSeriesCategory().getOfficeId())
|| deserialize.getOfficeId().equalsIgnoreCase(CWMS_OFFICE))) {
throw new IllegalArgumentException("TimeSeries Group office ID cannot be CWMS and must match the "
+ "TimeSeries Category office ID");
}

boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true);
TimeSeriesGroupDao dao = new TimeSeriesGroupDao(dsl);
dao.create(deserialize, failIfExists);
Expand All @@ -241,7 +283,8 @@ public void create(@NotNull Context ctx) {
+ "Default: 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, this only identifies the timeseries."),
+ "belong to. If the group and/or category belong to the CWMS office, "
+ "this only identifies the timeseries."),
},
method = HttpMethod.PATCH,
tags = {TAG}
Expand Down
37 changes: 35 additions & 2 deletions cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.jetbrains.annotations.NotNull;
Expand All @@ -71,6 +72,8 @@ public abstract class JooqDao<T> extends Dao<T> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();

static ExecuteListener listener = new ExceptionWrappingListener();
private static Pattern INVALID_OFFICE_ID = Pattern.compile(
"INVALID_OFFICE_ID: \"([^\"]+)\" is not a valid CWMS office id");

public enum DeleteMethod {
DELETE_ALL(DeleteRule.DELETE_ALL),
Expand Down Expand Up @@ -240,6 +243,8 @@ public static RuntimeException wrapException(RuntimeException input) {
retVal = buildInvalidUnits(input);
} else if (isUnsupportedOperationException(input)) {
retVal = buildUnsupportedOperationException(input);
} else if (isInvalidOffice(input)) {
retVal = buildInvalidOffice(input);
}

return retVal;
Expand Down Expand Up @@ -302,7 +307,16 @@ public static boolean isNotFound(RuntimeException input) {
}
}
return retVal;
}
}

public static boolean isInvalidOffice(RuntimeException input) {
return getSqlException(input)
.map(sqlException -> {
return hasCodeOrMessage(sqlException, Collections.singletonList(20010),
Collections.singletonList("INVALID_OFFICE_ID"));
})
.orElse(false);
}

public static boolean isInvalidItem(RuntimeException input) {
boolean retVal = false;
Expand Down Expand Up @@ -509,6 +523,25 @@ private static InvalidItemException buildInvalidUnits(RuntimeException input) {
return new InvalidItemException(localizedMessage, cause);
}

private static InvalidItemException buildInvalidOffice(RuntimeException input) {

Throwable cause = (input instanceof DataAccessException) ? input.getCause() : input;
String localizedMessage = cause.getLocalizedMessage();
if (localizedMessage != null) {
Matcher matcher = INVALID_OFFICE_ID.matcher(localizedMessage);
if (matcher.find()) {
String office = sanitizeOrNull(matcher.group(1));
if(office != null) {
localizedMessage = "\"" + office + "\" is not a valid CWMS office id";
}
}
}
if (localizedMessage == null || localizedMessage.isEmpty()) {
localizedMessage = "Invalid Office.";
}
return new InvalidItemException(localizedMessage, cause);
}

public static boolean isUnsupportedOperationException(RuntimeException input) {
boolean retVal = false;

Expand Down
35 changes: 19 additions & 16 deletions cwms-data-api/src/main/java/cwms/cda/data/dao/LocationGroupDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ public List<LocationGroup> getLocationGroups(@Nullable String officeId,
/**
* Get all location groups for a given office and category,
* as well as a where clause to filter the shared_ref_location_id.
* @Param officeId The office id to use for the query.
* @Param locCategoryLike A regex to use to filter the location categories. May be null.
* @Param sharedRefLocLike A where clause to filter the shared_loc_alias_id. May be null.
* @param officeId The office id to use for the query.
* @param locCategoryLike A regex to use to filter the location categories. May be null.
* @param sharedRefLocLike A where clause to filter the shared_loc_alias_id. May be null.
* @return A list of all location groups for the given parameters.
*/

Expand Down Expand Up @@ -432,21 +432,24 @@ public Feature buildFeatureFromAvLocRecordWithLocGroup(Record avLocRecord) {
return feature;
}

public FeatureCollection buildFeatureCollectionForLocationGroup(String officeId,
public FeatureCollection buildFeatureCollectionForLocationGroup(String locationOfficeId, String groupOfficeId,
String categoryOfficeId,
String categoryId,
String groupId, String units) {
AV_LOC_GRP_ASSGN alga = AV_LOC_GRP_ASSGN.AV_LOC_GRP_ASSGN;
AV_LOC al = AV_LOC.AV_LOC;

SelectSeekStep1<Record, BigDecimal> select = dsl.select(al.asterisk(), alga.CATEGORY_ID,
alga.GROUP_ID, alga.ATTRIBUTE, alga.ALIAS_ID, alga.SHARED_REF_LOCATION_ID,
alga.SHARED_ALIAS_ID)
.from(al).join(alga).on(al.LOCATION_ID.eq(alga.LOCATION_ID))
.where(alga.DB_OFFICE_ID.eq(officeId)
.and(alga.CATEGORY_ID.eq(categoryId)
.and(alga.GROUP_ID.eq(groupId))
.and(al.UNIT_SYSTEM.eq(units))))
.orderBy(alga.ATTRIBUTE);
alga.GROUP_ID, alga.ATTRIBUTE, alga.ALIAS_ID, alga.SHARED_REF_LOCATION_ID,
alga.SHARED_ALIAS_ID)
.from(al).join(alga).on(al.LOCATION_ID.eq(alga.LOCATION_ID))
.where(alga.DB_OFFICE_ID.eq(locationOfficeId)
.and(alga.CATEGORY_OFFICE_ID.eq(categoryOfficeId))
.and(alga.GROUP_OFFICE_ID.eq(groupOfficeId))
.and(alga.CATEGORY_ID.eq(categoryId)
.and(alga.GROUP_ID.eq(groupId))
.and(al.UNIT_SYSTEM.eq(units))))
.orderBy(alga.ATTRIBUTE);

List<Feature> features =
select.stream()
Expand Down Expand Up @@ -531,8 +534,8 @@ public void assignLocs(LocationGroup group, String office) {
/**
* Used when an appropriate context already exists to avoid opening a second connection.
* @param dslContext a dslContext that is assumed to be fully prepared for use in this operation
* @param group
* @param office
* @param group the location group to assign locations to
* @param office the office to use for the operation
*/
public void assignLocs(DSLContext dslContext, LocationGroup group, String office) {
List<AssignedLocation> assignedLocations = group.getAssignedLocations();
Expand All @@ -542,8 +545,8 @@ public void assignLocs(DSLContext dslContext, LocationGroup group, String office
.collect(toList());
LOC_ALIAS_ARRAY3 assignedLocs = new LOC_ALIAS_ARRAY3(collect);
LocationCategory cat = group.getLocationCategory();
CWMS_LOC_PACKAGE.call_ASSIGN_LOC_GROUPS3(dslContext.configuration(),
cat.getId(), group.getId(), assignedLocs, office);
CWMS_LOC_PACKAGE.call_ASSIGN_LOC_GROUPS3(dslContext.configuration(),
cat.getId(), group.getId(), assignedLocs, office);
}
}
}
Loading

0 comments on commit d82b008

Please sign in to comment.