Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- env: release
image: "ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:26.02.17"
- env: next-release
image: "ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:26.07.16-RC01"
image: "ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:26.07.16-RC02"
- env: previous
image: "ghcr.io/hydrologicengineeringcenter/cwms-database/cwms/database-ready-ora-23.5:25.07.01"
name: build and test (jdk ${{matrix.jdk}}, schema ${{matrix.schema.env}})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,8 @@ public void delete(@NotNull Context ctx, @NotNull String locationId) {
ctx.status(HttpServletResponse.SC_OK).json(re);
} catch (DataAccessException ex) {
SQLException cause = ex.getCause(SQLException.class);
if (cause != null && cause.getErrorCode() == 20031) {
if (cause != null &&
(cause.getErrorCode() == 20031 || cause.getErrorCode() == 20056)) {
throw new DeleteConflictException("Unable to delete requested location: "
+ locationId + " for office: " + office, cause);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
import cwms.cda.data.dto.CwmsDTOBase;
import cwms.cda.data.dto.StatusResponse;
import cwms.cda.data.dto.VerticalDatumInfo;
import cwms.cda.data.dto.rating.Ratings;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.annotations.FormattableWith;
Expand Down Expand Up @@ -184,7 +185,7 @@ private String deserializeRatingSet(Context ctx, boolean storeTemplate) throws I
String formatHeader = ctx.req.getContentType();
//Using placeholder CwmsDTOBase.class since we do not have a RatingSet DTO
//The contentType will match against the standard listing of Formats constants
ContentType contentType = Formats.parseHeader(formatHeader, CwmsDTOBase.class);
ContentType contentType = Formats.parseHeader(formatHeader, Ratings.class);
String body = ctx.body();
return deserializeRatingSet(body, contentType.getType(), storeTemplate);
}
Expand Down
2 changes: 2 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/Location.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import cwms.cda.api.enums.Nation;
import cwms.cda.data.dto.catalog.LocationAlias;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.OutputFormatter;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
Expand All @@ -33,6 +34,7 @@
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
@FormattableWith(contentType = Formats.GEOJSON, formatter = OutputFormatter.DUMMY.class)
public final class Location extends CwmsDTO {
@JsonProperty(required = true)
private final String name;
Expand Down
4 changes: 4 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
import cwms.cda.formatters.xml.XMLv1;
import cwms.cda.formatters.xml.XMLv2;

import java.util.Objects;

@JsonRootName("parameter")
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
@FormattableWith(contentType = Formats.XMLV1, formatter = XMLv1.class)
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
public final class Parameter extends CwmsDTO
{
private String name;
Expand Down
4 changes: 4 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/TimeSeries.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import cwms.cda.api.enums.VersionType;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.OutputFormatter;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
import cwms.cda.formatters.json.adapters.TimeSeriesRecordSerializer;
import cwms.cda.formatters.xml.XMLv2;
Expand All @@ -35,6 +37,8 @@
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
@FormattableWith(contentType = Formats.CSV, formatter = OutputFormatter.DUMMY.class)
public class TimeSeries extends CwmsDTOPaginated {
public static final String ZONED_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ'['VV']'";

Expand Down
4 changes: 4 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/TimeZoneId.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
import cwms.cda.formatters.xml.XMLv1;
import cwms.cda.formatters.xml.XMLv2;
import java.time.ZoneId;

@JsonRootName("time-zone")
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
@FormattableWith(contentType = Formats.XMLV1, formatter = XMLv1.class)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public final class TimeZoneId extends CwmsDTOBase {
@JsonProperty(required = true)
Expand Down
4 changes: 4 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/Unit.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
import cwms.cda.formatters.xml.XMLv1;
import cwms.cda.formatters.xml.XMLv2;

import java.util.ArrayList;
Expand All @@ -22,6 +24,8 @@
@JsonRootName("unit")
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
@FormattableWith(contentType = Formats.XMLV1, formatter = XMLv1.class)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public final class Unit extends CwmsDTOBase
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import cwms.cda.data.dto.CwmsDTO;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.NamedPgJsonFormatter;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.json.PgJsonFormatter;

@FormattableWith(contentType = Formats.NAMED_PGJSON, formatter = NamedPgJsonFormatter.class,
aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.PGJSON, formatter = PgJsonFormatter.class)
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
public final class Basin extends CwmsDTO
{
private final String basinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;

@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSONV1})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class, aliases = {Formats.DEFAULT, Formats.JSON})
@JsonDeserialize(builder = Turbine.Builder.class)
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import cwms.cda.data.dto.CwmsDTOPaginated;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.formatters.json.JsonV2;
import io.swagger.v3.oas.annotations.media.Schema;

Expand All @@ -42,6 +43,7 @@

@JsonRootName("location-levels")
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.DEFAULT, Formats.JSON})
@FormattableWith(contentType = Formats.JSONV1, formatter = JsonV1.class)
public class LocationLevels extends CwmsDTOPaginated {
@JacksonXmlElementWrapper
@JacksonXmlProperty(localName = "location-level")
Expand Down
13 changes: 13 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/rating/Ratings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cwms.cda.data.dto.rating;

import cwms.cda.data.dto.CwmsDTOBase;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.OutputFormatter;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.xml.XMLv2;

@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.DEFAULT, Formats.XML})
@FormattableWith(contentType = Formats.JSONV2, formatter = OutputFormatter.DUMMY.class, aliases = {Formats.JSON})
public class Ratings extends CwmsDTOBase {
/** marker interface for ContentType Annotations */
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ private ContentTypeAliasMap(Class<? extends CwmsDTOBase> dtoClass) {
FormattableWith[] formats = dtoClass.getAnnotationsByType(FormattableWith.class);
for (FormattableWith format : formats) {
ContentType type = new ContentType(format.contentType());

contentTypeMap.put(type, type); // we can always map to our self.
for (String alias : format.aliases()) {
contentTypeMap.put(new ContentType(alias), type);
}
Expand Down
8 changes: 0 additions & 8 deletions cwms-data-api/src/main/java/cwms/cda/formatters/Formats.java
Original file line number Diff line number Diff line change
Expand Up @@ -412,14 +412,6 @@ public static ContentType parseQueryParam(String queryParam, Class<? extends Cwm
if (aliasType != null) {
logger.atFinest().log("%s converted to %s", ct, aliasType);
contentTypes.add(aliasType);
} else {
//If the DTO parameter is null, alias map is empty. Compare against well-known types
//Only use the ContentType classes initialized in contentTypeList rather than
//the client headers itself
ContentType type = new ContentType(ct);
if (contentTypeList.contains(type)) {
contentTypes.add(type);
}
Comment on lines -415 to -422

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the CI tests this looks like it's causing the existing JSON/XML requests to return 406

i.e.

833 tests completed, 83 failed, 20 skipped

via
https://github.com/USACE/cwms-data-api/actions/runs/30954615142/job/92144620012

BasinControllerIT.test_get_create_delete expected HTTP 200 but received 406

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep... that's the problem. The removal of this code is correct, otherwise an invalid type "mapping" gets in below instead of properly failing, so now I'm dealing with the affects of that.

}
}
logger.atFinest().log("have %d", contentTypes.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,25 @@ default <T extends CwmsDTOBase> List<T> parseContentList(String content, Class<T
throw new UnsupportedOperationException(String.format(UNSUPPORTED_MESSAGE, getContentType()));
}

/**
* Used where more advanced handling is required.
* DUMMY
*/
public class DUMMY implements OutputFormatter {

@Override
public String getContentType() {
throw new UnsupportedOperationException("Unimplemented method 'getContentType'");
}

@Override
public String format(CwmsDTOBase dto) {
throw new UnsupportedOperationException("Unimplemented method 'format'");
}

@Override
public String format(List<? extends CwmsDTOBase> dtoList) {
throw new UnsupportedOperationException("Unimplemented method 'format'");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.json.JsonV1;
import cwms.cda.helpers.DatabaseHelpers.SCHEMA_VERSION;
import fixtures.CwmsDataApiSetupCallback;
import fixtures.TestAccounts.KeyUser;
import io.restassured.filter.log.LogDetail;
Expand Down Expand Up @@ -1209,6 +1210,15 @@ void testDeleteConflict() throws Exception {
String timeseriesId = "Putah_Creek.Elev.Ave.30Minutes.30Minutes.Raw";
createTimeseries(user.getOperatingOffice(), timeseriesId);

String expectedMessage = "Unable to delete requested location: Putah_Creek for office: SPK: ORA-20056: CAN_NOT_DELETE_LOC_2: Can not delete location: " +
"\"Putah_Creek\" because dependent data exists: time series identifiers=1.";

if (CwmsDataApiSetupCallback.getSchemaVersion() <= SCHEMA_VERSION.V2026_07_16.numeric()) {
expectedMessage = "Unable to delete requested location: "
+ "Putah_Creek for office: SPK: ORA-20031: CAN_NOT_DELETE_LOC_1: "
+ "Can not delete location: \"Putah_Creek\" because Timeseries Identifiers exist.";
}

// attempt to delete location that is referenced by TS
given()
.log().ifValidationFails(LogDetail.ALL,true)
Expand All @@ -1227,9 +1237,7 @@ void testDeleteConflict() throws Exception {
.body("source", equalTo("Database"))
.body("message",
equalTo("Cannot delete this record because it is linked to other data in CWMS"))
.body("details.message", equalTo("Unable to delete requested location: "
+ "Putah_Creek for office: SPK: ORA-20031: CAN_NOT_DELETE_LOC_1: "
+ "Can not delete location: \"Putah_Creek\" because Timeseries Identifiers exist."));
.body("details.message", equalTo(expectedMessage));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
.then()
.log().ifValidationFails(LogDetail.ALL, true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_OK))

Check failure on line 133 in cwms-data-api/src/test/java/cwms/cda/api/rss/RssHandlerIT.java

View workflow job for this annotation

GitHub Actions / build and test (jdk 11, schema next-release)

RssHandlerIT.RssHandlerIT.test_rss_feed_with_pagination (schema: 26.07.16-RC02)

java.lang.AssertionError: 1 expectation failed. Expected status code is <200> but was <429>.

Check failure on line 133 in cwms-data-api/src/test/java/cwms/cda/api/rss/RssHandlerIT.java

View workflow job for this annotation

GitHub Actions / build and test (jdk 11, schema previous)

RssHandlerIT.RssHandlerIT.test_rss_feed_with_pagination (schema: 25.07.01)

java.lang.AssertionError: 1 expectation failed. Expected status code is <200> but was <429>.

Check failure on line 133 in cwms-data-api/src/test/java/cwms/cda/api/rss/RssHandlerIT.java

View workflow job for this annotation

GitHub Actions / build and test (jdk 11, schema latest)

RssHandlerIT.RssHandlerIT.test_rss_feed_with_pagination (schema: latest-dev)

java.lang.AssertionError: 1 expectation failed. Expected status code is <200> but was <429>.
.extract();

XmlPath nextXml = rssXml(nextPage.asString());
Expand All @@ -144,7 +144,7 @@
String waitStr = nextPage.header("Retry-After");
int wait = waitStr != null && !waitStr.isEmpty() ? Integer.parseInt(waitStr) : 10;
try {
Thread.sleep(wait*1000);
Thread.sleep(wait*1000 + 500 /* extra half second just to avoid the best being brittle */);
} catch (InterruptedException ex) {
LOGGER.atFine().withCause(ex).log("Next query wait was interrupted.");
}
Expand Down
22 changes: 10 additions & 12 deletions cwms-data-api/src/test/java/cwms/cda/formatters/FormatsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import cwms.cda.data.dto.CwmsDTOBase;
import cwms.cda.data.dto.locationlevel.LocationLevels;
import cwms.cda.data.dto.Office;
import cwms.cda.data.dto.RecentValue;
import cwms.cda.data.dto.State;
import cwms.cda.data.dto.basinconnectivity.Basin;
import cwms.cda.data.dto.project.LockRevokerRights;
Expand Down Expand Up @@ -93,17 +94,9 @@ void testParseHeaderAndQueryParmXml() {
Map<String, String> parameters = contentType.getParameters();
assertTrue(parameters == null || parameters.isEmpty());


contentType = Formats.parseHeaderAndQueryParm("application/xml;version=2", null, Catalog.class);

assertNotNull(contentType);
assertEquals("application/xml", contentType.getType());
parameters = contentType.getParameters();
assertNotNull(parameters);
assertFalse(parameters.isEmpty());
assertTrue(parameters.containsKey("version"));
assertEquals("2", parameters.get("version"));

/** xml;version=2 is not a supported format of Catalog */
assertThrows(UnsupportedFormatException.class,
() -> Formats.parseHeaderAndQueryParm("application/xml;version=2", null, Catalog.class));
}

@Test
Expand All @@ -114,6 +107,10 @@ void testParseHeader() {
assertNotNull(contentType);
assertEquals("application/json", contentType.getType());

contentType = Formats.parseHeader("application/json;version=1", Catalog.class);
assertNotNull(contentType);
assertEquals("application/json", contentType.getType());

contentType = Formats.parseHeader("application/json;version=2", Catalog.class);
assertNotNull(contentType);
assertEquals("application/json", contentType.getType());
Expand All @@ -131,9 +128,10 @@ void testParseHeader() {
assertThrows(FormattingException.class, () -> Formats.parseHeader("abc", Catalog.class));
assertThrows(FormattingException.class, () -> Formats.parseHeader("abc,def", Catalog.class));

contentType = Formats.parseHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", RecentValue.class);
assertEquals("application/json", contentType.getType());
}


@ParameterizedTest
@EnumSource(ParseQueryOrParamTest.class)
void test_header_or_query_parm(ParseQueryOrParamTest test) {
Expand Down
24 changes: 19 additions & 5 deletions cwms-data-api/src/test/java/fixtures/CwmsDataApiSetupCallback.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import fixtures.tomcat.SingleSignOnWrapper;
import helpers.TsRandomSampler;
import io.restassured.RestAssured;
import io.restassured.config.EncoderConfig;
import io.restassured.config.JsonConfig;
import io.restassured.filter.log.LogDetail;
import io.restassured.path.json.config.JsonPathConfig;
Expand Down Expand Up @@ -179,11 +180,24 @@ public void beforeAll(ExtensionContext context) throws Exception {
RestAssured.baseURI=CwmsDataApiSetupCallback.httpUrl();
RestAssured.port = CwmsDataApiSetupCallback.httpPort();
RestAssured.basePath = System.getProperty("warContext");
// we only use doubles
RestAssured.config()
.jsonConfig(
JsonConfig.jsonConfig()
.numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE));
// actually assign the new config to the global configuration. just running this here without
// the assignment apparently does nothing.
RestAssured.config = RestAssured.config()
// we only use doubles (NOTE: this is commend out because this config was
// never originally active and will be addressed in a followup)
// .jsonConfig(
// JsonConfig.jsonConfig()
// .numberReturnType(JsonPathConfig.NumberReturnType.DOUBLE))
// our content type processing is a bit more picky now.
// I also don't recal seeing any default COntent-Type or Accept header
// defaults from browsers that include this much.
// if we start seeing it we need to add explicity @FormattableWith annotations
// per character as that is a distinct content-type.
.encoderConfig(
EncoderConfig.encoderConfig()
.appendDefaultContentCharsetToContentTypeIfUndefined(
false
));
healthCheck();
}
}
Expand Down
Loading