Skip to content

Commit

Permalink
Create dataset Functionality (#73)
Browse files Browse the repository at this point in the history
CatalogResource now a first class entity; able to invoke api operations.
Dataset first entity to utilise api operations available to children of CatalogResources. Create operation supported.
CatalogResource serialisation now supported.
CatalogResource varArgs now parsed.
Integration tests now added using WireMock.
  • Loading branch information
knighto82 authored Nov 21, 2024
1 parent 0ded2a5 commit 682387f
Show file tree
Hide file tree
Showing 38 changed files with 1,244 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package io.github.jpmorganchase.fusion.packaging;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
import io.github.jpmorganchase.fusion.Fusion;
import io.github.jpmorganchase.fusion.FusionConfiguration;
import io.github.jpmorganchase.fusion.model.Dataset;
import io.github.jpmorganchase.fusion.test.TestUtils;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static io.github.jpmorganchase.fusion.test.TestUtils.listOf;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;

@ExtendWith(WireMockExtension.class)
public class DatasetOperationsIT {

private static final Logger logger =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

@RegisterExtension
public static WireMockExtension wireMockRule = WireMockExtension.newInstance().options(WireMockConfiguration.wireMockConfig().dynamicPort()).build();

private Fusion sdk;

@BeforeEach
public void setUp() {
int port = wireMockRule.getRuntimeInfo().getHttpPort();
logger.debug("Wiremock is configured to port {}", port);

sdk = Fusion.builder()
.bearerToken("my-token")
.configuration(FusionConfiguration.builder()
.rootURL("http://localhost:" + port + "/")
.build()).build();
}

@Test
public void testCreateDataset() {
// Given
wireMockRule.stubFor(WireMock.post(WireMock.urlEqualTo("/catalogs/common/datasets/SD0001"))
.withRequestBody(equalToJson(TestUtils.loadJsonForIt("dataset/dataset-SD0001-create-request.json")))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("dataset/dataset-create-response.json")));

Dataset dataset = sdk.builders().dataset()
.identifier("SD0001")
.description("Sample dataset description 1")
.linkedEntity("SD0001/")
.title("Sample Dataset 1 | North America")
.frequency("Daily")
.build();

// When
dataset.create();

// Then Verify the response
//TODO :: Contract for response of dataset.create() needs to be decided
}

@Test
public void testCreateDatasetWithVarArgs() {
// Given
wireMockRule.stubFor(WireMock.post(WireMock.urlEqualTo("/catalogs/common/datasets/SD0002"))
.withRequestBody(equalToJson(TestUtils.loadJsonForIt("dataset/dataset-SD0002-create-request.json")))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("dataset/dataset-create-response.json")));

Dataset dataset = sdk.builders().dataset()
.identifier("SD0002")
.description("Sample dataset description 2")
.linkedEntity("SD0002/")
.title("Sample Dataset 2 | North America")
.frequency("Daily")
.varArg("category", listOf("Category 2"))
.varArg("createdDate", "2022-02-06")
.varArg("coverageStartDate", "2022-02-06")
.varArg("coverageEndDate", "2023-03-09")
.varArg("isThirdPartyData", Boolean.FALSE)
.varArg("isInternalOnlyDataset", Boolean.FALSE)
.varArg("language", "English")
.varArg("maintainer", "Maintainer 2")
.varArg("modifiedDate", "2023-03-09")
.varArg("publisher", "Publisher 2")
.varArg("region", listOf("North America"))
.varArg("source", listOf("Source System 2"))
.varArg("subCategory", listOf("Subcategory 2"))
.varArg("tag", listOf("Tag2"))
.varArg("isRestricted", Boolean.FALSE)
.varArg("isRawData", Boolean.FALSE)
.varArg("hasSample", Boolean.FALSE)
.build();

// When
dataset.create();


// Then Verify the response
//TODO :: Contract for response of dataset.create() needs to be decided
}

@Test
public void testCreateDatasetOverrideDefaultCatalog() {
// Given
wireMockRule.stubFor(WireMock.post(WireMock.urlEqualTo("/catalogs/foobar/datasets/SD0001"))
.withRequestBody(equalToJson(TestUtils.loadJsonForIt("dataset/dataset-SD0001-create-request.json")))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("dataset/dataset-create-response.json")));

Dataset dataset = sdk.builders().dataset()
.identifier("SD0001")
.description("Sample dataset description 1")
.linkedEntity("SD0001/")
.title("Sample Dataset 1 | North America")
.frequency("Daily")
.catalogIdentifier("foobar")
.build();

// When
dataset.create();

// Then Verify the response
//TODO :: Contract for response of dataset.create() needs to be decided
}

@Test
public void testListDatasets() {
// Given
wireMockRule.stubFor(WireMock.get(WireMock.urlEqualTo("/catalogs/common/datasets"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("dataset/multiple-dataset-response.json")));

// When
Map<String, Dataset> datasets = sdk.listDatasets();

// Then Verify the response
MatcherAssert.assertThat(datasets.containsKey("SD0001"), is(equalTo(true)));
MatcherAssert.assertThat(datasets.containsKey("SD0002"), is(equalTo(true)));
MatcherAssert.assertThat(datasets.containsKey("SD0003"), is(equalTo(true)));

}

@Test
public void testListDatasetsUsingIdContains() {
// Given
wireMockRule.stubFor(WireMock.get(WireMock.urlEqualTo("/catalogs/common/datasets"))
.willReturn(WireMock.aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)
.withBodyFile("dataset/multiple-dataset-response.json")));

// When
Map<String, Dataset> datasets = sdk.listDatasets("common", "SD0001", true);

// Then Verify the response
MatcherAssert.assertThat(datasets.containsKey("SD0001"), is(equalTo(true)));
MatcherAssert.assertThat(datasets.containsKey("SD0002"), is(equalTo(false)));
MatcherAssert.assertThat(datasets.containsKey("SD0003"), is(equalTo(false)));

}

}
31 changes: 29 additions & 2 deletions src/main/java/io/github/jpmorganchase/fusion/Fusion.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package io.github.jpmorganchase.fusion;

import static io.github.jpmorganchase.fusion.filter.DatasetFilter.filterDatasets;

import io.github.jpmorganchase.fusion.api.APIManager;
import io.github.jpmorganchase.fusion.api.FusionAPIManager;
import io.github.jpmorganchase.fusion.api.exception.APICallException;
import io.github.jpmorganchase.fusion.api.exception.ApiInputValidationException;
import io.github.jpmorganchase.fusion.api.exception.FileDownloadException;
import io.github.jpmorganchase.fusion.api.exception.FileUploadException;
import io.github.jpmorganchase.fusion.builders.APIConfiguredBuilders;
import io.github.jpmorganchase.fusion.builders.Builders;
import io.github.jpmorganchase.fusion.http.Client;
import io.github.jpmorganchase.fusion.http.JdkClient;
import io.github.jpmorganchase.fusion.model.*;
Expand All @@ -19,6 +23,8 @@
import io.github.jpmorganchase.fusion.parsing.APIResponseParser;
import io.github.jpmorganchase.fusion.parsing.GsonAPIResponseParser;
import io.github.jpmorganchase.fusion.parsing.ParsingException;
import io.github.jpmorganchase.fusion.serializing.APIRequestSerializer;
import io.github.jpmorganchase.fusion.serializing.GsonAPIRequestSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
Expand All @@ -41,13 +47,17 @@ public class Fusion {
private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

private APIManager api;
private Builders builders;
private String defaultCatalog;
private String defaultPath;
private String rootURL;

@Builder.Default
private APIResponseParser responseParser = new GsonAPIResponseParser();

@Builder.Default
private APIRequestSerializer requestSerializer = new GsonAPIRequestSerializer();

/**
* Get the default download path - Please see default {@link FusionConfiguration}
*/
Expand Down Expand Up @@ -187,11 +197,10 @@ public Map<String, DataProduct> listProducts() {
* @throws ParsingException if the response from Fusion could not be parsed successfully
* @throws OAuthException if a token could not be retrieved for authentication
*/
// TODO: Search parameters
public Map<String, Dataset> listDatasets(String catalogName, String contains, boolean idContains) {
String url = String.format("%1scatalogs/%2s/datasets", this.rootURL, catalogName);
String json = this.api.callAPI(url);
return responseParser.parseDatasetResponse(json);
return filterDatasets(responseParser.parseDatasetResponse(json), contains, idContains);
}

/**
Expand Down Expand Up @@ -772,6 +781,16 @@ public void upload(
this.upload(catalogName, dataset, seriesMember, distribution, data, dataDate, dataDate, dataDate, headers);
}

/**
* Returns a builder for creating a {@link io.github.jpmorganchase.fusion.model.Dataset} object.
* The builder can be used to set properties and then create or update an instance of {@link io.github.jpmorganchase.fusion.model.Dataset}.
*
* @return {@link Builders} The object that provides access to specific model builders for different types of datasets.
*/
public Builders builders() {
return this.builders;
}

public static FusionBuilder builder() {
return new CustomFusionBuilder();
}
Expand All @@ -780,6 +799,7 @@ public static class FusionBuilder {

protected Client client;
protected APIManager api;
protected Builders builders;
protected String rootURL;
protected String defaultCatalog;
protected String defaultPath;
Expand Down Expand Up @@ -867,6 +887,13 @@ public Fusion build() {
.build();
}

if (Objects.isNull(builders)) {
builders = APIConfiguredBuilders.builder()
.apiManager(api)
.configuration(configuration)
.build();
}

return super.build();
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/io/github/jpmorganchase/fusion/api/APIManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,26 @@
import io.github.jpmorganchase.fusion.api.exception.APICallException;
import io.github.jpmorganchase.fusion.api.operations.APIDownloadOperations;
import io.github.jpmorganchase.fusion.api.operations.APIUploadOperations;
import io.github.jpmorganchase.fusion.model.CatalogResource;

public interface APIManager extends APIDownloadOperations, APIUploadOperations {

/**
* Sends a GET request to the specified API endpoint.
*
* @param apiPath the API endpoint path to which the GET request will be sent
* @return the response body as a {@code String} if the request is successful
* @throws APICallException if the response status indicates an error or the request fails
*/
String callAPI(String apiPath) throws APICallException;

/**
* Sends a POST request to the specified API endpoint with the provided catalog resource.
*
* @param apiPath the API endpoint path to which the POST request will be sent
* @param catalogResource the resource object to be serialized and sent as the request body
* @return the response body as a {@code String} if the request is successful
* @throws APICallException if the response status indicates an error or the request fails
*/
String callAPIToPost(String apiPath, CatalogResource catalogResource);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@
import io.github.jpmorganchase.fusion.http.Client;
import io.github.jpmorganchase.fusion.http.HttpResponse;
import io.github.jpmorganchase.fusion.http.JdkClient;
import io.github.jpmorganchase.fusion.model.CatalogResource;
import io.github.jpmorganchase.fusion.oauth.credential.BearerTokenCredentials;
import io.github.jpmorganchase.fusion.oauth.provider.FusionTokenProvider;
import io.github.jpmorganchase.fusion.serializing.APIRequestSerializer;
import io.github.jpmorganchase.fusion.serializing.GsonAPIRequestSerializer;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -32,14 +35,24 @@ public class FusionAPIManager implements APIManager {
private final APIDownloadOperations downloader;
private APIUploadOperations uploader;

@Builder.Default
private APIRequestSerializer serializer = new GsonAPIRequestSerializer();

public void updateBearerToken(String token) {
tokenProvider.updateCredentials(new BearerTokenCredentials(token));
}

/**
* Call the API with the path provided and return the JSON response.
* Sends a GET request to the specified API endpoint.
*
* <p>This method constructs the necessary authorization headers using a bearer token from
* the {@code tokenProvider} and sends a GET request to the specified {@code apiPath} using
* the {@code httpClient}. It then checks the HTTP response status for errors and returns the
* response body if the request is successful.
*
* @param apiPath appended to the base URL
* @param apiPath the API endpoint path to which the GET request will be sent
* @return the response body as a {@code String} if the request is successful
* @throws APICallException if the response status indicates an error or the request fails
*/
@Override
public String callAPI(String apiPath) throws APICallException {
Expand All @@ -51,6 +64,29 @@ public String callAPI(String apiPath) throws APICallException {
return response.getBody();
}

/**
* Sends a POST request to the specified API endpoint with the provided catalog resource.
*
* <p>This method constructs the necessary authorization headers using a bearer token from
* the {@code tokenProvider}, serializes the given {@code catalogResource} into JSON,
* and sends a POST request to the specified {@code apiPath} using the {@code httpClient}.
* It then checks the HTTP response status for errors and returns the response body if successful.
*
* @param apiPath the API endpoint path to which the POST request will be sent
* @param catalogResource the resource object to be serialized and sent as the request body
* @return the response body as a {@code String} if the request is successful
* @throws APICallException if the response status indicates an error or the request fails
*/
@Override
public String callAPIToPost(String apiPath, CatalogResource catalogResource) throws APICallException {
Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.put("Authorization", "Bearer " + tokenProvider.getSessionBearerToken());

HttpResponse<String> response = httpClient.post(apiPath, requestHeaders, serializer.serialize(catalogResource));
checkResponseStatus(response);
return response.getBody();
}

@Override
public void callAPIFileDownload(
String apiPath, String fileName, String catalog, String dataset, Map<String, String> headers)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.jpmorganchase.fusion.builders;

import io.github.jpmorganchase.fusion.FusionConfiguration;
import io.github.jpmorganchase.fusion.api.APIManager;
import io.github.jpmorganchase.fusion.model.Dataset;
import lombok.Builder;

@Builder
public class APIConfiguredBuilders implements Builders {

APIManager apiManager;
FusionConfiguration configuration;

@Override
public Dataset.DatasetBuilder dataset() {
return Dataset.builder()
.apiManager(apiManager)
.rootUrl(configuration.getRootURL())
.catalogIdentifier(configuration.getDefaultCatalog());
}
}
Loading

0 comments on commit 682387f

Please sign in to comment.