From 5990c938db76cfb5d202f900a827f6c6ca76e100 Mon Sep 17 00:00:00 2001 From: zack-rma Date: Wed, 18 Dec 2024 14:59:38 -0800 Subject: [PATCH 1/5] Implemented DataSourceResources Controller refactor --- .../org/opendcs/odcsapi/dao/DbException.java | 5 + .../odcsapi/res/DataSourceResources.java | 256 +++++++++++++---- .../odcsapi/res/DataSourceResourcesTest.java | 144 ++++++++++ .../org/opendcs/odcsapi/res/it/BaseIT.java | 2 +- .../odcsapi/res/it/DataSourceResourcesIT.java | 261 ++++++++++++++++++ 5 files changed, 618 insertions(+), 50 deletions(-) create mode 100644 opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java create mode 100644 opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java index 398f14eba..46e35c379 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/dao/DbException.java @@ -32,4 +32,9 @@ public DbException(String message, Exception cause) { super(message, cause); } + + public DbException(String message) + { + super(message); + } } diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java index adfee7703..e0a56778b 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java @@ -15,10 +15,12 @@ package org.opendcs.odcsapi.res; -import java.sql.SQLException; -import java.util.logging.Logger; +import java.util.ArrayList; +import java.util.Properties; +import java.util.Vector; import javax.annotation.security.RolesAllowed; +import javax.servlet.http.HttpServletResponse; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; @@ -31,18 +33,21 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import decodes.db.DataSource; +import decodes.db.DataSourceList; +import decodes.db.DatabaseException; +import decodes.db.DatabaseIO; +import decodes.sql.DbKey; import org.opendcs.odcsapi.beans.ApiDataSource; -import org.opendcs.odcsapi.dao.ApiDataSourceDAO; +import org.opendcs.odcsapi.beans.ApiDataSourceGroupMember; +import org.opendcs.odcsapi.beans.ApiDataSourceRef; import org.opendcs.odcsapi.dao.DbException; import org.opendcs.odcsapi.errorhandling.ErrorCodes; import org.opendcs.odcsapi.errorhandling.WebAppException; -import org.opendcs.odcsapi.hydrojson.DbInterface; import org.opendcs.odcsapi.sec.AuthorizationCheck; -import org.opendcs.odcsapi.util.ApiConstants; -import org.opendcs.odcsapi.util.ApiHttpUtil; @Path("/") -public class DataSourceResources +public class DataSourceResources extends OpenDcsResource { @Context HttpHeaders httpHeaders; @@ -52,12 +57,53 @@ public class DataSourceResources @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response getDataSourceRefs() throws DbException { - Logger.getLogger(ApiConstants.loggerName).fine("getDataSourceRefs"); - try (DbInterface dbi = new DbInterface(); - ApiDataSourceDAO dao = new ApiDataSourceDAO(dbi)) + try { - return ApiHttpUtil.createResponse(dao.readDataSourceRefs()); + DatabaseIO dbio = getLegacyDatabase(); + DataSourceList dsl = new DataSourceList(); + dbio.readDataSourceList(dsl); + return Response.status(HttpServletResponse.SC_OK).entity(map(dsl)).build(); } + catch (DatabaseException ex) + { + throw new DbException("Error reading data source list: " + ex); + } + } + + static ArrayList map(DataSourceList dsl) + { + ArrayList ret = new ArrayList<>(); + for(DataSource ds : dsl.getList()) + { + ApiDataSourceRef adr = new ApiDataSourceRef(); + if (ds.getId() != null) + { + adr.setDataSourceId(ds.getId().getValue()); + } + else + { + adr.setDataSourceId(DbKey.NullKey.getValue()); + } + adr.setName(ds.getName()); + adr.setType(ds.dataSourceType); + adr.setUsedBy(ds.numUsedBy); + adr.setArguments(map(ds.getArguments())); + ret.add(adr); + } + return ret; + } + + static String map(Properties props) + { + if (props == null || props.isEmpty()) + return null; + + StringBuilder retVal = new StringBuilder(); + for (Object key : props.keySet()) + { + retVal.append(key).append("=").append(props.getProperty((String) key)).append(","); + } + return retVal.toString(); } @GET @@ -65,22 +111,76 @@ public Response getDataSourceRefs() throws DbException @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) public Response geDataSource(@QueryParam("datasourceid") Long dataSourceId) - throws WebAppException, DbException, SQLException + throws WebAppException, DbException { if (dataSourceId == null) - throw new WebAppException(ErrorCodes.MISSING_ID, - "Missing required datasourceid parameter."); - - Logger.getLogger(ApiConstants.loggerName).fine("getDataSource id=" + dataSourceId); - try (DbInterface dbi = new DbInterface(); - ApiDataSourceDAO dao = new ApiDataSourceDAO(dbi)) - { - ApiDataSource ret = dao.readDataSource(dataSourceId); - if (ret == null) - throw new WebAppException(ErrorCodes.NO_SUCH_OBJECT, - "No such DECODES data source with id=" + dataSourceId + "."); - return ApiHttpUtil.createResponse(ret); + { + throw new WebAppException(ErrorCodes.MISSING_ID, + "Missing required datasourceid parameter."); } + + try + { + DatabaseIO dbio = getLegacyDatabase(); + DataSource ds = new DataSource(DbKey.createDbKey(dataSourceId)); + dbio.readDataSource(ds); + + if (ds.getName() == null) + { + throw new WebAppException(ErrorCodes.NO_SUCH_OBJECT, + "No such DECODES data source with id=" + dataSourceId + "."); + } + ApiDataSource ret = map(ds); + return Response.status(HttpServletResponse.SC_OK).entity(ret).build(); + } + catch (DatabaseException ex) + { + throw new DbException("Error reading data source: " + ex); + } + } + + static ApiDataSource map(DataSource ds) + { + if (ds == null) + return null; + ApiDataSource ads = new ApiDataSource(); + if (ds.getId() != null) + { + ads.setDataSourceId(ds.getId().getValue()); + } + else + { + ads.setDataSourceId(DbKey.NullKey.getValue()); + } + ads.setName(ds.getName()); + ads.setType(ds.dataSourceType); + ads.setProps(ds.getArguments()); + ads.setGroupMembers(map(ds.groupMembers)); + return ads; + } + + static ArrayList map(Vector groupMembers) + { + if (groupMembers == null) + { + return new ArrayList<>(); + } + ArrayList ret = new ArrayList<>(); + for(DataSource ds : groupMembers) + { + ApiDataSourceGroupMember ads = new ApiDataSourceGroupMember(); + if (ds.getId() != null) + { + ads.setDataSourceId(ds.getId().getValue()); + } + else + { + ads.setDataSourceId(DbKey.NullKey.getValue()); + } + ads.setDataSourceName(ds.getName()); + ret.add(ads); + } + return ret; } @POST @@ -88,18 +188,61 @@ public Response geDataSource(@QueryParam("datasourceid") Long dataSourceId) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) - public Response postDatasource(ApiDataSource datasource) throws WebAppException, DbException, SQLException + public Response postDatasource(ApiDataSource datasource) throws DbException, WebAppException + { + if (datasource == null) + { + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, + "Missing required data source object."); + } + try + { + DatabaseIO dbio = getLegacyDatabase(); + DataSource source = map(datasource); + dbio.writeDataSource(source); + return Response.status(HttpServletResponse.SC_OK) + .entity(map(source)) + .build(); + } + catch (DatabaseException ex) + { + throw new DbException("Error writing data source: " + ex); + } + } + + static DataSource map(ApiDataSource ads) throws DatabaseException { - Logger.getLogger(ApiConstants.loggerName).fine( - "post datasource received datasource " + datasource.getName() - + " with ID=" + datasource.getDataSourceId()); - - try (DbInterface dbi = new DbInterface(); - ApiDataSourceDAO dsDao = new ApiDataSourceDAO(dbi)) + DataSource ds = new DataSource(); + if (ads.getDataSourceId() != null) { - dsDao.writedDataSource(datasource); - return ApiHttpUtil.createResponse(datasource); + ds.setId(DbKey.createDbKey(ads.getDataSourceId())); } + else + { + ds.setId(DbKey.NullKey); + } + ds.setName(ads.getName()); + ds.dataSourceType = ads.getType(); + ds.arguments = ads.getProps(); + ds.numUsedBy = ads.getUsedBy(); + ds.groupMembers = map(ads.getGroupMembers()); + return ds; + } + + static Vector map(ArrayList groupMembers) + { + Vector ret = new Vector<>(); + if (groupMembers == null) + { + return ret; + } + for(ApiDataSourceGroupMember ads : groupMembers) + { + DataSource ds = new DataSource(DbKey.createDbKey(ads.getDataSourceId())); + ds.setName(ads.getDataSourceName()); + ret.add(ds); + } + return ret; } @DELETE @@ -107,23 +250,38 @@ public Response postDatasource(ApiDataSource datasource) throws WebAppException, @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) - public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) throws DbException + public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) throws DbException, WebAppException { - Logger.getLogger(ApiConstants.loggerName).fine( - "DELETE datasource received datasourceid=" + datasourceId); - - // Use username and password to attempt to connect to the database - try (DbInterface dbi = new DbInterface(); - ApiDataSourceDAO dsDao = new ApiDataSourceDAO(dbi)) - { - String errmsg = dsDao.datasourceUsedByRs(datasourceId); - if (errmsg != null) - return ApiHttpUtil.createResponse(" Cannot delete datasource with ID " + datasourceId - + " because it is used by the following routing specs: " - + errmsg, ErrorCodes.NOT_ALLOWED); - - dsDao.deleteDatasource(datasourceId); - return ApiHttpUtil.createResponse("Datasource with ID " + datasourceId + " deleted"); + try + { + if (datasourceId == null) + { + throw new WebAppException(ErrorCodes.MISSING_ID, "Missing required datasourceid parameter."); + } + DatabaseIO dao = getLegacyDatabase(); + DataSource ds = new DataSource(DbKey.createDbKey(datasourceId)); + dao.readDataSource(ds); + if (ds.getName() == null) + { + return Response.status(HttpServletResponse.SC_NOT_FOUND) + .entity("No such data source with ID " + datasourceId).build(); + } + + if (ds.numUsedBy > 0) + { + return Response.status(HttpServletResponse.SC_METHOD_NOT_ALLOWED) + .entity(" Cannot delete datasource with ID " + datasourceId + + " because it is used by the following number of routing specs: " + + ds.numUsedBy).build(); + } + + dao.deleteDataSource(ds); + return Response.status(HttpServletResponse.SC_OK) + .entity("Datasource with ID " + datasourceId + " deleted").build(); + } + catch (DatabaseException ex) + { + throw new DbException("Error deleting data source: " + ex); } } diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java new file mode 100644 index 000000000..80ad0f76d --- /dev/null +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java @@ -0,0 +1,144 @@ +package org.opendcs.odcsapi.res; + +import java.util.ArrayList; +import java.util.Properties; +import java.util.Vector; + +import decodes.db.DataSource; +import decodes.sql.DbKey; +import org.junit.jupiter.api.Test; +import org.opendcs.odcsapi.beans.ApiDataSource; +import org.opendcs.odcsapi.beans.ApiDataSourceGroupMember; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; +import static org.opendcs.odcsapi.res.DataSourceResources.map; + +final class DataSourceResourcesTest +{ + @Test + void testDataSourceMap() throws Exception + { + DataSource ds = new DataSource(); + ds.numUsedBy = 12; + ds.setName("Parent"); + ds.setId(DbKey.createDbKey(57391L)); + Properties properties = new Properties(); + properties.setProperty("key", "value"); + ds.arguments = properties; + ds.dataSourceType = "Test type"; + ds.setDataSourceArg("This is a data source arg"); + Vector groupMembers = new Vector<>(); + DataSource member = new DataSource(); + member.setName("This is a group member"); + groupMembers.add(member); + ds.groupMembers = groupMembers; + + ApiDataSource apiData = map(ds); + assertNotNull(apiData); + assertEquals(apiData.getDataSourceId(), ds.getId().getValue()); + assertEquals(apiData.getName(), ds.getName()); + assertMatch(apiData.getGroupMembers(), ds.groupMembers); + } + + private static void assertMatch(ArrayList groupMembers, Vector groupMemVector) + { + for (ApiDataSourceGroupMember member : groupMembers) + { + boolean found = false; + for (DataSource source : groupMemVector) + { + if (member.getDataSourceId().equals(source.getId().getValue())) + { + found = true; + assertEquals(member.getDataSourceName(), source.getName()); + } + } + if (!found) + { + fail("Unable to find matching group member in list"); + } + } + } + + @Test + void testDataSourceListMap() + { + Vector groupMembers = new Vector<>(); + + ArrayList members = map(groupMembers); + assertNotNull(members); + assertMatch(members, groupMembers); + } + + @Test + void testApiDataSourceMap() throws Exception + { + ApiDataSource dataSource = new ApiDataSource(); + dataSource.setDataSourceId(58559642L); + dataSource.setName("data source name"); + dataSource.setType("Test type"); + dataSource.setUsedBy(12); + Properties props = new Properties(); + props.setProperty("Key", "Value"); + dataSource.setProps(props); + ArrayList memberList = new ArrayList<>(); + ApiDataSourceGroupMember member = new ApiDataSourceGroupMember(); + member.setDataSourceName("Child data source"); + member.setDataSourceId(8675309L); + memberList.add(member); + dataSource.setGroupMembers(memberList); + + DataSource result = map(dataSource); + + assertNotNull(result); + assertEquals(dataSource.getName(), result.getName()); + assertEquals(dataSource.getDataSourceId(), result.getId().getValue()); + assertEquals(dataSource.getUsedBy(), result.numUsedBy); + assertEquals(dataSource.getType(), result.dataSourceType); + assertEquals(dataSource.getProps(), result.arguments); + assertEquals(dataSource.getGroupMembers().size(), result.groupMembers.size()); + for (int i = 0; i < dataSource.getGroupMembers().size(); i++) + { + assertEquals(dataSource.getGroupMembers().get(i).getDataSourceName(), result.groupMembers.get(i).getName()); + assertEquals(dataSource.getGroupMembers().get(i).getDataSourceId(), result.groupMembers.get(i).getId().getValue()); + } + } + + @Test + void testDataSourceGroupMemberMap() + { + ArrayList groupMembers = new ArrayList<>(); + ApiDataSourceGroupMember member = new ApiDataSourceGroupMember(); + member.setDataSourceId(123456789L); + member.setDataSourceName("Test data source"); + groupMembers.add(member); + + Vector result = DataSourceResources.map(groupMembers); + + assertNotNull(result); + assertEquals(groupMembers.size(), result.size()); + for (int i = 0; i < groupMembers.size(); i++) + { + assertEquals(groupMembers.get(i).getDataSourceName(), result.get(i).getName()); + assertEquals(groupMembers.get(i).getDataSourceId(), result.get(i).getId().getValue()); + } + } + + @Test + void testPropertyMap() + { + Properties props = new Properties(); + props.setProperty("key", "value"); + props.setProperty("key2", "value2"); + + ApiDataSource apiData = new ApiDataSource(); + apiData.setProps(props); + + String result = map(apiData.getProps()); + + assertNotNull(result); + assertEquals("key=value,key2=value2,", result); + } +} diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/BaseIT.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/BaseIT.java index 999509a9f..c1b57e797 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/BaseIT.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/BaseIT.java @@ -128,7 +128,7 @@ void authenticate(SessionFilter sessionFilter) void logout(SessionFilter sessionFilter) { - if(DatabaseSetupExtension.getCurrentDbType() == DbType.OPEN_TSDB) + if (DatabaseSetupExtension.getCurrentDbType() == DbType.OPEN_TSDB) { given() .log().ifValidationFails(LogDetail.ALL, true) diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java new file mode 100644 index 000000000..ff6f0d477 --- /dev/null +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java @@ -0,0 +1,261 @@ +package org.opendcs.odcsapi.res.it; + + +import java.util.ArrayList; +import java.util.Properties; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.restassured.filter.log.LogDetail; +import io.restassured.filter.session.SessionFilter; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; +import org.opendcs.odcsapi.beans.ApiDataSource; +import org.opendcs.odcsapi.beans.ApiDataSourceGroupMember; +import org.opendcs.odcsapi.fixtures.DatabaseContextProvider; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.greaterThan; + +@Tag("integration") +@ExtendWith(DatabaseContextProvider.class) +final class DataSourceResourcesIT extends BaseIT +{ + private static SessionFilter sessionFilter; + private static Long sourceId; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @BeforeEach + void setUp() throws Exception + { + setUpCreds(); + sessionFilter = new SessionFilter(); + + authenticate(sessionFilter); + + ApiDataSource dsGroupMem = new ApiDataSource(); + dsGroupMem.setName("Sensor Data Value"); + dsGroupMem.setUsedBy(2); + dsGroupMem.setType("Sensor"); + Properties props = new Properties(); + props.setProperty("country", "USA"); + dsGroupMem.setProps(props); + + String dsJson = OBJECT_MAPPER.writeValueAsString(dsGroupMem); + + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(dsJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + Long memberSourceId = response.body().jsonPath().getLong("dataSourceId"); + + ApiDataSource ds = new ApiDataSource(); + ds.setName("Sensor Data"); + ds.setUsedBy(12); + ds.setType("Sensor"); + Properties properties = new Properties(); + properties.setProperty("country", "USA"); + ds.setProps(properties); + ArrayList groupMembers = new ArrayList<>(); + ApiDataSourceGroupMember groupMember = new ApiDataSourceGroupMember(); + groupMember.setDataSourceName(dsGroupMem.getName()); + groupMember.setDataSourceId(memberSourceId); + groupMembers.add(groupMember); + ds.setGroupMembers(groupMembers); + + dsJson = OBJECT_MAPPER.writeValueAsString(ds); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(dsJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + sourceId = response.body().jsonPath().getLong("dataSourceId"); + } + + @AfterEach + void tearDown() + { + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .queryParam("datasourceid", sourceId) + .filter(sessionFilter) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + ; + + logout(sessionFilter); + } + + @TestTemplate + void testDataSourceRefs() + { + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("datasourcerefs") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .body("size()", greaterThan(0)) + ; + } + + @TestTemplate + void testGetDataSource() + { + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .queryParam("datasourceid", sourceId) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + ; + } + + @TestTemplate + void testPostAndDeleteDataSource() throws Exception + { + ApiDataSource dsGroupMem = new ApiDataSource(); + dsGroupMem.setName("Satellite Data Value"); + dsGroupMem.setUsedBy(2); + dsGroupMem.setType("Satellite"); + Properties props = new Properties(); + props.setProperty("country", "USA"); + dsGroupMem.setProps(props); + + String dsJson = OBJECT_MAPPER.writeValueAsString(dsGroupMem); + + ExtractableResponse response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(dsJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + Long memberSourceId = response.body().jsonPath().getLong("dataSourceId"); + + ApiDataSource ds = new ApiDataSource(); + ds.setName("Satellite Data"); + ds.setUsedBy(12); + ds.setType("Satellite"); + Properties properties = new Properties(); + properties.setProperty("location", "low Earth orbit"); + ds.setProps(properties); + ArrayList groupMembers = new ArrayList<>(); + ApiDataSourceGroupMember groupMember = new ApiDataSourceGroupMember(); + groupMember.setDataSourceName(dsGroupMem.getName()); + groupMember.setDataSourceId(memberSourceId); + groupMembers.add(groupMember); + ds.setGroupMembers(groupMembers); + + dsJson = OBJECT_MAPPER.writeValueAsString(ds); + + response = given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .filter(sessionFilter) + .body(dsJson) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .extract() + ; + + Long newSourceId = response.body().jsonPath().getLong("dataSourceId"); + + given() + .log().ifValidationFails(LogDetail.ALL, true) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", authHeader) + .queryParam("datasourceid", newSourceId) + .filter(sessionFilter) + .when() + .redirects().follow(true) + .redirects().max(3) + .delete("datasource") + .then() + .log().ifValidationFails(LogDetail.ALL, true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + ; + } +} From f5338466080eaf63f046bad54ba540a1752b9c4e Mon Sep 17 00:00:00 2001 From: zack-rma Date: Wed, 8 Jan 2025 14:39:14 -0800 Subject: [PATCH 2/5] Updated mapping data types, database closure --- .../odcsapi/res/DataSourceResources.java | 45 +++++++++++++------ .../odcsapi/res/DataSourceResourcesTest.java | 5 ++- .../odcsapi/res/it/DataSourceResourcesIT.java | 2 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java index e0a56778b..b07b1716b 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java @@ -16,6 +16,7 @@ package org.opendcs.odcsapi.res; import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.Vector; @@ -49,6 +50,8 @@ @Path("/") public class DataSourceResources extends OpenDcsResource { + private DatabaseIO dbIo; + @Context HttpHeaders httpHeaders; @GET @@ -59,15 +62,19 @@ public Response getDataSourceRefs() throws DbException { try { - DatabaseIO dbio = getLegacyDatabase(); + dbIo = getLegacyDatabase(); DataSourceList dsl = new DataSourceList(); - dbio.readDataSourceList(dsl); + dbIo.readDataSourceList(dsl); return Response.status(HttpServletResponse.SC_OK).entity(map(dsl)).build(); } catch (DatabaseException ex) { throw new DbException("Error reading data source list: " + ex); } + finally + { + dbIo.close(); + } } static ArrayList map(DataSourceList dsl) @@ -110,7 +117,7 @@ static String map(Properties props) @Path("datasource") @Produces(MediaType.APPLICATION_JSON) @RolesAllowed({AuthorizationCheck.ODCS_API_GUEST}) - public Response geDataSource(@QueryParam("datasourceid") Long dataSourceId) + public Response getDataSource(@QueryParam("datasourceid") Long dataSourceId) throws WebAppException, DbException { if (dataSourceId == null) @@ -121,9 +128,9 @@ public Response geDataSource(@QueryParam("datasourceid") Long dataSourceId) try { - DatabaseIO dbio = getLegacyDatabase(); + dbIo = getLegacyDatabase(); DataSource ds = new DataSource(DbKey.createDbKey(dataSourceId)); - dbio.readDataSource(ds); + dbIo.readDataSource(ds); if (ds.getName() == null) { @@ -137,6 +144,10 @@ public Response geDataSource(@QueryParam("datasourceid") Long dataSourceId) { throw new DbException("Error reading data source: " + ex); } + finally + { + dbIo.close(); + } } static ApiDataSource map(DataSource ds) @@ -159,13 +170,13 @@ static ApiDataSource map(DataSource ds) return ads; } - static ArrayList map(Vector groupMembers) + static List map(Vector groupMembers) { if (groupMembers == null) { return new ArrayList<>(); } - ArrayList ret = new ArrayList<>(); + List ret = new ArrayList<>(); for(DataSource ds : groupMembers) { ApiDataSourceGroupMember ads = new ApiDataSourceGroupMember(); @@ -197,9 +208,9 @@ public Response postDatasource(ApiDataSource datasource) throws DbException, Web } try { - DatabaseIO dbio = getLegacyDatabase(); + dbIo = getLegacyDatabase(); DataSource source = map(datasource); - dbio.writeDataSource(source); + dbIo.writeDataSource(source); return Response.status(HttpServletResponse.SC_OK) .entity(map(source)) .build(); @@ -208,6 +219,10 @@ public Response postDatasource(ApiDataSource datasource) throws DbException, Web { throw new DbException("Error writing data source: " + ex); } + finally + { + dbIo.close(); + } } static DataSource map(ApiDataSource ads) throws DatabaseException @@ -229,7 +244,7 @@ static DataSource map(ApiDataSource ads) throws DatabaseException return ds; } - static Vector map(ArrayList groupMembers) + static Vector map(List groupMembers) { Vector ret = new Vector<>(); if (groupMembers == null) @@ -258,9 +273,9 @@ public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) { throw new WebAppException(ErrorCodes.MISSING_ID, "Missing required datasourceid parameter."); } - DatabaseIO dao = getLegacyDatabase(); + dbIo = getLegacyDatabase(); DataSource ds = new DataSource(DbKey.createDbKey(datasourceId)); - dao.readDataSource(ds); + dbIo.readDataSource(ds); if (ds.getName() == null) { return Response.status(HttpServletResponse.SC_NOT_FOUND) @@ -275,7 +290,7 @@ public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) + ds.numUsedBy).build(); } - dao.deleteDataSource(ds); + dbIo.deleteDataSource(ds); return Response.status(HttpServletResponse.SC_OK) .entity("Datasource with ID " + datasourceId + " deleted").build(); } @@ -283,6 +298,10 @@ public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) { throw new DbException("Error deleting data source: " + ex); } + finally + { + dbIo.close(); + } } diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java index 80ad0f76d..aeb370763 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java @@ -1,6 +1,7 @@ package org.opendcs.odcsapi.res; import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.Vector; @@ -42,7 +43,7 @@ void testDataSourceMap() throws Exception assertMatch(apiData.getGroupMembers(), ds.groupMembers); } - private static void assertMatch(ArrayList groupMembers, Vector groupMemVector) + private static void assertMatch(List groupMembers, Vector groupMemVector) { for (ApiDataSourceGroupMember member : groupMembers) { @@ -67,7 +68,7 @@ void testDataSourceListMap() { Vector groupMembers = new Vector<>(); - ArrayList members = map(groupMembers); + List members = map(groupMembers); assertNotNull(members); assertMatch(members, groupMembers); } diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java index ff6f0d477..ee4bbae21 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/it/DataSourceResourcesIT.java @@ -24,7 +24,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.greaterThan; -@Tag("integration") +@Tag("integration-opentsdb-only") @ExtendWith(DatabaseContextProvider.class) final class DataSourceResourcesIT extends BaseIT { From e32fb7054f240f9975a88d8e22e2d2e9e840083d Mon Sep 17 00:00:00 2001 From: zack-rma Date: Wed, 8 Jan 2025 17:18:09 -0800 Subject: [PATCH 3/5] Updated mapping and tests, added property parsing --- .../odcsapi/res/DataSourceResources.java | 61 +++++++++++++++---- .../odcsapi/res/DataSourceResourcesTest.java | 45 +++++++++++++- 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java index b07b1716b..cdaefa8cc 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java @@ -43,7 +43,6 @@ import org.opendcs.odcsapi.beans.ApiDataSourceGroupMember; import org.opendcs.odcsapi.beans.ApiDataSourceRef; import org.opendcs.odcsapi.dao.DbException; -import org.opendcs.odcsapi.errorhandling.ErrorCodes; import org.opendcs.odcsapi.errorhandling.WebAppException; import org.opendcs.odcsapi.sec.AuthorizationCheck; @@ -94,13 +93,20 @@ static ArrayList map(DataSourceList dsl) adr.setName(ds.getName()); adr.setType(ds.dataSourceType); adr.setUsedBy(ds.numUsedBy); - adr.setArguments(map(ds.getArguments())); + if (ds.getArguments() != null) + { + adr.setArguments(propsToString(ds.getArguments())); + } + else + { + adr.setArguments(ds.getDataSourceArg()); + } ret.add(adr); } return ret; } - static String map(Properties props) + static String propsToString(Properties props) { if (props == null || props.isEmpty()) return null; @@ -110,7 +116,7 @@ static String map(Properties props) { retVal.append(key).append("=").append(props.getProperty((String) key)).append(","); } - return retVal.toString(); + return retVal.substring(0, retVal.length() - 1); } @GET @@ -120,9 +126,10 @@ static String map(Properties props) public Response getDataSource(@QueryParam("datasourceid") Long dataSourceId) throws WebAppException, DbException { + String notFound = "No such DECODES data source with id="; if (dataSourceId == null) { - throw new WebAppException(ErrorCodes.MISSING_ID, + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, "Missing required datasourceid parameter."); } @@ -134,14 +141,19 @@ public Response getDataSource(@QueryParam("datasourceid") Long dataSourceId) if (ds.getName() == null) { - throw new WebAppException(ErrorCodes.NO_SUCH_OBJECT, - "No such DECODES data source with id=" + dataSourceId + "."); + throw new WebAppException(HttpServletResponse.SC_NOT_FOUND, + notFound + dataSourceId + "."); } ApiDataSource ret = map(ds); return Response.status(HttpServletResponse.SC_OK).entity(ret).build(); } catch (DatabaseException ex) { + if (ex.getMessage().contains("No DataSource found with id")) + { + return Response.status(HttpServletResponse.SC_NOT_FOUND) + .entity(notFound + dataSourceId + ".").build(); + } throw new DbException("Error reading data source: " + ex); } finally @@ -165,8 +177,16 @@ static ApiDataSource map(DataSource ds) } ads.setName(ds.getName()); ads.setType(ds.dataSourceType); - ads.setProps(ds.getArguments()); + if (ds.getArguments() != null) + { + ads.setProps(ds.getArguments()); + } + else + { + ads.setProps(parseProps(ds.getDataSourceArg())); + } ads.setGroupMembers(map(ds.groupMembers)); + ads.setUsedBy(ds.numUsedBy); return ads; } @@ -239,6 +259,7 @@ static DataSource map(ApiDataSource ads) throws DatabaseException ds.setName(ads.getName()); ds.dataSourceType = ads.getType(); ds.arguments = ads.getProps(); + ds.setDataSourceArg(propsToString(ads.getProps())); ds.numUsedBy = ads.getUsedBy(); ds.groupMembers = map(ads.getGroupMembers()); return ds; @@ -267,12 +288,12 @@ static Vector map(List groupMembers) @RolesAllowed({AuthorizationCheck.ODCS_API_ADMIN, AuthorizationCheck.ODCS_API_USER}) public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) throws DbException, WebAppException { + if (datasourceId == null) + { + throw new WebAppException(HttpServletResponse.SC_BAD_REQUEST, "Missing required datasourceid parameter."); + } try { - if (datasourceId == null) - { - throw new WebAppException(ErrorCodes.MISSING_ID, "Missing required datasourceid parameter."); - } dbIo = getLegacyDatabase(); DataSource ds = new DataSource(DbKey.createDbKey(datasourceId)); dbIo.readDataSource(ds); @@ -304,5 +325,19 @@ public Response deleteDatasource(@QueryParam("datasourceid") Long datasourceId) } } - + static Properties parseProps(String properties) + { + if (properties == null || properties.isEmpty()) + { + return new Properties(); + } + Properties props = new Properties(); + String[] pairs = properties.split(","); + for (String pair : pairs) + { + String[] keyValue = pair.split("="); + props.setProperty(keyValue[0], keyValue[1]); + } + return props; + } } diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java index aeb370763..f3b2c6d9e 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java @@ -6,15 +6,18 @@ import java.util.Vector; import decodes.db.DataSource; +import decodes.db.DataSourceList; import decodes.sql.DbKey; import org.junit.jupiter.api.Test; import org.opendcs.odcsapi.beans.ApiDataSource; import org.opendcs.odcsapi.beans.ApiDataSourceGroupMember; +import org.opendcs.odcsapi.beans.ApiDataSourceRef; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import static org.opendcs.odcsapi.res.DataSourceResources.map; +import static org.opendcs.odcsapi.res.DataSourceResources.parseProps; final class DataSourceResourcesTest { @@ -37,10 +40,14 @@ void testDataSourceMap() throws Exception ds.groupMembers = groupMembers; ApiDataSource apiData = map(ds); + assertNotNull(apiData); assertEquals(apiData.getDataSourceId(), ds.getId().getValue()); assertEquals(apiData.getName(), ds.getName()); assertMatch(apiData.getGroupMembers(), ds.groupMembers); + assertEquals(apiData.getUsedBy(), ds.numUsedBy); + assertEquals(apiData.getType(), ds.dataSourceType); + assertEquals(apiData.getProps(), ds.arguments); } private static void assertMatch(List groupMembers, Vector groupMemVector) @@ -127,6 +134,26 @@ void testDataSourceGroupMemberMap() } } + @Test + void testSourceListToApiRefMap() throws Exception + { + DataSourceList list = new DataSourceList(); + DataSource ds = new DataSource(); + ds.setName("Test"); + ds.setDataSourceArg("test=true"); + ds.setId(DbKey.createDbKey(12345L)); + list.add(ds); + + List result = map(list); + + assertNotNull(result); + ApiDataSourceRef ref = result.get(0); + assertNotNull(ref); + assertEquals(ds.getName(), ref.getName()); + assertEquals(ds.getId().getValue(), ref.getDataSourceId()); + assertEquals(ds.getDataSourceArg(), ref.getArguments()); + } + @Test void testPropertyMap() { @@ -137,9 +164,23 @@ void testPropertyMap() ApiDataSource apiData = new ApiDataSource(); apiData.setProps(props); - String result = map(apiData.getProps()); + String result = DataSourceResources.propsToString(apiData.getProps()); assertNotNull(result); - assertEquals("key=value,key2=value2,", result); + assertEquals("key=value,key2=value2", result); + } + + @Test + void testPropertyStringParser() + { + String propString = "key=value,key2=value2"; + Properties result = parseProps(propString); + assertEquals(2, result.size()); + assertEquals("value", result.getProperty("key")); + assertEquals("value2", result.getProperty("key2")); + + propString = ""; + result = parseProps(propString); + assertEquals(0, result.size()); } } From a1d6d00fbc90ec8a3ca2f3985b1002a1f107a439 Mon Sep 17 00:00:00 2001 From: zack-rma Date: Wed, 8 Jan 2025 17:24:55 -0800 Subject: [PATCH 4/5] Updated mapping, test --- .../java/org/opendcs/odcsapi/res/DataSourceResources.java | 2 +- .../java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java index cdaefa8cc..a6c6cd095 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java @@ -109,7 +109,7 @@ static ArrayList map(DataSourceList dsl) static String propsToString(Properties props) { if (props == null || props.isEmpty()) - return null; + return ""; StringBuilder retVal = new StringBuilder(); for (Object key : props.keySet()) diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java index f3b2c6d9e..8f2e501d1 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java @@ -168,6 +168,10 @@ void testPropertyMap() assertNotNull(result); assertEquals("key=value,key2=value2", result); + + props = new Properties(); + result = DataSourceResources.propsToString(props); + assertEquals("", result); } @Test From 172ceef0e56a4759f9bf870e29cc620b96a004f7 Mon Sep 17 00:00:00 2001 From: zack-rma Date: Wed, 8 Jan 2025 17:30:53 -0800 Subject: [PATCH 5/5] Added regex pattern compilation --- .../java/org/opendcs/odcsapi/res/DataSourceResources.java | 7 +++++-- .../org/opendcs/odcsapi/res/DataSourceResourcesTest.java | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java index a6c6cd095..e0e64d26a 100644 --- a/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java +++ b/opendcs-rest-api/src/main/java/org/opendcs/odcsapi/res/DataSourceResources.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Properties; import java.util.Vector; +import java.util.regex.Pattern; import javax.annotation.security.RolesAllowed; import javax.servlet.http.HttpServletResponse; @@ -50,6 +51,8 @@ public class DataSourceResources extends OpenDcsResource { private DatabaseIO dbIo; + private static final Pattern COMMA = Pattern.compile(","); + private static final Pattern EQUAL = Pattern.compile("="); @Context HttpHeaders httpHeaders; @@ -332,10 +335,10 @@ static Properties parseProps(String properties) return new Properties(); } Properties props = new Properties(); - String[] pairs = properties.split(","); + String[] pairs = properties.split(COMMA.pattern()); for (String pair : pairs) { - String[] keyValue = pair.split("="); + String[] keyValue = pair.split(EQUAL.pattern()); props.setProperty(keyValue[0], keyValue[1]); } return props; diff --git a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java index 8f2e501d1..637449b2b 100644 --- a/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java +++ b/opendcs-rest-api/src/test/java/org/opendcs/odcsapi/res/DataSourceResourcesTest.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.fail; import static org.opendcs.odcsapi.res.DataSourceResources.map; import static org.opendcs.odcsapi.res.DataSourceResources.parseProps; +import static org.opendcs.odcsapi.res.DataSourceResources.propsToString; final class DataSourceResourcesTest { @@ -164,13 +165,13 @@ void testPropertyMap() ApiDataSource apiData = new ApiDataSource(); apiData.setProps(props); - String result = DataSourceResources.propsToString(apiData.getProps()); + String result = propsToString(apiData.getProps()); assertNotNull(result); assertEquals("key=value,key2=value2", result); props = new Properties(); - result = DataSourceResources.propsToString(props); + result = propsToString(props); assertEquals("", result); }