Skip to content

Commit 7339843

Browse files
authored
Issue 1080: Location Post Update (#1163)
Fixes #1080 Adds `FAIL-IF-EXISTS` query parameter to locations create endpoint. Flag defaults to true.
1 parent f8c605a commit 7339843

28 files changed

+465
-85
lines changed

cwms-data-api/src/main/java/cwms/cda/ApiServlet.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
import java.time.DateTimeException;
204204
import java.util.ArrayList;
205205
import java.util.Arrays;
206+
import java.util.HashMap;
206207
import java.util.LinkedHashMap;
207208
import java.util.List;
208209
import java.util.Map;
@@ -408,7 +409,14 @@ public void init() {
408409
ctx.status(HttpServletResponse.SC_BAD_REQUEST).json(re);
409410
})
410411
.exception(AlreadyExists.class, (e, ctx) -> {
411-
CdaError re = new CdaError("Already Exists.");
412+
CdaError re;
413+
if (e.getMessage() == null || e.getMessage().isEmpty()) {
414+
re = new CdaError("Already exists");
415+
} else {
416+
Map<String, String> details = new HashMap<>();
417+
details.put("message", e.getMessage());
418+
re = new CdaError("Already exists", details);
419+
}
412420
logger.atInfo().withCause(e).log(re.toString());
413421
ctx.status(HttpServletResponse.SC_CONFLICT).json(re);
414422
})

cwms-data-api/src/main/java/cwms/cda/api/LocationController.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import static cwms.cda.api.Controllers.CREATE;
3030
import static cwms.cda.api.Controllers.DATUM;
3131
import static cwms.cda.api.Controllers.DELETE;
32+
import static cwms.cda.api.Controllers.FAIL_IF_EXISTS;
3233
import static cwms.cda.api.Controllers.FORMAT;
3334
import static cwms.cda.api.Controllers.GET_ALL;
3435
import static cwms.cda.api.Controllers.GET_ONE;
@@ -280,6 +281,13 @@ public void getOne(@NotNull Context ctx, @NotNull String name) {
280281
@OpenApiContent(from = Location.class, type = Formats.XML)
281282
},
282283
required = true),
284+
queryParams = {
285+
@OpenApiParam(name = FAIL_IF_EXISTS, type = Boolean.class,
286+
description = "Specifies whether to fail if the location already exists. "
287+
+ "Default: true. If true, an error will be returned if the "
288+
+ "location already exists. If false, the existing location will "
289+
+ "be updated with the new values.")
290+
},
283291
description = "Create new CWMS Location",
284292
method = HttpMethod.POST,
285293
path = "/locations",
@@ -296,8 +304,9 @@ public void create(@NotNull Context ctx) {
296304
String formatHeader = ctx.req.getContentType();
297305
ContentType contentType = Formats.parseHeader(formatHeader, Location.class);
298306
Location locationFromBody = Formats.parseContent(contentType, ctx.body(), Location.class);
299-
locationsDao.storeLocation(locationFromBody);
300-
ctx.status(HttpServletResponse.SC_OK).json("Created Location");
307+
boolean failIfExists = ctx.queryParamAsClass(FAIL_IF_EXISTS, Boolean.class).getOrDefault(true);
308+
locationsDao.storeLocation(locationFromBody, failIfExists);
309+
ctx.status(HttpServletResponse.SC_CREATED).json("Created Location");
301310
} catch (IOException ex) {
302311
CdaError re = new CdaError("failed to process request");
303312
logger.log(Level.SEVERE, re.toString(), ex);
@@ -344,7 +353,7 @@ public void update(@NotNull Context ctx, @NotNull String locationId) {
344353
locationsDao.renameLocation(locationId, updatedLocation);
345354
ctx.status(HttpServletResponse.SC_OK).json("Updated and renamed Location");
346355
} else {
347-
locationsDao.storeLocation(updatedLocation);
356+
locationsDao.storeLocation(updatedLocation, false);
348357
ctx.status(HttpServletResponse.SC_OK).json("Updated Location");
349358
}
350359
} catch (NotFoundException e) {

cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,9 @@ public static boolean isAlreadyExists(RuntimeException input) {
425425

426426
retVal = hasCodeOrMessage(sqlException, codes, segments);
427427

428+
} else if (input instanceof AlreadyExists) {
429+
// If the input is already an AlreadyExists exception, then we can just return true.
430+
retVal = true;
428431
}
429432
return retVal;
430433
}
@@ -436,14 +439,18 @@ private static RuntimeException buildAlreadyExists(RuntimeException input) {
436439
DataAccessException dae = (DataAccessException) input;
437440
cause = dae.getCause();
438441
}
442+
AlreadyExists exception;
443+
if (cause instanceof AlreadyExists) {
444+
exception = (AlreadyExists) cause;
445+
} else {
446+
exception = new AlreadyExists(cause);
439447

440-
AlreadyExists exception = new AlreadyExists(cause);
441-
442-
String localizedMessage = cause.getLocalizedMessage();
443-
if (localizedMessage != null) {
444-
String[] parts = localizedMessage.split("\n");
445-
if (parts.length > 1) {
446-
exception = new AlreadyExists(parts[0], cause);
448+
String localizedMessage = cause.getLocalizedMessage();
449+
if (localizedMessage != null) {
450+
String[] parts = localizedMessage.split("\n");
451+
if (parts.length > 1) {
452+
exception = new AlreadyExists(parts[0], cause);
453+
}
447454
}
448455
}
449456
return exception;

cwms-data-api/src/main/java/cwms/cda/data/dao/LocationsDao.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ public interface LocationsDao {
4141

4242
void deleteLocation(String locationName, String officeId, boolean cascadeDelete);
4343

44+
@Deprecated
4445
void storeLocation(Location location) throws IOException;
4546

47+
void storeLocation(Location location, boolean failIfExists) throws IOException;
48+
4649
void renameLocation(String oldLocationName, Location renamedLocation) throws IOException;
4750

4851
FeatureCollection buildFeatureCollection(String names, String units, String officeId);

cwms-data-api/src/main/java/cwms/cda/data/dao/LocationsDaoImpl.java

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@
4343

4444
import cwms.cda.api.enums.Nation;
4545
import cwms.cda.api.enums.Unit;
46+
import cwms.cda.api.errors.AlreadyExists;
4647
import cwms.cda.api.errors.NotFoundException;
48+
import cwms.cda.data.dao.location.kind.LocationUtil;
4749
import cwms.cda.data.dto.Catalog;
4850
import cwms.cda.data.dto.Location;
4951
import cwms.cda.data.dto.catalog.CatalogEntry;
@@ -58,6 +60,7 @@
5860
import java.util.List;
5961
import java.util.Map;
6062
import java.util.Objects;
63+
import java.util.Optional;
6164
import java.util.Set;
6265
import java.util.logging.Level;
6366
import java.util.logging.Logger;
@@ -82,6 +85,7 @@
8285
import usace.cwms.db.dao.util.services.CwmsDbServiceLookup;
8386
import usace.cwms.db.jooq.codegen.packages.CWMS_LOC_PACKAGE;
8487
import usace.cwms.db.jooq.codegen.tables.AV_LOC2;
88+
import usace.cwms.db.jooq.codegen.udt.records.LOCATION_OBJ_T;
8589

8690

8791
public class LocationsDaoImpl extends JooqDao<Location> implements LocationsDao {
@@ -214,28 +218,50 @@ public void deleteLocation(String locationName, String officeId, boolean cascade
214218
});
215219
}
216220

221+
/**
222+
* @deprecated Use {@link #storeLocation(Location, boolean)} instead.
223+
*/
224+
@Deprecated
217225
@Override
218226
public void storeLocation(Location location) throws IOException {
227+
storeLocation(location, false);
228+
}
229+
230+
@Override
231+
public void storeLocation(Location location, boolean failIfExists) throws IOException {
219232
location.validate();
220233
try {
221234
connection(dsl, c -> {
222-
setOffice(c,location);
223-
CwmsDbLoc locJooq = CwmsDbServiceLookup.buildCwmsDb(CwmsDbLoc.class, c);
224-
String elevationUnits = location.getElevationUnits() == null
225-
? Unit.METER.getValue() : location.getElevationUnits();
226-
locJooq.store(c, location.getOfficeId(), location.getName(),
227-
location.getStateInitial(), location.getCountyName(),
228-
location.getTimezoneName(), location.getLocationType(),
229-
location.getLatitude(), location.getLongitude(), location.getElevation(),
230-
elevationUnits, location.getVerticalDatum(),
231-
location.getHorizontalDatum(), location.getPublicName(),
232-
location.getLongName(),
233-
location.getDescription(), location.getActive(),
234-
location.getLocationKind(), location.getMapLabel(),
235-
location.getPublishedLatitude(),
236-
location.getPublishedLongitude(), location.getBoundingOfficeId(),
237-
location.getNation().getName(), location.getNearestCity(), true);
238-
235+
setOffice(c, location);
236+
LOCATION_OBJ_T locationObjT = getLocationObj(location);
237+
Configuration configuration = getDslContext(c, location.getOfficeId()).configuration();
238+
try {
239+
CWMS_LOC_PACKAGE.call_STORE_LOCATION__2(configuration, locationObjT,
240+
formatBool(failIfExists));
241+
} catch (DataAccessException e) {
242+
try {
243+
String dbLocationName = CWMS_LOC_PACKAGE.call_GET_LOCATION_ID(
244+
configuration, location.getName(), location.getBoundingOfficeId());
245+
if (dbLocationName != null && !dbLocationName.isEmpty()) {
246+
String message;
247+
if (dbLocationName.equalsIgnoreCase(location.getName())) {
248+
message = String.format("The location with name: %s already exists in office: %s",
249+
location.getName(), location.getBoundingOfficeId());
250+
} else {
251+
message = String.format("The location with alias: '%s' and proper name: "
252+
+ "'%s' already exists in office: '%s'.",
253+
location.getName(), dbLocationName, location.getBoundingOfficeId());
254+
}
255+
throw new AlreadyExists(message, e);
256+
} else {
257+
throw wrapException(e);
258+
}
259+
} catch (DataAccessException | NotFoundException ignored) {
260+
// If we can't find the location by name, then it doesn't exist.
261+
// We can throw the original exception.
262+
throw wrapException(e);
263+
}
264+
}
239265
});
240266
} catch (DataAccessException ex) {
241267
throw new IOException("Failed to store Location", ex);
@@ -525,8 +551,10 @@ private static LocationCatalogEntry buildCatalogEntry(usace.cwms.db.jooq.codegen
525551
.timeZone(loc.getTIME_ZONE_NAME())
526552
.latitude(loc.getLATITUDE() != null ? loc.getLATITUDE().doubleValue() : null)
527553
.longitude(loc.getLONGITUDE() != null ? loc.getLONGITUDE().doubleValue() : null)
528-
.publishedLatitude(loc.getPUBLISHED_LATITUDE() != null ? loc.getPUBLISHED_LATITUDE().doubleValue() : null)
529-
.publishedLongitude(loc.getPUBLISHED_LONGITUDE() != null ? loc.getPUBLISHED_LONGITUDE().doubleValue() : null)
554+
.publishedLatitude(loc.getPUBLISHED_LATITUDE() != null
555+
? loc.getPUBLISHED_LATITUDE().doubleValue() : null)
556+
.publishedLongitude(loc.getPUBLISHED_LONGITUDE() != null
557+
? loc.getPUBLISHED_LONGITUDE().doubleValue() : null)
530558
.horizontalDatum(loc.getHORIZONTAL_DATUM())
531559
.elevation(loc.getELEVATION())
532560
.unit(loc.getUNIT_ID())
@@ -541,4 +569,35 @@ private static LocationCatalogEntry buildCatalogEntry(usace.cwms.db.jooq.codegen
541569
.build();
542570
}
543571

572+
private static LOCATION_OBJ_T getLocationObj(Location location) {
573+
LOCATION_OBJ_T retval = null;
574+
if (location != null) {
575+
retval = new LOCATION_OBJ_T();
576+
String elevationUnits = location.getElevationUnits() == null
577+
? Unit.METER.getValue() : location.getElevationUnits();
578+
retval.setLOCATION_REF(LocationUtil.getLocationRef(location.getName(), location.getOfficeId()));
579+
retval.setLOCATION_KIND_ID(location.getLocationKind());
580+
retval.setTIME_ZONE_NAME(location.getTimezoneName());
581+
retval.setLATITUDE(toBigDecimal(location.getLatitude()));
582+
retval.setLONGITUDE(toBigDecimal(location.getLongitude()));
583+
retval.setHORIZONTAL_DATUM(location.getHorizontalDatum());
584+
retval.setACTIVE_FLAG(formatBool(location.getActive()));
585+
retval.setDESCRIPTION(location.getDescription());
586+
retval.setELEVATION(toBigDecimal(location.getElevation()));
587+
retval.setELEV_UNIT_ID(elevationUnits);
588+
retval.setCOUNTY_NAME(location.getCountyName());
589+
retval.setBOUNDING_OFFICE_ID(location.getBoundingOfficeId());
590+
retval.setNATION_ID(Optional.ofNullable(location.getNation()).map(Nation::getName).orElse(null));
591+
retval.setMAP_LABEL(location.getMapLabel());
592+
retval.setPUBLIC_NAME(location.getPublicName());
593+
retval.setPUBLISHED_LATITUDE(toBigDecimal(location.getPublishedLatitude()));
594+
retval.setPUBLISHED_LONGITUDE(toBigDecimal(location.getPublishedLongitude()));
595+
retval.setVERTICAL_DATUM(location.getVerticalDatum());
596+
retval.setLONG_NAME(location.getLongName());
597+
retval.setSTATE_INITIAL(location.getStateInitial());
598+
retval.setLOCATION_TYPE(location.getLocationType());
599+
retval.setNEAREST_CITY(location.getNearestCity());
600+
}
601+
return retval;
602+
}
544603
}

cwms-data-api/src/test/java/cwms/cda/api/AccessManagerTestIT.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import static org.junit.jupiter.api.Assertions.assertNotNull;
1818

1919
@Tag("integration")
20-
public class AccessManagerTestIT extends DataApiTestIT {
20+
final class AccessManagerTestIT extends DataApiTestIT {
2121

2222
@ParameterizedTest
2323
@ArgumentsSource(UserSpecSource.class)
@@ -73,14 +73,15 @@ void can_create_with_user(String authType, TestAccounts.KeyUser user, RequestSpe
7373
.log().ifValidationFails(LogDetail.ALL,true)
7474
.contentType("application/json")
7575
.queryParam("office", user.getOperatingOffice())
76+
.queryParam("fail-if-exists", "false")
7677
.spec(authSpec)
7778
.body(json)
7879
.when()
7980
.post( "/locations/")
8081
.then()
8182
.log().ifValidationFails(LogDetail.ALL,true)
8283
.assertThat()
83-
.statusCode(is(HttpServletResponse.SC_OK));
84+
.statusCode(is(HttpServletResponse.SC_CREATED));
8485
}
8586

8687
@ParameterizedTest

cwms-data-api/src/test/java/cwms/cda/api/BasinControllerIT.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ static void setup() throws Exception {
125125
.withNearestCity("Denver")
126126
.build();
127127
try {
128-
locationsDao.storeLocation(loc);
129-
locationsDao.storeLocation(loc2);
128+
locationsDao.storeLocation(loc, false);
129+
locationsDao.storeLocation(loc2, false);
130130
basinDao.storeBasin(BASIN_CONNECT);
131131
} catch (IOException e) {
132132
throw new RuntimeException(e);

cwms-data-api/src/test/java/cwms/cda/api/EmbankmentControllerIT.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@
4040
import org.jooq.DSLContext;
4141
import org.junit.jupiter.api.AfterAll;
4242
import org.junit.jupiter.api.BeforeAll;
43-
import org.junit.jupiter.api.Disabled;
4443
import org.junit.jupiter.api.Test;
4544
import usace.cwms.db.jooq.codegen.packages.CWMS_PROJECT_PACKAGE;
4645
import usace.cwms.db.jooq.codegen.udt.records.PROJECT_OBJ_T;
@@ -54,7 +53,6 @@
5453
import java.time.Instant;
5554

5655
import static cwms.cda.api.Controllers.FAIL_IF_EXISTS;
57-
import static cwms.cda.api.Controllers.OFFICE;
5856
import static cwms.cda.data.dao.DaoTest.getDslContext;
5957
import static cwms.cda.security.ApiKeyIdentityProvider.AUTH_HEADER;
6058
import static io.restassured.RestAssured.given;
@@ -79,23 +77,23 @@ final class EmbankmentControllerIT extends DataApiTestIT {
7977
}
8078

8179
@BeforeAll
82-
public static void setup() throws Exception {
80+
static void setup() throws Exception {
8381
CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
8482
databaseLink.connection(c -> {
8583
try {
8684
DSLContext context = getDslContext(c, EMBANKMENT_LOC.getOfficeId());
8785
LocationsDaoImpl locationsDao = new LocationsDaoImpl(context);
8886
PROJECT_OBJ_T projectObjT = buildProject();
8987
CWMS_PROJECT_PACKAGE.call_STORE_PROJECT(context.configuration(), projectObjT, "T");
90-
locationsDao.storeLocation(EMBANKMENT_LOC);
88+
locationsDao.storeLocation(EMBANKMENT_LOC, false);
9189
} catch (IOException e) {
9290
throw new RuntimeException(e);
9391
}
9492
}, CwmsDataApiSetupCallback.getWebUser());
9593
}
9694

9795
@AfterAll
98-
public static void tearDown() throws Exception {
96+
static void tearDown() throws Exception {
9997

10098
CwmsDatabaseContainer<?> databaseLink = CwmsDataApiSetupCallback.getDatabaseLink();
10199
databaseLink.connection(c -> {

0 commit comments

Comments
 (0)