Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create dataset Functionality #73

Merged
merged 15 commits into from
Nov 21, 2024
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
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
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
Loading