Skip to content

Commit

Permalink
Merge pull request #701 from RyanM-RMA/bugfix/content_type_ratings
Browse files Browse the repository at this point in the history
Bugfix/content type ratings
  • Loading branch information
RyanM-RMA authored Jun 16, 2024
2 parents 7553f1a + d3f2c84 commit b0e30d5
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 36 deletions.
74 changes: 41 additions & 33 deletions cwms-data-api/src/main/java/cwms/cda/api/RatingController.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import static cwms.cda.api.Controllers.UNIT;
import static cwms.cda.api.Controllers.UPDATE;
import static cwms.cda.api.Controllers.VERSION_DATE;
import static cwms.cda.api.Controllers.addDeprecatedContentTypeWarning;
import static cwms.cda.data.dao.JooqDao.getDslContext;

import com.codahale.metrics.Histogram;
Expand All @@ -59,9 +60,12 @@
import cwms.cda.data.dao.JsonRatingUtils;
import cwms.cda.data.dao.RatingDao;
import cwms.cda.data.dao.RatingSetDao;
import cwms.cda.data.dto.CwmsDTOBase;
import cwms.cda.formatters.ContentType;
import cwms.cda.formatters.Formats;
import cwms.cda.formatters.FormattingException;
import cwms.cda.formatters.annotations.FormattableWith;
import cwms.cda.formatters.json.JsonV2;
import cwms.cda.formatters.xml.XMLv2;
import cwms.cda.helpers.DateUtils;
import hec.data.RatingException;
import hec.data.cwmsRating.RatingSet;
Expand Down Expand Up @@ -287,7 +291,6 @@ public void getAll(@NotNull Context ctx) {

RatingDao ratingDao = getRatingDao(dsl);

String format = ctx.queryParamAsClass(FORMAT, String.class).getOrDefault("json");
String names = ctx.queryParamAsClass(NAME, String.class).getOrDefault("*");
String unit = ctx.queryParam(UNIT);
String datum = ctx.queryParam(DATUM);
Expand All @@ -296,40 +299,31 @@ public void getAll(@NotNull Context ctx) {
String end = ctx.queryParam(END);
String timezone = ctx.queryParamAsClass(TIMEZONE, String.class).getOrDefault("UTC");

switch (format) {
case "json": {
ctx.contentType(Formats.JSON);
break;
}
case "tab": {
ctx.contentType(Formats.TAB);
break;
}
case "csv": {
ctx.contentType(Formats.CSV);
break;
}
case "xml": {
ctx.contentType(Formats.XML);
break;
}
case "wml2": {
ctx.contentType(Formats.WML2);
break;
}
case "jpg": // same handler
case "png":
default: {
ctx.status(HttpServletResponse.SC_NOT_IMPLEMENTED)
.json(CdaError.notImplemented());
return;
}
String format = ctx.queryParamAsClass(FORMAT, String.class).getOrDefault("");
String header = ctx.header(Header.ACCEPT);

ContentType contentType = Formats.parseHeaderAndQueryParm(header, format, RatingAliasMarker.class);

if (format.isEmpty())
{
//Use the full content type here (i.e. application/json;version=2)
ctx.contentType(contentType.toString());
}
else
{
//Legacy content type only includes the basic type (i.e. application/json)
ctx.contentType(contentType.getType());
}

String results = ratingDao.retrieveRatings(format, names, unit, datum, office, start,
//At the moment, we still use the legacy formatting here, since we don't have a newer API for serializing/deserializing
//a collection of rating sets - unlike getOne.
String legacyFormat = Formats.getLegacyTypeFromContentType(contentType);
String results = ratingDao.retrieveRatings(legacyFormat, names, unit, datum, office, start,
end, timezone);

ctx.status(HttpServletResponse.SC_OK);
ctx.result(results);
addDeprecatedContentTypeWarning(ctx, contentType);
requestResultSize.update(results.length());
}
}
Expand Down Expand Up @@ -404,12 +398,17 @@ private String getRatingSetString(Context ctx, RatingSet.DatabaseLoadMethod meth

try (final Timer.Context ignored = markAndTime("getRatingSetString")) {
String acceptHeader = ctx.header(Header.ACCEPT);
ContentType contentType = Formats.parseHeader(acceptHeader, RatingAliasMarker.class);

boolean isJson = Formats.JSONV2.equals(contentType.toString());
boolean isXml = Formats.XMLV2.equals(contentType.toString());

if (Formats.JSONV2.equals(acceptHeader) || Formats.XMLV2.equals(acceptHeader)) {
if (isJson || isXml) {
ctx.contentType(contentType.toString());
try {
RatingSet ratingSet = getRatingSet(ctx, method, officeId, rating, begin, end);
if (ratingSet != null) {
if (Formats.JSONV2.equals(acceptHeader)) {
if (isJson) {
retval = JsonRatingUtils.toJson(ratingSet);
} else {
retval = RatingXmlFactory.toXml(ratingSet, " ");
Expand Down Expand Up @@ -438,6 +437,8 @@ private String getRatingSetString(Context ctx, RatingSet.DatabaseLoadMethod meth
ctx.status(HttpServletResponse.SC_NOT_IMPLEMENTED);
ctx.json(CdaError.notImplemented());
}

addDeprecatedContentTypeWarning(ctx, contentType);
}
return retval;
}
Expand Down Expand Up @@ -486,4 +487,11 @@ public void update(@NotNull Context ctx, @NotNull String ratingId) {
}
}

/**
* This is a marker interface only used by <code>Formats</code> to support <code>ContentType</code> aliases.
* There's no reason to implement this interface.
*/
@FormattableWith(contentType = Formats.JSONV2, formatter = JsonV2.class, aliases = {Formats.JSON})
@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML, Formats.DEFAULT})
private interface RatingAliasMarker extends CwmsDTOBase { }
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ public void create(String ratingSetXml, boolean storeTemplate) throws IOExceptio
// can't exist if we are creating, if it exists use store
String office = extractOfficeId(ratingSetXml);
DSLContext context = getDslContext(c, office);
CWMS_RATING_PACKAGE.call_STORE_RATINGS_XML__5(context.configuration(),
String errs = CWMS_RATING_PACKAGE.call_STORE_RATINGS_XML__5(context.configuration(),
ratingSetXml, "T", storeTemplate ? "T" : "F");
if (errs != null && !errs.isEmpty())
{
throw new DataAccessException(errs);
}
});
} catch (DataAccessException ex) {
Throwable cause = ex.getCause();
Expand Down
3 changes: 1 addition & 2 deletions cwms-data-api/src/main/java/cwms/cda/formatters/Formats.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,11 @@ public static ContentType parseHeader(String header) {
}

public static ContentType parseHeader(String header, Class<? extends CwmsDTOBase> klass) {
ArrayList<ContentType> contentTypes = new ArrayList<>();
ContentTypeAliasMap aliasMap = ContentTypeAliasMap.empty();
if (klass != null) {
aliasMap = ContentTypeAliasMap.forDtoClass(klass);
}

ArrayList<ContentType> contentTypes = new ArrayList<>();
if (header != null && !header.isEmpty()) {
String[] all = header.split(",");
logger.log(Level.FINEST, "Finding handlers {0}", all.length);
Expand Down
218 changes: 218 additions & 0 deletions cwms-data-api/src/test/java/cwms/cda/api/RatingsControllerTestIT.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* Copyright (c) 2024. Hydrologic Engineering Center (HEC).
* United States Army Corps of Engineers
* All Rights Reserved. HEC PROPRIETARY/CONFIDENTIAL.
* Source may not be released without written approval from HEC
*/

package cwms.cda.api;

import cwms.cda.formatters.Formats;
import fixtures.TestAccounts;
import hec.data.cwmsRating.io.RatingSetContainer;
import hec.data.cwmsRating.io.RatingSpecContainer;
import io.restassured.filter.log.LogDetail;
import mil.army.usace.hec.cwms.rating.io.xml.RatingContainerXmlFactory;
import mil.army.usace.hec.cwms.rating.io.xml.RatingSetContainerXmlFactory;
import mil.army.usace.hec.cwms.rating.io.xml.RatingSpecXmlFactory;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import javax.servlet.http.HttpServletResponse;

import java.util.Arrays;

import static cwms.cda.api.Controllers.FORMAT;
import static cwms.cda.api.Controllers.OFFICE;
import static cwms.cda.api.Controllers.STORE_TEMPLATE;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;

@Tag("integration")
public class RatingsControllerTestIT extends DataApiTestIT
{
private static final String EXISTING_LOC = "RatingsControllerTestIT";
private static final String EXISTING_SPEC = EXISTING_LOC + ".Stage;Flow.COE.Production";
private static final String SPK = "SPK";

@BeforeAll
static void beforeAll() throws Exception
{
//Make sure we always have something.
createLocation(EXISTING_LOC, true, SPK);

String ratingXml = readResourceFile("cwms/cda/api/Zanesville_Stage_Flow_COE_Production.xml");
ratingXml = ratingXml.replaceAll("Zanesville", EXISTING_LOC);
RatingSetContainer container = RatingSetContainerXmlFactory.ratingSetContainerFromXml(ratingXml);
RatingSpecContainer specContainer = container.ratingSpecContainer;
specContainer.officeId = SPK;
specContainer.specOfficeId = SPK;
specContainer.locationId = EXISTING_LOC;
String specXml = RatingSpecXmlFactory.toXml(specContainer, "", 0, true);
String templateXml = RatingSpecXmlFactory.toXml(specContainer, "", 0);
String setXml = RatingContainerXmlFactory.toXml(container, "", 0, true, false);
TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL;

//Create Template
given()
.log().ifValidationFails(LogDetail.ALL,true)
.contentType(Formats.XMLV2)
.body(templateXml)
.header("Authorization", user.toHeaderValue())
.queryParam(OFFICE, SPK)
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/ratings/template")
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_CREATED));

//Create Spec
given()
.log().ifValidationFails(LogDetail.ALL,true)
.contentType(Formats.XMLV2)
.body(specXml)
.header("Authorization", user.toHeaderValue())
.queryParam(OFFICE, SPK)
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/ratings/spec")
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_CREATED));

//Create the set
given()
.log().ifValidationFails(LogDetail.ALL,true)
.contentType(Formats.XMLV2)
.body(setXml)
.header("Authorization", user.toHeaderValue())
.queryParam(OFFICE, SPK)
.queryParam(STORE_TEMPLATE, false)
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/ratings")
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_OK));
}

@ParameterizedTest
@EnumSource(GetAllLegacyTest.class)
void test_getAll_legacy(GetAllLegacyTest test)
{
given()
.log().ifValidationFails(LogDetail.ALL,true)
.queryParam(FORMAT, test._queryParam)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/ratings")
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_OK))
.contentType(is(test._expectedContentType));
}

@ParameterizedTest
@EnumSource(GetAllTest.class)
void test_getAll(GetAllTest test)
{
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(test._accept)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/ratings")
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_OK))
.contentType(is(test._expectedContentType));
}

@ParameterizedTest
@EnumSource(GetOneTest.class)
void test_getOne(GetOneTest test)
{
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(test._accept)
.queryParam(OFFICE, SPK)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/ratings/" + EXISTING_SPEC)
.then()
.assertThat()
.log().ifValidationFails(LogDetail.ALL,true)
.statusCode(is(HttpServletResponse.SC_OK))
.contentType(is(test._expectedContentType));
}

enum GetOneTest
{
DEFAULT(Formats.DEFAULT, Formats.XMLV2),
XML(Formats.XML, Formats.XMLV2),
XMLV2(Formats.XMLV2, Formats.XMLV2),
JSON(Formats.JSON, Formats.JSONV2),
JSONV2(Formats.JSONV2, Formats.JSONV2),
;

final String _accept;
final String _expectedContentType;

GetOneTest(String accept, String expectedContentType)
{
_accept = accept;
_expectedContentType = expectedContentType;
}
}

enum GetAllLegacyTest
{
JSON(Formats.JSON_LEGACY, Formats.JSON),
XML(Formats.XML_LEGACY, Formats.XML),
;

final String _queryParam;
final String _expectedContentType;

GetAllLegacyTest(String queryParam, String expectedContentType)
{
_queryParam = queryParam;
_expectedContentType = expectedContentType;
}
}

enum GetAllTest
{
DEFAULT(Formats.DEFAULT, Formats.XMLV2),
XML(Formats.XML, Formats.XMLV2),
XMLV1(Formats.XMLV1, Formats.XMLV1),
XMLV2(Formats.XMLV2, Formats.XMLV2),
JSON(Formats.JSON, Formats.JSONV2),
JSONV1(Formats.JSONV1, Formats.JSONV1),
JSONV2(Formats.JSONV2, Formats.JSONV2),
;

final String _accept;
final String _expectedContentType;

GetAllTest(String accept, String expectedContentType)
{
_accept = accept;
_expectedContentType = expectedContentType;
}
}
}

0 comments on commit b0e30d5

Please sign in to comment.