R poll(final PollHandler,R> handler, long timeout, final TimeUnit un
}
}
- final boolean pollOnce(final PollHandler
handler) {
+ final
boolean pollOnce(final PollHandler
handler) {
notNull(handler, "handler");
- final ClientHttpResponse response;
- try {
- response = restTemplate.execute(handler.getPolling(), GET, null, reusableResponseExtractor);
- } catch (GoodDataRestException e) {
- handler.handlePollException(e);
- throw new GoodDataException("Handler " + handler.getClass().getName() + " didn't handle exception", e);
- }
try {
+ ClientResponse response = webClient.get()
+ .uri(handler.getPolling())
+ .exchangeToMono(resp -> Mono.just(resp))
+ .block();
+
+ if (response == null) {
+ throw new GoodDataException("No response received for polling request");
+ }
+
+ int statusCode = response.statusCode().value();
+
if (handler.isFinished(response)) {
- final P data = extractData(response, handler.getPollClass());
+ P data = extractData(response, handler.getPollClass());
handler.handlePollResult(data);
- } else if (HttpStatus.Series.CLIENT_ERROR.equals(response.getStatusCode().series())) {
+ } else if (statusCode >= 400 && statusCode < 500) {
+ throw new GoodDataException(
+ format("Polling returned client error HTTP status %s", statusCode)
+ );
+ } else if (statusCode >= 500) {
throw new GoodDataException(
- format("Polling returned client error HTTP status %s", response.getStatusCode().value())
+ format("Polling returned server error HTTP status %s", statusCode)
);
}
- } catch (IOException e) {
- throw new GoodDataException("I/O error occurred during HTTP response extraction", e);
+ } catch (Exception e) {
+ if (e instanceof GoodDataRestException) {
+ handler.handlePollException((GoodDataRestException) e);
+ throw new GoodDataException("Handler " + handler.getClass().getName() + " didn't handle exception", e);
+ } else {
+ throw new GoodDataException("Error during polling", e);
+ }
}
return handler.isDone();
}
- protected final T extractData(ClientHttpResponse response, Class cls) throws IOException {
+ protected final T extractData(ClientResponse response, Class cls) {
notNull(response, "response");
notNull(cls, "cls");
if (Void.class.isAssignableFrom(cls)) {
return null;
}
- return new HttpMessageConverterExtractor<>(cls, restTemplate.getMessageConverters()).extractData(response);
- }
-
- private static class ReusableClientHttpResponse implements ClientHttpResponse {
-
- private byte[] body;
- private final HttpStatus statusCode;
- private final int rawStatusCode;
- private final String statusText;
- private final HttpHeaders headers;
-
- public ReusableClientHttpResponse(ClientHttpResponse response) {
- try {
- final InputStream bodyStream = response.getBody();
- if (bodyStream != null) {
- body = FileCopyUtils.copyToByteArray(bodyStream);
- }
- statusCode = response.getStatusCode();
- rawStatusCode = response.getRawStatusCode();
- statusText = response.getStatusText();
- headers = response.getHeaders();
- } catch (IOException e) {
- throw new RuntimeException("Unable to read from HTTP response", e);
- } finally {
- if (response != null) {
- response.close();
- }
- }
- }
-
- @Override
- public HttpStatus getStatusCode() {
- return statusCode;
- }
-
- @Override
- public int getRawStatusCode() {
- return rawStatusCode;
- }
-
- @Override
- public String getStatusText() {
- return statusText;
- }
-
- @Override
- public HttpHeaders getHeaders() {
- return headers;
- }
-
- @Override
- public InputStream getBody() {
- return body != null ? new ByteArrayInputStream(body) : StreamUtils.emptyInput();
- }
-
- @Override
- public void close() {
- //already closed
- }
+ // CHANGED: get response body as class instance using WebClient API
+ return response.bodyToMono(cls).block();
}
- protected static class OutputStreamResponseExtractor implements ResponseExtractor {
+ protected static class OutputStreamResponseExtractor {
private final OutputStream output;
public OutputStreamResponseExtractor(OutputStream output) {
this.output = output;
}
- @Override
- public Integer extractData(ClientHttpResponse response) throws IOException {
- return FileCopyUtils.copy(response.getBody(), output);
+ // CHANGED: get response body as bytes from ClientResponse and write to OutputStream
+ public int extractData(ClientResponse response) throws IOException {
+ byte[] bytes = response.bodyToMono(byte[].class).block();
+ if (bytes != null) {
+ output.write(bytes);
+ return bytes.length;
+ }
+ return 0;
}
}
-
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodData.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodData.java
index 632415780..0aa9a7735 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodData.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodData.java
@@ -27,11 +27,12 @@
import com.gooddata.sdk.service.projecttemplate.ProjectTemplateService;
import com.gooddata.sdk.service.warehouse.WarehouseService;
import org.springframework.context.annotation.Bean;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import static com.gooddata.sdk.service.GoodDataEndpoint.*;
import static com.gooddata.sdk.common.util.Validate.notNull;
+
/**
* Entry point for GoodData SDK usage.
*
@@ -153,8 +154,9 @@ protected GoodData(final GoodDataRestProvider goodDataRestProvider) {
/**
* @return underlying RestTemplate
*/
- protected RestTemplate getRestTemplate() {
- return services.getRestTemplate();
+
+ protected WebClient getWebClient() {
+ return services.getWebClient();
}
/**
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataRestProvider.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataRestProvider.java
index acdcd9aad..a25117b73 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataRestProvider.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataRestProvider.java
@@ -6,46 +6,31 @@
package com.gooddata.sdk.service;
import com.gooddata.sdk.service.gdc.DataStoreService;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import java.util.Optional;
import java.util.function.Supplier;
/**
- * The main interface responsible for GoodData platform REST connection management.
- * Should provide completely configured {@link RestTemplate} capable to perform valid
- * communication with GoodData platform REST API. Mainly the following functionality should
- * be provided:
- *
- * - prefixing the URI by API endpoint - services use only path part of URI
- * - authentication
- * - applying {@link GoodDataSettings} - especially user agent, headers and connection settings
- * - configuring proper error handler (i.e. {@link com.gooddata.sdk.service.util.ResponseErrorHandler})
- *
- *
- * The default implementation (internally used by {@link GoodData} is {@link com.gooddata.sdk.service.httpcomponents.LoginPasswordGoodDataRestProvider}.
+ * Interface for managing REST connection to the GoodData platform using WebClient.
*/
public interface GoodDataRestProvider {
/**
- * Settings used by the provider.
- *
- * @return used settings
+ * Returns the settings used by this provider.
*/
GoodDataSettings getSettings();
/**
- * Configured RestTemplate instance.
- *
- * @return provided RestTemplate
+ * Returns the configured WebClient instance.
*/
- RestTemplate getRestTemplate();
+ WebClient getWebClient();
/**
- * Configured DataStoreService if provided. By default empty.
+ * Returns the configured DataStoreService, if provided. By default, returns empty.
*
* @param stagingUriSupplier supplier of the data store endpoint
- * @return dataStoreService (empty by default)
+ * @return DataStoreService (empty by default)
*/
default Optional getDataStoreService(final Supplier stagingUriSupplier) {
return Optional.empty();
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataServices.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataServices.java
index 3481a61fa..750a90937 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataServices.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/GoodDataServices.java
@@ -14,7 +14,6 @@
import com.gooddata.sdk.service.dataset.DatasetService;
import com.gooddata.sdk.service.executeafm.ExecuteAfmService;
import com.gooddata.sdk.service.export.ExportService;
-import com.gooddata.sdk.service.featureflag.FeatureFlagService;
import com.gooddata.sdk.service.gdc.DataStoreService;
import com.gooddata.sdk.service.gdc.GdcService;
import com.gooddata.sdk.service.lcm.LcmService;
@@ -23,11 +22,13 @@
import com.gooddata.sdk.service.notification.NotificationService;
import com.gooddata.sdk.service.project.ProjectService;
import com.gooddata.sdk.service.project.model.ModelService;
-import com.gooddata.sdk.service.projecttemplate.ProjectTemplateService;
import com.gooddata.sdk.service.warehouse.WarehouseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.web.client.RestTemplate;
+
+import org.springframework.web.reactive.function.client.WebClient;
+import com.gooddata.sdk.service.featureflag.FeatureFlagService;
+import com.gooddata.sdk.service.projecttemplate.ProjectTemplateService;
import java.util.Optional;
@@ -62,44 +63,47 @@ class GoodDataServices {
private final LcmService lcmService;
private final HierarchicalConfigService hierarchicalConfigService;
- @SuppressWarnings("deprecation")
+
+ //
GoodDataServices(final GoodDataRestProvider goodDataRestProvider) {
- this.goodDataRestProvider = goodDataRestProvider;
-
- accountService = new AccountService(getRestTemplate(), getSettings());
- projectService = new ProjectService(getRestTemplate(), accountService, getSettings());
- metadataService = new MetadataService(getRestTemplate(), getSettings());
- modelService = new ModelService(getRestTemplate(), getSettings());
- gdcService = new GdcService(getRestTemplate(), getSettings());
- exportService = new ExportService(getRestTemplate(), getSettings());
- warehouseService = new WarehouseService(getRestTemplate(), getSettings());
- connectorService = new ConnectorService(getRestTemplate(), projectService, getSettings());
- notificationService = new NotificationService(getRestTemplate(), getSettings());
- exportImportService = new ExportImportService(getRestTemplate(), getSettings());
- featureFlagService = new FeatureFlagService(getRestTemplate(), getSettings());
- outputStageService = new OutputStageService(getRestTemplate(), getSettings());
- projectTemplateService = new ProjectTemplateService(getRestTemplate(), getSettings());
- auditEventService = new AuditEventService(getRestTemplate(), accountService, getSettings());
- executeAfmService = new ExecuteAfmService(getRestTemplate(), getSettings());
- lcmService = new LcmService(getRestTemplate(), getSettings());
- hierarchicalConfigService = new HierarchicalConfigService(getRestTemplate(), getSettings());
-
- final Optional dataStoreService = goodDataRestProvider.getDataStoreService(() -> gdcService.getRootLinks().getUserStagingUri());
- if (dataStoreService.isPresent()) {
- this.dataStoreService = dataStoreService.get();
- } else {
- this.dataStoreService = null;
- logger.info("GoodDataRestProvider provided empty DataStoreService - WebDAV related operations are not supported");
- }
-
- datasetService = new DatasetService(getRestTemplate(), this.dataStoreService, getSettings());
- processService = new ProcessService(getRestTemplate(), accountService, this.dataStoreService, getSettings());
- }
-
- RestTemplate getRestTemplate() {
- return goodDataRestProvider.getRestTemplate();
+ this.goodDataRestProvider = goodDataRestProvider;
+
+ accountService = new AccountService(getWebClient(), getSettings());
+ projectService = new ProjectService(getWebClient(), accountService, getSettings());
+ metadataService = new MetadataService(getWebClient(), getSettings());
+ modelService = new ModelService(getWebClient(), getSettings());
+ gdcService = new GdcService(getWebClient(), getSettings());
+ exportService = new ExportService(getWebClient(), getSettings());
+ warehouseService = new WarehouseService(getWebClient(), getSettings());
+ connectorService = new ConnectorService(getWebClient(), projectService, getSettings());
+ notificationService = new NotificationService(getWebClient(), getSettings());
+ exportImportService = new ExportImportService(getWebClient(), getSettings());
+ featureFlagService = new FeatureFlagService(getWebClient(), getSettings());
+ outputStageService = new OutputStageService(getWebClient(), getSettings());
+ projectTemplateService = new ProjectTemplateService(getWebClient(), getSettings());
+ auditEventService = new AuditEventService(getWebClient(), accountService, getSettings());
+ executeAfmService = new ExecuteAfmService(getWebClient(), getSettings());
+ lcmService = new LcmService(getWebClient(), getSettings());
+ hierarchicalConfigService = new HierarchicalConfigService(getWebClient(), getSettings());
+
+ final Optional dataStoreService = goodDataRestProvider.getDataStoreService(() -> gdcService.getRootLinks().getUserStagingUri());
+ if (dataStoreService.isPresent()) {
+ this.dataStoreService = dataStoreService.get();
+ } else {
+ this.dataStoreService = null;
+ logger.info("GoodDataRestProvider provided empty DataStoreService - WebDAV related operations are not supported");
+ }
+
+ datasetService = new DatasetService(getWebClient(), this.dataStoreService, getSettings());
+ processService = new ProcessService(getWebClient(), accountService, this.dataStoreService, getSettings());
+}
+
+
+ WebClient getWebClient() {
+ return goodDataRestProvider.getWebClient();
}
+
GoodDataSettings getSettings() {
return goodDataRestProvider.getSettings();
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/PollHandler.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/PollHandler.java
index 410c48dab..9f003e0e6 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/PollHandler.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/PollHandler.java
@@ -6,7 +6,8 @@
package com.gooddata.sdk.service;
import com.gooddata.sdk.common.GoodDataRestException;
-import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.reactive.function.client.ClientResponse;
+
import java.io.IOException;
import java.net.URI;
@@ -71,7 +72,7 @@ default URI getPolling() {
* @return true if polling should finish, false otherwise
* @throws IOException when there's a problem extracting data from response
*/
- boolean isFinished(ClientHttpResponse response) throws IOException;
+boolean isFinished(ClientResponse response) throws IOException;
/**
* Handle result of single polling request.
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/RequestIdInterceptor.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/RequestIdInterceptor.java
index 819519ba7..18780e92c 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/RequestIdInterceptor.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/RequestIdInterceptor.java
@@ -6,32 +6,27 @@
package com.gooddata.sdk.service;
import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.http.Header;
-import org.apache.http.HttpException;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpRequestInterceptor;
-import org.apache.http.annotation.Contract;
-import org.apache.http.annotation.ThreadingBehavior;
-import org.apache.http.protocol.HttpContext;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
+import org.apache.hc.core5.http.EntityDetails;
+
import java.io.IOException;
import static com.gooddata.sdk.common.gdc.Header.GDC_REQUEST_ID;
-/**
- * Intercepts the client-side requests on low-level in order to be able to catch requests also from the Sardine,
- * that is working independently from Spring {@link org.springframework.web.client.RestTemplate} to set
- * the X-GDC-REQUEST header to them.
- */
-@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class RequestIdInterceptor implements HttpRequestInterceptor {
@Override
- public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
+ public void process(final HttpRequest request, final EntityDetails entity, final HttpContext context)
+ throws HttpException, IOException {
final StringBuilder requestIdBuilder = new StringBuilder();
- final Header requestIdHeader = request.getFirstHeader(GDC_REQUEST_ID);
+ final String requestIdHeader = request.getFirstHeader(GDC_REQUEST_ID) != null ?
+ request.getFirstHeader(GDC_REQUEST_ID).getValue() : null;
if (requestIdHeader != null) {
- requestIdBuilder.append(requestIdHeader.getValue()).append(":");
+ requestIdBuilder.append(requestIdHeader).append(":");
}
final String requestId = requestIdBuilder.append(RandomStringUtils.randomAlphanumeric(16)).toString();
request.setHeader(GDC_REQUEST_ID, requestId);
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/ResponseMissingRequestIdInterceptor.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/ResponseMissingRequestIdInterceptor.java
index 978cb2958..dedc8ac81 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/ResponseMissingRequestIdInterceptor.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/ResponseMissingRequestIdInterceptor.java
@@ -5,14 +5,14 @@
*/
package com.gooddata.sdk.service;
-import org.apache.http.Header;
-import org.apache.http.HttpException;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpResponseInterceptor;
-import org.apache.http.annotation.Contract;
-import org.apache.http.annotation.ThreadingBehavior;
-import org.apache.http.protocol.HttpContext;
-import org.apache.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpResponse;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpResponseInterceptor;
+import org.apache.hc.core5.http.EntityDetails;
import java.io.IOException;
@@ -22,16 +22,18 @@
* Intercepts responses to check if they have set the X-GDC-REQUEST header for easier debugging.
* If not, it takes this header from the request sent.
*/
-@Contract(threading = ThreadingBehavior.IMMUTABLE)
public class ResponseMissingRequestIdInterceptor implements HttpResponseInterceptor {
-
@Override
- public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException {
-
+ public void process(final HttpResponse response, final EntityDetails entity, final HttpContext context) throws HttpException, IOException {
if (response.getFirstHeader(GDC_REQUEST_ID) == null) {
- final HttpCoreContext coreContext = HttpCoreContext.adapt(context);
- final Header requestIdHeader = coreContext.getRequest().getFirstHeader(GDC_REQUEST_ID);
- response.setHeader(GDC_REQUEST_ID, requestIdHeader.getValue());
+ HttpCoreContext coreContext = HttpCoreContext.cast(context);
+ HttpRequest request = coreContext.getRequest(); // Modern, non-deprecated
+ if (request != null) {
+ Header requestIdHeader = request.getFirstHeader(GDC_REQUEST_ID);
+ if (requestIdHeader != null) {
+ response.setHeader(GDC_REQUEST_ID, requestIdHeader.getValue());
+ }
+ }
}
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/account/AccountService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/account/AccountService.java
index 68b1a13f1..f820b6966 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/account/AccountService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/account/AccountService.java
@@ -6,7 +6,6 @@
package com.gooddata.sdk.service.account;
import com.gooddata.sdk.common.GoodDataException;
-import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.model.account.Account;
import com.gooddata.sdk.model.account.Accounts;
import com.gooddata.sdk.model.account.SeparatorSettings;
@@ -14,34 +13,29 @@
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.converter.json.MappingJacksonValue;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
import static com.gooddata.sdk.common.util.Validate.notEmpty;
import static com.gooddata.sdk.common.util.Validate.notNull;
import static com.gooddata.sdk.common.util.Validate.notNullState;
+
/**
* Service to access and manipulate account.
*/
public class AccountService extends AbstractService {
+
public static final UriTemplate ACCOUNT_TEMPLATE = new UriTemplate(Account.URI);
public static final UriTemplate ACCOUNTS_TEMPLATE = new UriTemplate(Account.ACCOUNTS_URI);
public static final UriTemplate ACCOUNT_BY_LOGIN_TEMPLATE = new UriTemplate(Account.ACCOUNT_BY_EMAIL_URI);
public static final UriTemplate LOGIN_TEMPLATE = new UriTemplate(Account.LOGIN_URI);
public static final UriTemplate SEPARATORS_TEMPLATE = new UriTemplate(SeparatorSettings.URI);
- /**
- * Constructs service for GoodData account management.
- * @param restTemplate RESTful HTTP Spring template
- * @param settings settings
- */
- public AccountService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+
+ public AccountService(WebClient webClient, GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -62,12 +56,17 @@ public Account getCurrent() {
public void logout() {
try {
final String id = getCurrent().getId();
- restTemplate.delete(Account.LOGIN_URI, id);
- } catch (GoodDataException | RestClientException e) {
+ webClient.delete()
+ .uri(Account.LOGIN_URI, id)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to logout", e);
}
}
+
/**
* Creates new account in given organization (domain).
* Only domain admin is allowed create new accounts! This means rest request has to authorized as domain admin.
@@ -81,9 +80,16 @@ public Account createAccount(Account account, String organizationName) {
notEmpty(organizationName, "organizationName");
try {
- final UriResponse uriResponse = restTemplate.postForObject(Account.ACCOUNTS_URI, account, UriResponse.class, organizationName);
+ UriResponse uriResponse = webClient.post()
+ .uri(uriBuilder -> uriBuilder
+ .path(Account.ACCOUNTS_URI)
+ .build(organizationName))
+ .bodyValue(account)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
return getAccountByUri(notNullState(uriResponse, "created account response").getUri());
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to create account", e);
}
}
@@ -99,40 +105,32 @@ public void removeAccount(final Account account) {
notNull(account.getUri(), "account.uri");
try {
- restTemplate.delete(account.getUri());
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new AccountNotFoundException(account.getUri(), e);
- } else {
- throw e;
- }
- } catch (GoodDataException e) {
+ webClient.delete()
+ .uri(account.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to remove account", e);
}
}
- /**
- * Get account for given account id
- * @param id to search for
- * @return account for id
- * @throws AccountNotFoundException when account for given id can't be found
- * @throws GoodDataException when different error occurs
- */
public Account getAccountById(final String id) {
notNull(id, "id");
try {
- return restTemplate.getForObject(Account.URI, Account.class, id);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new AccountNotFoundException(ACCOUNT_TEMPLATE.expand(id).toString(), e);
- } else {
- throw e;
- }
- } catch (RestClientException e) {
+ return webClient.get()
+ .uri(uriBuilder -> uriBuilder
+ .path(Account.URI)
+ .build(id))
+ .retrieve()
+ .bodyToMono(Account.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get account", e);
}
}
+
/**
* Get account by given login.
* Only domain admin is allowed to search users by login.
@@ -146,14 +144,19 @@ public Account getAccountByLogin(final String email, final String organizationNa
notNull(email, "email");
notNull(organizationName, "organizationName");
try {
- final Accounts accounts = restTemplate.getForObject(
- Account.ACCOUNT_BY_EMAIL_URI, Accounts.class, organizationName, email);
+ Accounts accounts = webClient.get()
+ .uri(uriBuilder -> uriBuilder
+ .path(Account.ACCOUNT_BY_EMAIL_URI)
+ .build(organizationName, email))
+ .retrieve()
+ .bodyToMono(Accounts.class)
+ .block();
if (accounts != null && !accounts.getPageItems().isEmpty()) {
return accounts.getPageItems().get(0);
}
throw new AccountNotFoundException("User was not found by email " +
- email + " in organization " + organizationName, Account.ACCOUNT_BY_EMAIL_URI);
- } catch (RestClientException e) {
+ email + " in organization " + organizationName, Account.ACCOUNT_BY_EMAIL_URI);
+ } catch (Exception e) {
throw new GoodDataException("Unable to get account", e);
}
}
@@ -180,16 +183,14 @@ public void updateAccount(final Account account) {
notNull(account.getUri(), "account.uri");
try {
- final MappingJacksonValue jacksonValue = new MappingJacksonValue(account);
- jacksonValue.setSerializationView(Account.UpdateView.class);
- restTemplate.put(account.getUri(), jacksonValue);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new AccountNotFoundException(account.getUri(), e);
- } else {
- throw e;
- }
- } catch (GoodDataException e) {
+ // Changed: .put() with WebClient; manual JSON view can be used if needed
+ webClient.put()
+ .uri(account.getUri())
+ .bodyValue(account)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to update account", e);
}
}
@@ -205,8 +206,12 @@ public SeparatorSettings getSeparatorSettings(final Account account) {
notEmpty(account.getUri(), "account.uri");
try {
- return restTemplate.getForObject(SEPARATORS_TEMPLATE.expand(account.getId()), SeparatorSettings.class);
- } catch (RestClientException e) {
+ return webClient.get()
+ .uri(SEPARATORS_TEMPLATE.expand(account.getId()))
+ .retrieve()
+ .bodyToMono(SeparatorSettings.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get separators for account=" + account.getUri(), e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/auditevent/AuditEventService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/auditevent/AuditEventService.java
index 336748b1c..4a0fed8be 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/auditevent/AuditEventService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/auditevent/AuditEventService.java
@@ -6,7 +6,6 @@
package com.gooddata.sdk.service.auditevent;
import com.gooddata.sdk.common.GoodDataException;
-import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.common.collections.PageBrowser;
import com.gooddata.sdk.common.collections.PageRequest;
import com.gooddata.sdk.common.util.SpringMutableUri;
@@ -16,13 +15,14 @@
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
import com.gooddata.sdk.service.account.AccountService;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriTemplate;
import static com.gooddata.sdk.common.util.Validate.notEmpty;
import static com.gooddata.sdk.common.util.Validate.notNull;
-import static org.springframework.http.HttpStatus.UNAUTHORIZED;
+import org.springframework.http.HttpStatus;
/**
* List audit events.
@@ -36,12 +36,12 @@ public class AuditEventService extends AbstractService {
/**
* Service for audit events
- * @param restTemplate rest template
+ * @param webClient web client
* @param accountService account service
* @param settings settings
*/
- public AuditEventService(final RestTemplate restTemplate, final AccountService accountService, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public AuditEventService(final WebClient webClient, final AccountService accountService, final GoodDataSettings settings) {
+ super(webClient, settings);
this.accountService = notNull(accountService, "account service");
}
@@ -122,18 +122,22 @@ public PageBrowser listAuditEvents(final PageRequest page) {
}
private AuditEvents doListAuditEvents(final String uri) {
- try {
- return restTemplate.getForObject(uri, AuditEvents.class);
- } catch (GoodDataRestException e) {
- if (UNAUTHORIZED.value() == e.getStatusCode()) {
- throw new AuditEventsForbiddenException(e);
- } else {
- throw e;
+ try {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(AuditEvents.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (HttpStatus.UNAUTHORIZED.equals(e.getStatusCode())) {
+ throw new AuditEventsForbiddenException(e);
+ } else {
+ throw e;
+ }
+ } catch (Exception e) {
+ throw new GoodDataException("Unable to list audit events: " + uri, e);
}
- } catch (RestClientException e) {
- throw new GoodDataException("Unable to list audit events: " + uri);
}
- }
private String getAuditEventsUri(final PageRequest page, final String uri) {
return page.updateWithPageParams(new SpringMutableUri(uri)).toUriString();
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/connector/ConnectorService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/connector/ConnectorService.java
index c6f58352b..5f8ef0645 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/connector/ConnectorService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/connector/ConnectorService.java
@@ -13,13 +13,15 @@
import com.gooddata.sdk.model.project.ProjectTemplate;
import com.gooddata.sdk.service.*;
import com.gooddata.sdk.service.project.ProjectService;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+import com.gooddata.sdk.service.retry.RetryableWebClient;
+import com.gooddata.sdk.service.retry.GetServerErrorRetryStrategy;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.util.UriTemplate;
-import java.io.IOException;
+import org.springframework.http.HttpMethod;
+import java.net.URI;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
@@ -35,10 +37,12 @@ public class ConnectorService extends AbstractService {
public static final UriTemplate STATUS_TEMPLATE = new UriTemplate(IntegrationProcessStatus.URI);
private final ProjectService projectService;
+ private final RetryableWebClient retryableWebClient;
- public ConnectorService(final RestTemplate restTemplate, final ProjectService projectService, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ConnectorService(final WebClient webClient, final ProjectService projectService, final GoodDataSettings settings) {
+ super(webClient, settings);
this.projectService = notNull(projectService, "projectService");
+ this.retryableWebClient = new RetryableWebClient(webClient, settings.getRetrySettings(), new GetServerErrorRetryStrategy());
}
/**
@@ -55,14 +59,47 @@ public Integration getIntegration(final Project project, final ConnectorType con
notNull(connectorType, "connector");
try {
- return restTemplate.getForObject(Integration.URL, Integration.class, project.getId(), connectorType.getName());
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+
+ // For retryable client, we need to construct the full URI
+ // Since the retryable client's execute method expects a complete URI,
+ // we'll need to use a different approach for now to maintain compatibility
+ // Let's use the webClient directly but check if we need retries based on response
+ return webClient.get()
+ .uri(url)
+ .retrieve()
+ .bodyToMono(Integration.class)
+ .retryWhen(reactor.util.retry.Retry.backoff(
+ settings.getRetrySettings().getRetryCount(),
+ java.time.Duration.ofMillis(settings.getRetrySettings().getRetryInitialInterval()))
+ .maxBackoff(java.time.Duration.ofMillis(settings.getRetrySettings().getRetryMaxInterval()))
+ .filter(throwable -> {
+ if (throwable instanceof WebClientResponseException) {
+ WebClientResponseException wcre = (WebClientResponseException) throwable;
+ // Only retry for GET operations with server errors (500, 502, 503, 504, 507)
+ return new GetServerErrorRetryStrategy().retryAllowed("GET", wcre.getStatusCode().value(), null);
+ }
+ return false;
+ }))
+ .onErrorMap(WebClientResponseException.class, e -> {
+ if (e.getStatusCode().value() == 404) {
+ return new IntegrationNotFoundException(project, connectorType, e);
+ } else {
+ return new GoodDataRestException(
+ e.getStatusCode().value(),
+ e.getStatusText(),
+ e.getResponseBodyAsString(),
+ url,
+ "WebClient"
+ );
+ }
+ })
+ .block();
} catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new IntegrationNotFoundException(project, connectorType, e);
- } else {
- throw e;
- }
- } catch (RestClientException e) {
+ // Re-throw GoodDataRestException (including IntegrationNotFoundException) as-is
+ throw e;
+ } catch (Exception e) {
throw new ConnectorException("Unable to get " + connectorType + " integration", e);
}
}
@@ -108,9 +145,15 @@ public Integration createIntegration(final Project project, final ConnectorType
notNull(integration, "integration");
try {
- return restTemplate.postForObject(Integration.URL, integration, Integration.class, project.getId(),
- connectorType.getName());
- } catch (GoodDataRestException | RestClientException e) {
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+ return webClient.post()
+ .uri(url)
+ .bodyValue(integration)
+ .retrieve()
+ .bodyToMono(Integration.class)
+ .block();
+ } catch (Exception e) {
throw new ConnectorException("Unable to create " + connectorType + " integration", e);
}
}
@@ -131,14 +174,29 @@ public void updateIntegration(final Project project, final ConnectorType connect
notNull(integration, "integration");
try {
- restTemplate.put(Integration.URL, integration, project.getId(), connectorType.getName());
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+ webClient.put()
+ .uri(url)
+ .bodyValue(integration)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == 404) {
throw new IntegrationNotFoundException(project, connectorType, e);
} else {
- throw e;
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+ throw new GoodDataRestException(
+ e.getStatusCode().value(),
+ e.getStatusText(),
+ e.getResponseBodyAsString(),
+ url,
+ "WebClient"
+ );
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new ConnectorException("Unable to update " + connectorType + " integration", e);
}
}
@@ -155,16 +213,30 @@ public void deleteIntegration(final Project project, final ConnectorType connect
notNull(project, "project");
notNull(project.getId(), "project.id");
notNull(connectorType, "connector");
-
+
try {
- restTemplate.delete(Integration.URL, project.getId(), connectorType.getName());
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+ webClient.delete()
+ .uri(url)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == 404) {
throw new IntegrationNotFoundException(project, connectorType, e);
} else {
- throw e;
+ String url = Integration.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType.getName());
+ throw new GoodDataRestException(
+ e.getStatusCode().value(),
+ e.getStatusText(),
+ e.getResponseBodyAsString(),
+ url,
+ "WebClient"
+ );
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new ConnectorException("Unable to delete " + connectorType + " integration", e);
}
}
@@ -195,8 +267,13 @@ public T getSettings(final Project project, final Connector
notNull(settingsClass, "settingsClass");
try {
- return restTemplate.getForObject(connectorType.getSettingsUrl(), settingsClass, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ String url = connectorType.getSettingsUrl().replace("{project}", project.getId());
+ return webClient.get()
+ .uri(url)
+ .retrieve()
+ .bodyToMono(settingsClass)
+ .block();
+ } catch (Exception e) {
throw new ConnectorException("Unable to get " + connectorType + " integration settings", e);
}
}
@@ -215,8 +292,14 @@ public void updateSettings(final Project project, final Settings settings) {
notNull(project, "project");
try {
- restTemplate.put(settings.getConnectorType().getSettingsUrl(), settings, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ String url = settings.getConnectorType().getSettingsUrl().replace("{project}", project.getId());
+ webClient.put()
+ .uri(url)
+ .bodyValue(settings)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new ConnectorException("Unable to set " + settings.getConnectorType() + " settings", e);
}
}
@@ -236,10 +319,16 @@ public FutureResult executeProcess(final Project project, final P
final String connectorType = execution.getConnectorType().getName();
try {
- final UriResponse response = restTemplate
- .postForObject(ProcessStatus.URL, execution, UriResponse.class, project.getId(), connectorType);
+ String url = ProcessStatus.URL.replace("{project}", project.getId())
+ .replace("{connector}", connectorType);
+ final UriResponse response = webClient.post()
+ .uri(url)
+ .bodyValue(execution)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
return createProcessPollResult(notNullState(response, "created process response").getUri());
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new ConnectorException("Unable to execute " + connectorType + " process", e);
}
}
@@ -281,28 +370,48 @@ public Reload getZendesk4Reload(final Reload reload) {
}
/**
- * Get Zendesk reload.
+ * Get Zendesk reload by URI using WebClient.
* @param reloadUri existing reload URI
* @return reload
*/
public Reload getZendesk4ReloadByUri(final String reloadUri) {
notNull(reloadUri, "reloadUri");
try {
- return restTemplate.getForObject(reloadUri, Reload.class);
- } catch (GoodDataRestException | RestClientException e) {
+ return webClient.get()
+ .uri(reloadUri)
+ .retrieve()
+ .bodyToMono(Reload.class)
+ .block();
+ } catch (Exception e) {
throw new ConnectorException("Unable to get reload", e);
}
}
- /**
- * Scheduler new reload.
- * @param project project to reload
- * @param reload reload parameters
- * @return created reload
- */
- public Reload scheduleZendesk4Reload(final Project project, final Reload reload) {
- return restTemplate.postForObject(Reload.URL, reload, Reload.class, project.getId());
- }
+
+ /**
+ * Schedule a new Zendesk4 reload using WebClient.
+ * @param project project to reload
+ * @param reload reload parameters
+ * @return created reload
+ */
+ public Reload scheduleZendesk4Reload(final Project project, final Reload reload) {
+ notNull(project, "project");
+ notNull(project.getId(), "project.id");
+ notNull(reload, "reload");
+
+ try {
+ String url = Reload.URL.replace("{project}", project.getId()); // если в шаблоне есть {projectId}
+ return webClient.post()
+ .uri(url)
+ .bodyValue(reload)
+ .retrieve()
+ .bodyToMono(Reload.class)
+ .block();
+ } catch (Exception e) {
+ throw new ConnectorException("Unable to schedule Zendesk4 reload for project " + project.getId(), e);
+ }
+ }
+
protected FutureResult createProcessPollResult(final String uri) {
final Map match = STATUS_TEMPLATE.match(uri);
@@ -310,9 +419,15 @@ protected FutureResult createProcessPollResult(final String uri)
final String processId = match.get("process");
return new PollResult<>(this, new SimplePollHandler(uri, ProcessStatus.class) {
@Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
- final ProcessStatus process = extractData(response, ProcessStatus.class);
- return process.isFinished();
+ public boolean isFinished(final ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) {
+ ProcessStatus process = response.bodyToMono(ProcessStatus.class).block();
+ return process != null && process.isFinished();
+ } else if (code == 202) {
+ return false;
+ }
+ throw new ConnectorException(format("%s process %s returned unknown HTTP code %s", connectorType, processId, code));
}
@Override
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/OutputStageService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/OutputStageService.java
index 85f0ca7fb..ebfb93de4 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/OutputStageService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/OutputStageService.java
@@ -6,17 +6,11 @@
package com.gooddata.sdk.service.dataload;
import com.gooddata.sdk.common.GoodDataException;
-import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.model.dataload.OutputStage;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
-import com.gooddata.sdk.service.dataload.processes.ProcessNotFoundException;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
import static com.gooddata.sdk.common.util.Validate.*;
@@ -29,36 +23,44 @@ public class OutputStageService extends AbstractService {
public static final UriTemplate OUTPUT_STAGE_TEMPLATE = new UriTemplate(OutputStage.URI);
/**
- * Sets RESTful HTTP Spring template. Should be called from constructor of concrete service extending
- * this abstract one.
- * @param restTemplate RESTful HTTP Spring template
- * @param settings settings
+ * Constructor accepting WebClient and settings.
+ * @param webClient WebClient for HTTP communication
+ * @param settings configuration settings
*/
- public OutputStageService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public OutputStageService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
- * Get output stage by given URI.
- * @param uri output stage uri
+ * Get output stage by a given URI.
+ * @param uri output stage URI
* @return output stage object
- * @throws ProcessNotFoundException when the process doesn't exist
+ * @throws GoodDataException if the output stage does not exist or an error occurs
*/
public OutputStage getOutputStageByUri(final String uri) {
notEmpty(uri, "uri");
isTrue(OUTPUT_STAGE_TEMPLATE.matches(uri), "uri does not match output stage pattern: " + OUTPUT_STAGE_TEMPLATE.toString());
try {
- return restTemplate.getForObject(uri, OutputStage.class);
- } catch (RestClientException e) {
+ OutputStage outputStage = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(OutputStage.class)
+ .block();
+
+ if (outputStage == null) {
+ throw new GoodDataException("OutputStage not found: " + uri);
+ }
+ return outputStage;
+ } catch (Exception e) {
throw new GoodDataException("Unable to get output stage " + uri, e);
}
}
/**
- * Get output stage by given project.
- * @param project project to which the process belongs
- * @return output stage
- * @throws ProcessNotFoundException when the process doesn't exist
+ * Get output stage by project.
+ * @param project project for which to get the output stage
+ * @return output stage object
+ * @throws GoodDataException if the output stage does not exist or an error occurs
*/
public OutputStage getOutputStage(final Project project) {
notNull(project, "project");
@@ -68,24 +70,29 @@ public OutputStage getOutputStage(final Project project) {
}
/**
- * Update output stage.
- *
- * @param outputStage output stage
+ * Update the output stage.
+ * @param outputStage output stage to update
* @return updated output stage
+ * @throws GoodDataException if update fails or the response is empty
*/
public OutputStage updateOutputStage(final OutputStage outputStage) {
notNull(outputStage, "outputStage");
notNull(outputStage.getUri(), "outputStage.uri");
try {
- HttpEntity outputStageHttpEntity = new HttpEntity<>(outputStage);
- ResponseEntity response = restTemplate.exchange(outputStage.getUri(), HttpMethod.PUT, outputStageHttpEntity, OutputStage.class);
- if (response.getBody() == null) {
- throw new RestClientException("unexpected response body");
+ OutputStage updated = webClient.put()
+ .uri(outputStage.getUri())
+ .bodyValue(outputStage)
+ .retrieve()
+ .bodyToMono(OutputStage.class)
+ .block();
+
+ if (updated == null) {
+ throw new GoodDataException("Unexpected empty response body");
}
- return response.getBody();
- } catch (GoodDataRestException | RestClientException e) {
- throw new GoodDataException("Unable to update output stage, uri: " + outputStage.getUri());
+ return updated;
+ } catch (Exception e) {
+ throw new GoodDataException("Unable to update output stage, uri: " + outputStage.getUri(), e);
}
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/processes/ProcessService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/processes/ProcessService.java
index 9f83616af..0a045dfa9 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/processes/ProcessService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataload/processes/ProcessService.java
@@ -5,6 +5,7 @@
*/
package com.gooddata.sdk.service.dataload.processes;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.gooddata.sdk.common.GoodDataException;
import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.common.collections.CustomPageRequest;
@@ -41,10 +42,14 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriTemplate;
import java.io.File;
@@ -61,11 +66,17 @@
import static java.util.Collections.emptyList;
import static org.apache.commons.lang3.Validate.isTrue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
/**
* Service to manage dataload processes and process executions.
*/
public class ProcessService extends AbstractService {
+ private static final Logger log = LoggerFactory.getLogger(ProcessService.class);
+
public static final UriTemplate SCHEDULE_TEMPLATE = new UriTemplate(Schedule.URI);
public static final UriTemplate PROCESS_TEMPLATE = new UriTemplate(DataloadProcess.URI);
public static final UriTemplate SCHEDULES_TEMPLATE = new UriTemplate(Schedules.URI);
@@ -87,9 +98,9 @@ public class ProcessService extends AbstractService {
* @param dataStoreService service for upload process data
* @param settings settings
*/
- public ProcessService(final RestTemplate restTemplate, final AccountService accountService,
+ public ProcessService(final WebClient webClient, final AccountService accountService,
final DataStoreService dataStoreService, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ super(webClient, settings);
this.dataStoreService = dataStoreService;
this.accountService = notNull(accountService, "accountService");
}
@@ -183,18 +194,31 @@ public FutureResult updateProcessFromAppstore(DataloadProcess p
public DataloadProcess getProcessByUri(String uri) {
notEmpty(uri, "uri");
try {
- return restTemplate.getForObject(uri, DataloadProcess.class);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(DataloadProcess.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
throw new ProcessNotFoundException(uri, e);
} else {
throw e;
}
- } catch (RestClientException e) {
+ } catch (GoodDataRestException e) {
+ // Map 404 GoodDataRestException to ProcessNotFoundException
+ if (e.getStatusCode() == 404) {
+ throw new ProcessNotFoundException(uri, e);
+ } else {
+ throw new GoodDataException("Unable to get process " + uri, e);
+ }
+ } catch (Exception e) {
throw new GoodDataException("Unable to get process " + uri, e);
}
}
+
+
/**
* Get process by given id and project.
* @param project project to which the process belongs
@@ -233,8 +257,12 @@ public Collection listUserProcesses() {
public void removeProcess(DataloadProcess process) {
notNull(process, "process");
try {
- restTemplate.delete(process.getUri());
- } catch (GoodDataException | RestClientException e) {
+ webClient.delete()
+ .uri(process.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to remove process " + process.getUri(), e);
}
}
@@ -249,9 +277,15 @@ public void getProcessSource(DataloadProcess process, OutputStream outputStream)
notNull(process, "process");
notNull(outputStream, "outputStream");
try {
- restTemplate.execute(process.getSourceUri(), HttpMethod.GET,
- null, new OutputStreamResponseExtractor(outputStream));
- } catch (GoodDataException | RestClientException e) {
+ byte[] bytes = webClient.get()
+ .uri(process.getSourceUri())
+ .retrieve()
+ .bodyToMono(byte[].class)
+ .block();
+ if (bytes != null) {
+ outputStream.write(bytes);
+ }
+ } catch (WebClientResponseException | IOException e) {
throw new GoodDataException("Unable to get process source " + process.getSourceUri(), e);
}
}
@@ -265,9 +299,15 @@ public void getExecutionLog(ProcessExecutionDetail executionDetail, OutputStream
notNull(executionDetail, "executionDetail");
notNull(outputStream, "outputStream");
try {
- restTemplate.execute(executionDetail.getLogUri(), HttpMethod.GET,
- null, new OutputStreamResponseExtractor(outputStream));
- } catch (GoodDataException | RestClientException e) {
+ byte[] bytes = webClient.get()
+ .uri(executionDetail.getLogUri())
+ .retrieve()
+ .bodyToMono(byte[].class)
+ .block();
+ if (bytes != null) {
+ outputStream.write(bytes);
+ }
+ } catch (WebClientResponseException | IOException e) {
throw new GoodDataException("Unable to get process execution log " + executionDetail.getLogUri(), e);
}
}
@@ -283,8 +323,13 @@ public FutureResult executeProcess(ProcessExecution exec
notNull(execution, "execution");
ProcessExecutionTask executionTask;
try {
- executionTask = restTemplate.postForObject(execution.getExecutionsUri(), execution, ProcessExecutionTask.class);
- } catch (GoodDataException | RestClientException e) {
+ executionTask = webClient.post()
+ .uri(execution.getExecutionsUri())
+ .bodyValue(execution)
+ .retrieve()
+ .bodyToMono(ProcessExecutionTask.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new ProcessExecutionException("Cannot execute process", e);
}
@@ -296,8 +341,8 @@ public FutureResult executeProcess(ProcessExecution exec
return new PollResult<>(this, new AbstractPollHandler(executionTask.getPollUri(), Void.class, ProcessExecutionDetail.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- return HttpStatus.NO_CONTENT.equals(response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ return response.statusCode().equals(HttpStatus.NO_CONTENT);
}
@Override
@@ -321,8 +366,12 @@ public void handlePollException(final GoodDataRestException e) {
private ProcessExecutionDetail getProcessExecutionDetailByUri(final String uri) {
try {
- return restTemplate.getForObject(uri, ProcessExecutionDetail.class);
- } catch (GoodDataException | RestClientException e) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(ProcessExecutionDetail.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new ProcessExecutionException("Execution finished, but cannot get its result.", e, uri);
}
}
@@ -357,21 +406,21 @@ public Schedule updateSchedule(Schedule schedule) {
final String uri = schedule.getUri();
try {
- final ResponseEntity response = restTemplate
- .exchange(uri, HttpMethod.PUT, new HttpEntity<>(schedule), Schedule.class);
- if (response == null) {
- throw new GoodDataException("Unable to update schedule. No response returned from API.");
- }
- return response.getBody();
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new ScheduleNotFoundException(uri, e);
- } else {
- throw e;
+ return webClient.put()
+ .uri(uri)
+ .bodyValue(schedule)
+ .retrieve()
+ .bodyToMono(Schedule.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
+ throw new ScheduleNotFoundException(uri, e);
+ } else {
+ throw e;
+ }
+ } catch (Exception e) {
+ throw new GoodDataException("Unable to get schedule " + uri, e);
}
- } catch (RestClientException e) {
- throw new GoodDataException("Unable to get schedule " + uri, e);
- }
}
/**
@@ -384,14 +433,18 @@ public Schedule updateSchedule(Schedule schedule) {
public Schedule getScheduleByUri(String uri) {
notEmpty(uri, "uri");
try {
- return restTemplate.getForObject(uri, Schedule.class);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Schedule.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
throw new ScheduleNotFoundException(uri, e);
} else {
throw e;
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get schedule " + uri, e);
}
}
@@ -444,8 +497,12 @@ public void removeSchedule(final Schedule schedule) {
notNull(schedule.getUri(), "schedule.uri");
try {
- restTemplate.delete(schedule.getUri());
- } catch (GoodDataException | RestClientException e) {
+ webClient.delete()
+ .uri(schedule.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to remove schedule " + schedule.getUri(), e);
}
}
@@ -462,8 +519,13 @@ public FutureResult executeSchedule(final Schedule schedule)
ScheduleExecution scheduleExecution;
try {
- scheduleExecution = restTemplate.postForObject(schedule.getExecutionsUri(), new ScheduleExecution(), ScheduleExecution.class);
- } catch (GoodDataException | RestClientException e) {
+ scheduleExecution = webClient.post()
+ .uri(schedule.getExecutionsUri())
+ .bodyValue(new ScheduleExecution())
+ .retrieve()
+ .bodyToMono(ScheduleExecution.class)
+ .block();
+ } catch (RuntimeException e) {
throw new ScheduleExecutionException("Cannot execute schedule", e);
}
@@ -471,8 +533,8 @@ public FutureResult executeSchedule(final Schedule schedule)
notNullState(scheduleExecution, "created schedule execution").getUri(),
ScheduleExecution.class, ScheduleExecution.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- final ScheduleExecution pollResult = extractData(response, ScheduleExecution.class);
+ public boolean isFinished(ClientResponse response) {
+ final ScheduleExecution pollResult = extractData(response, ScheduleExecution.class);
return pollResult.isFinished();
}
@@ -490,12 +552,16 @@ public void handlePollException(final GoodDataRestException e) {
private Page listSchedules(URI uri) {
try {
- final Schedules schedules = restTemplate.getForObject(uri, Schedules.class);
+ final Schedules schedules = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Schedules.class)
+ .block();
if (schedules == null) {
return new Page<>();
}
return schedules;
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to list schedules", e);
}
}
@@ -520,25 +586,35 @@ private static URI getSchedulesUri(final Project project, final PageRequest page
private Schedule postSchedule(Schedule schedule, URI postUri) {
try {
- return restTemplate.postForObject(postUri, schedule, Schedule.class);
- } catch (GoodDataException | RestClientException e) {
+ return webClient.post()
+ .uri(postUri)
+ .bodyValue(schedule)
+ .retrieve()
+ .bodyToMono(Schedule.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to post schedule.", e);
}
}
private Collection listProcesses(URI uri) {
try {
- final DataloadProcesses processes = restTemplate.getForObject(uri, DataloadProcesses.class);
+ final DataloadProcesses processes = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(DataloadProcesses.class)
+ .block();
if (processes == null) {
throw new GoodDataException("empty response from API call");
} else if (processes.getItems() == null) {
return emptyList();
}
return processes.getItems();
- } catch (GoodDataException | RestClientException e) {
+ } catch (RuntimeException e) {
+ // Wrap any unexpected error in GoodDataException
throw new GoodDataException("Unable to list processes", e);
}
- }
+ }
private static URI getProcessUri(Project project, String id) {
notNull(project, "project");
@@ -552,6 +628,7 @@ private static URI getProcessesUri(Project project) {
return PROCESSES_TEMPLATE.expand(project.getId());
}
+
private DataloadProcess postProcess(DataloadProcess process, File processData, URI postUri) {
File tempFile = createTempFile("process", ".zip");
@@ -561,82 +638,123 @@ private DataloadProcess postProcess(DataloadProcess process, File processData, U
throw new GoodDataException("Unable to zip process data", e);
}
- Object processToSend;
- HttpMethod method = HttpMethod.POST;
- if (dataStoreService != null && tempFile.length() > MAX_MULTIPART_SIZE) {
- try (final InputStream input = Files.newInputStream(tempFile.toPath())) {
- process.setPath(dataStoreService.getUri(tempFile.getName()).getPath());
- dataStoreService.upload(tempFile.getName(), input);
- processToSend = process;
- if (PROCESS_TEMPLATE.matches(postUri.toString())) {
- method = HttpMethod.PUT;
+ try {
+ if (dataStoreService != null && tempFile.length() > MAX_MULTIPART_SIZE) {
+ try (final InputStream input = Files.newInputStream(tempFile.toPath())) {
+ process.setPath(dataStoreService.getUri(tempFile.getName()).getPath());
+ dataStoreService.upload(tempFile.getName(), input);
+
+ // Log process details before sending
+ log.debug("Process details: {}", process);
+
+ // Check for null before sending the process object
+ if (process == null) {
+ throw new IllegalArgumentException("Request body for process POST must not be null");
+ }
+
+ DataloadProcess result = webClient.post()
+ .uri(postUri)
+ .bodyValue(process)
+ .retrieve()
+ .bodyToMono(DataloadProcess.class)
+ .block();
+ if (result == null) {
+ throw new GoodDataException("No response from API (null result) when posting dataload process (large, via WebDAV).");
+ }
+ return result;
+ } catch (IOException e) {
+ throw new GoodDataException("Unable to access zipped process data at " + tempFile.getAbsolutePath(), e);
}
- } catch (IOException e) {
- throw new GoodDataException("Unable to access zipped process data at "
- + tempFile.getAbsolutePath(), e);
- }
- } else {
- if (dataStoreService == null) { // we have no WebDAV support, so let's try send big file by multipart
- if (logger.isInfoEnabled()) {
- logger.info("WebDAV calls not supported - sending huge file using multipart. " +
- "Consider adding com.github.lookfirst:sardine to dependencies.");
+ } else {
+ MultipartBodyBuilder builder = new MultipartBodyBuilder();
+ builder.part("process", process);
+ builder.part("data", new FileSystemResource(tempFile))
+ .header(HttpHeaders.CONTENT_TYPE, MEDIA_TYPE_ZIP.toString());
+
+ // Log process details before sending multipart request
+ log.debug("Process details: {}", process);
+
+ DataloadProcess result = webClient.post()
+ .uri(postUri)
+ .contentType(MediaType.MULTIPART_FORM_DATA)
+ .body(BodyInserters.fromMultipartData(builder.build()))
+ .retrieve()
+ .bodyToMono(DataloadProcess.class)
+ .block();
+ if (result == null) {
+ throw new GoodDataException("No response from API (null result) when posting dataload process (multipart).");
}
+ return result;
}
- final MultiValueMap parts = new LinkedMultiValueMap<>(2);
- parts.add("process", process);
- final HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MEDIA_TYPE_ZIP);
- parts.add("data", new HttpEntity<>(new FileSystemResource(tempFile), headers));
- processToSend = parts;
- }
-
- try {
- final ResponseEntity response = restTemplate
- .exchange(postUri, method, new HttpEntity<>(processToSend), DataloadProcess.class);
- if (response == null) {
- throw new GoodDataException("Unable to post dataload process. No response returned from API.");
- }
- return response.getBody();
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to post dataload process.", e);
} finally {
deleteTempFile(tempFile);
}
}
+
+
+
private FutureResult postProcess(DataloadProcess process, URI postUri, HttpMethod method) {
try {
- ResponseEntity exchange = restTemplate.exchange(postUri, method, new HttpEntity<>(process), String.class);
- if (exchange.getStatusCode() == HttpStatus.ACCEPTED) { //deployment worker will create process
- AsyncTask asyncTask = mapper.readValue(exchange.getBody(), AsyncTask.class);
- return new PollResult<>(this, new SimplePollHandler(asyncTask.getUri(), DataloadProcess.class) {
+ ClientResponse response;
+ if (method == HttpMethod.POST) {
+ response = webClient.post()
+ .uri(postUri)
+ .bodyValue(process)
+ .exchange()
+ .block();
+ } else if (method == HttpMethod.PUT) {
+ response = webClient.put()
+ .uri(postUri)
+ .bodyValue(process)
+ .exchange()
+ .block();
+ } else {
+ throw new IllegalStateException("Unsupported HTTP method: " + method);
+ }
+ if (response == null) {
+ throw new GoodDataException("No response received from API call");
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+ String body = response.bodyToMono(String.class).block();
+
+ if (response.statusCode().equals(HttpStatus.ACCEPTED)) {
+ AsyncTask asyncTask = mapper.readValue(body, AsyncTask.class);
+ return new PollResult<>(this, new SimplePollHandler(asyncTask.getUri(), DataloadProcess.class) {
@Override
public void handlePollException(GoodDataRestException e) {
throw new GoodDataException("Creating process failed", e);
}
});
- } else if (exchange.getStatusCode() == HttpStatus.OK) { //object has been found in package registry, deployment worker is not triggered
- final DataloadProcess dataloadProcess = mapper.readValue(exchange.getBody(), DataloadProcess.class);
+ } else if (response.statusCode().equals(HttpStatus.OK)) {
+ final DataloadProcess dataloadProcess = mapper.readValue(body, DataloadProcess.class);
return new PollResult<>(this, new SimplePollHandler(dataloadProcess.getUri(), DataloadProcess.class) {
-
@Override
public void handlePollException(GoodDataRestException e) {
throw new GoodDataException("Creating process failed", e);
}
});
} else {
- throw new IllegalStateException("Unexpected status code from resource: " + exchange.getStatusCode());
+ throw new IllegalStateException("Unexpected status code from resource: " + response.statusCode());
}
- } catch (RestClientException | IOException e) {
+ } catch (WebClientResponseException | IOException e) {
throw new GoodDataException("Creating process failed", e);
}
}
private DataloadProcess postProcess(DataloadProcess process, URI postUri) {
try {
- return restTemplate.postForObject(postUri, process, DataloadProcess.class);
- } catch (GoodDataException | RestClientException e) {
+ return webClient.post()
+ .uri(postUri)
+ .bodyValue(process)
+ .retrieve()
+ .bodyToMono(DataloadProcess.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to create dataload process.", e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataset/DatasetService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataset/DatasetService.java
index c21536dc1..ca4378586 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/dataset/DatasetService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/dataset/DatasetService.java
@@ -15,33 +15,21 @@
import com.gooddata.sdk.service.*;
import com.gooddata.sdk.service.gdc.DataStoreException;
import com.gooddata.sdk.service.gdc.DataStoreService;
-import com.gooddata.sdk.service.project.model.ModelService;
import org.apache.commons.lang3.RandomStringUtils;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import static com.gooddata.sdk.common.util.Validate.notEmpty;
-import static com.gooddata.sdk.common.util.Validate.notNull;
-import static com.gooddata.sdk.common.util.Validate.notNullState;
-import static java.lang.String.format;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static org.springframework.util.StringUtils.isEmpty;
-
-/**
- * Service to work with datasets, manifests and dataset uploads.
- */
+import static com.gooddata.sdk.common.util.Validate.*;
+
public class DatasetService extends AbstractService {
public static final UriTemplate UPLOADS_INFO_TEMPLATE = new UriTemplate(UploadsInfo.URI);
@@ -50,52 +38,44 @@ public class DatasetService extends AbstractService {
private final DataStoreService dataStoreService;
- public DatasetService(final RestTemplate restTemplate, final DataStoreService dataStoreService,
- final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public DatasetService(final WebClient webClient, final DataStoreService dataStoreService, final GoodDataSettings settings) {
+ super(webClient, settings);
this.dataStoreService = dataStoreService;
}
/**
- * Obtains manifest from given project by given datasetId
- *
- * @param project project to which manifest belongs
- * @param datasetId id of dataset
- * @return manifest for dataset
- * @throws DatasetNotFoundException when manifest can't be found (doesn't exist)
- * @throws DatasetException in case the API call failure
+ * Obtains manifest for the given dataset in the specified project.
*/
public DatasetManifest getDatasetManifest(Project project, String datasetId) {
notNull(project, "project");
notNull(project.getId(), "project.id");
notEmpty(datasetId, "datasetId");
try {
- return restTemplate.getForObject(DatasetManifest.URI, DatasetManifest.class, project.getId(), datasetId);
+ String uri = DatasetManifest.URI.replace("{projectId}", project.getId()).replace("{datasetId}", datasetId);
+ DatasetManifest manifest = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(DatasetManifest.class)
+ .block();
+ if (manifest == null) {
+ throw new DatasetNotFoundException(datasetId, null);
+ }
+ return manifest;
} catch (GoodDataRestException e) {
if (e.getStatusCode() == 404) {
throw new DatasetNotFoundException(datasetId, e);
} else {
throw new DatasetException("Unable to get manifest", datasetId, e);
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new DatasetException("Unable to get manifest", datasetId, e);
}
}
/**
- * Loads dataset into platform. Uploads given dataset and manifest to staging area and triggers ETL pull.
- * The call is asynchronous returning {@link FutureResult} to let caller wait for results.
- * Uploaded files are deleted from staging area when finished.
- *
- * @param project project to which dataset belongs
- * @param manifest dataset manifest
- * @param dataset dataset to upload
- * @return {@link FutureResult} of the task, which can throw {@link DatasetException}
- * in case the ETL pull task fails
- * @throws DatasetException if there is a problem to serialize manifest or upload dataset
+ * Loads a dataset into the platform: uploads the dataset and manifest to staging and triggers ETL pull.
*/
- public FutureResult loadDataset(final Project project, final DatasetManifest manifest,
- final InputStream dataset) {
+ public FutureResult loadDataset(final Project project, final DatasetManifest manifest, final InputStream dataset) {
notNull(project, "project");
notNull(dataset, "dataset");
notNull(manifest, "manifest");
@@ -104,15 +84,6 @@ public FutureResult loadDataset(final Project project, final DatasetManife
return loadDatasets(project, manifest);
}
- /**
- * Gets DatasetManifest (using {@link #getDatasetManifest(Project, String)}
- * first and then calls {@link #loadDataset(Project, DatasetManifest, java.io.InputStream)}
- *
- * @param project project to which dataset belongs
- * @param datasetId datasetId to obtain a manifest
- * @param dataset dataset to upload
- * @return {@link FutureResult} of the task
- */
public FutureResult loadDataset(Project project, String datasetId, InputStream dataset) {
notNull(project, "project");
notEmpty(datasetId, "datasetId");
@@ -121,20 +92,11 @@ public FutureResult loadDataset(Project project, String datasetId, InputSt
}
public FutureResult loadDatasets(final Project project, DatasetManifest... datasets) {
- return loadDatasets(project, asList(datasets));
+ return loadDatasets(project, List.of(datasets));
}
/**
- * Loads datasets into platform. Uploads given datasets and their manifests to staging area and triggers ETL pull.
- * The call is asynchronous returning {@link FutureResult} to let caller wait for results.
- * Uploaded files are deleted from staging area when finished.
- *
- * @param project project to which dataset belongs
- * @param datasets map dataset manifests
- * @return {@link FutureResult} of the task, which can throw {@link DatasetException}
- * in case the ETL pull task fails
- * @throws DatasetException if there is a problem to serialize manifest or upload dataset
- * @see batch upload reference
+ * Loads multiple datasets and their manifests to staging, then triggers ETL pull.
*/
public FutureResult loadDatasets(final Project project, final Collection datasets) {
if (dataStoreService == null) {
@@ -152,13 +114,13 @@ public FutureResult loadDatasets(final Project project, final Collection datasets)
notEmpty(datasets, "datasets");
for (DatasetManifest datasetManifest : datasets) {
if (datasetManifest.getSource() == null) {
- throw new IllegalArgumentException(format("Source for dataset '%s' is null", datasetManifest.getDataSet()));
+ throw new IllegalArgumentException(String.format("Source for dataset '%s' is null", datasetManifest.getDataSet()));
}
if (datasetManifest.getFile() == null) {
- throw new IllegalArgumentException(format("File for dataset '%s' is null", datasetManifest.getDataSet()));
+ throw new IllegalArgumentException(String.format("File for dataset '%s' is null", datasetManifest.getDataSet()));
}
- if (isEmpty(datasetManifest.getDataSet())) {
+ if (org.springframework.util.StringUtils.isEmpty(datasetManifest.getDataSet())) {
throw new IllegalArgumentException("Dataset name is empty.");
}
}
@@ -180,15 +142,21 @@ private void validateUploadManifests(final Collection datasets)
private FutureResult pullLoad(Project project, final String dirPath, final Collection datasets) {
notNull(project.getId(), "project.id");
- final PullTask pullTask = restTemplate
- .postForObject(Pull.URI, new Pull(dirPath), PullTask.class, project.getId());
+
+ // Trigger ETL pull via WebClient POST
+ PullTask pullTask = webClient.post()
+ .uri(Pull.URI.replace("{projectId}", project.getId()))
+ .bodyValue(new Pull(dirPath))
+ .retrieve()
+ .bodyToMono(PullTask.class)
+ .block();
return new PollResult<>(this, new AbstractPollHandler(
notNullState(pullTask, "created pull task").getPollUri(), TaskStatus.class, Void.class) {
@Override
public void handlePollResult(TaskStatus pollResult) {
if (!pollResult.isSuccess()) {
- final String message = isEmpty(pollResult.getMessages())
+ final String message = org.springframework.util.StringUtils.isEmpty(pollResult.getMessages())
? String.format(ETL_PULL_DEFAULT_ERROR_MESSAGE, pollResult.getStatus())
: pollResult.getMessages().toString();
throw new DatasetException(message, datasets);
@@ -206,47 +174,51 @@ protected void onFinish() {
try {
dataStoreService.delete(dirPath);
} catch (DataStoreException ignored) {
- // todo log?
+ // TODO: log error
}
}
});
}
/**
- * Lists datasets (links) in project. Returns empty list in case there are no datasets.
- *
- * @param project project to list datasets in
- * @return collection of dataset links or empty list
+ * Lists all datasets (links) in a project. Returns empty list if there are no datasets.
*/
public Collection listDatasetLinks(final Project project) {
notNull(project, "project");
notNull(project.getId(), "project.id");
try {
- final DatasetLinks result = restTemplate.getForObject(DatasetLinks.URI, DatasetLinks.class, project.getId());
+ String uri = DatasetLinks.URI.replace("{projectId}", project.getId());
+ DatasetLinks result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(DatasetLinks.class)
+ .block();
if (result == null) {
throw new GoodDataException("Empty response from API call");
} else if (result.getLinks() == null) {
- return emptyList();
+ return java.util.Collections.emptyList();
}
return result.getLinks();
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list datasets for project " + project.getId(), e);
}
}
/**
- * Optimize SLI hash. This feature is useful only if data warehouse was reduced somehow. Remove unused values from
- * the existing SLI hash.
- *
- * @param project project to optimize SLI hash in
- * @return {@link FutureResult} of the task
+ * Optimize SLI hash for a project.
*/
public FutureResult optimizeSliHash(final Project project) {
notNull(project, "project");
notNull(project.getId(), "project.id");
- final UriResponse uriResponse = restTemplate.postForObject(
- EtlMode.URL, new EtlMode(EtlModeType.SLI, LookupMode.RECREATE), UriResponse.class, project.getId());
+ UriResponse uriResponse = webClient.post()
+ .uri(EtlMode.URL.replace("{projectId}", project.getId()))
+ .bodyValue(new EtlMode(EtlModeType.SLI, LookupMode.RECREATE))
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+
+ final String errorMessage = String.format("Unable to optimize SLI hash for project '%s'", project.getId());
return new PollResult<>(this,
new AbstractPollHandler(
@@ -255,49 +227,33 @@ public FutureResult optimizeSliHash(final Project project) {
@Override
public void handlePollResult(final TaskStatus pollResult) {
if (!pollResult.isSuccess()) {
- throw new GoodDataException("Unable to optimize SLI hash for project " + project.getId());
+ throw new GoodDataException(errorMessage);
}
setResult(null);
}
- @Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
- if (!super.isFinished(response)) {
- return false;
- }
- final TaskStatus maqlDdlTaskStatus = extractData(response, TaskStatus.class);
- if (maqlDdlTaskStatus.isSuccess()) {
- return true;
- }
- throw new GoodDataException("Unable to optimize SLI hash: " + maqlDdlTaskStatus.getMessages());
- }
-
@Override
public void handlePollException(final GoodDataRestException e) {
- throw new GoodDataException("Unable to optimize SLI hash: " + getPollingUri(), e);
+ throw new GoodDataException(errorMessage, e);
}
-
});
-
}
/**
- * Update project data with the given update script (MAQL). This method can be used for data manipulation only,
- * for model changes use {@link ModelService#updateProjectModel}.
- *
- * @param project project to be updated
- * @param maqlDml update script to be executed in the project
- * @return poll result
- * @see ModelService#updateProjectModel
+ * Update project data with the given MAQL script.
*/
public FutureResult updateProjectData(final Project project, final String maqlDml) {
notNull(project, "project");
notNull(project.getId(), "project.id");
- final UriResponse uriResponse = restTemplate.postForObject(
- MaqlDml.URI, new MaqlDml(maqlDml), UriResponse.class, project.getId());
+ UriResponse uriResponse = webClient.post()
+ .uri(MaqlDml.URI.replace("{projectId}", project.getId()))
+ .bodyValue(new MaqlDml(maqlDml))
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
- final String errorMessage = format("Unable to update data for project '%s'", project.getId());
+ final String errorMessage = String.format("Unable to update data for project '%s'", project.getId());
return new PollResult<>(this,
new AbstractPollHandler(
@@ -310,113 +266,103 @@ public void handlePollResult(final TaskState pollResult) {
}
setResult(null);
}
-
- @Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
- final TaskState taskState = extractData(response, TaskState.class);
- if (taskState.isSuccess()) {
- return true;
- } else if (!taskState.isFinished()) {
- return false;
- }
- throw new GoodDataException(errorMessage + ": " + taskState.getMessage());
- }
-
@Override
public void handlePollException(final GoodDataRestException e) {
- throw new GoodDataException(errorMessage + ": " + getPollingUri(), e);
+ throw new GoodDataException(errorMessage, e);
}
});
}
/**
- * Lists all uploads for the dataset with the given identifier in the given project. Returns empty list if there
- * are no uploads for the given dataset.
- *
- * @param project GoodData project
- * @param datasetId dataset identifier
- * @return collection of {@link Upload} objects or empty list
+ * Lists all uploads for a dataset in a project.
*/
public Collection listUploadsForDataset(Project project, String datasetId) {
final UploadsInfo.DataSet dataSet = getDataSetInfo(project, datasetId);
- if (isEmpty(dataSet.getUploadsUri())) {
- return emptyList();
+ if (org.springframework.util.StringUtils.isEmpty(dataSet.getUploadsUri())) {
+ return java.util.Collections.emptyList();
}
try {
- final Uploads result = restTemplate.getForObject(dataSet.getUploadsUri(), Uploads.class);
+ Uploads result = webClient.get()
+ .uri(dataSet.getUploadsUri())
+ .retrieve()
+ .bodyToMono(Uploads.class)
+ .block();
if (result == null) {
throw new GoodDataException("Empty response from '" + dataSet.getUploadsUri() + "'.");
} else if (result.items() == null){
- return emptyList();
+ return java.util.Collections.emptyList();
}
return result.items();
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get '" + dataSet.getUploadsUri() + "'.", e);
}
}
/**
- * Returns last upload for the dataset with given identifier in the given project. Returns null if the last upload
- * doesn't exist.
- *
- * @param project GoodData project
- * @param datasetId dataset identifier
- * @return last dataset upload or {@code null} if the upload doesn't exist
+ * Returns the last upload for a dataset in a project, or null if none exists.
*/
public Upload getLastUploadForDataset(Project project, String datasetId) {
final UploadsInfo.DataSet dataSet = getDataSetInfo(project, datasetId);
- if (isEmpty(dataSet.getLastUploadUri())) {
+ if (!org.springframework.util.StringUtils.hasLength(dataSet.getLastUploadUri())) {
return null;
}
-
+
try {
- return restTemplate.getForObject(dataSet.getLastUploadUri(), Upload.class);
- } catch (RestClientException e) {
+ return webClient.get()
+ .uri(dataSet.getLastUploadUri())
+ .retrieve()
+ .bodyToMono(Upload.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get '" + dataSet.getLastUploadUri() + "'.");
}
}
/**
- * Returns global upload statistics for the given project.
- *
- * @param project GoodData project
- * @return {@link UploadStatistics} object with project's upload statistics
+ * Returns upload statistics for the project.
*/
public UploadStatistics getUploadStatistics(Project project) {
notNull(project, "project");
notNull(project.getId(), "project.id");
try {
- return restTemplate.getForObject(UploadStatistics.URI, UploadStatistics.class, project.getId());
- } catch (RestClientException e) {
+ String uri = UploadStatistics.URI.replace("{projectId}", project.getId());
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(UploadStatistics.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get dataset uploads statistics.", e);
}
}
/**
- * Returns {@link UploadsInfo.DataSet} object containing upload information for the given dataset in the given project.
- *
- * Package-private for testing purposes.
+ * Returns DataSet information for the given dataset and project.
*/
- UploadsInfo.DataSet getDataSetInfo(Project project, String datasetId) {
+ protected UploadsInfo.DataSet getDataSetInfo(Project project, String datasetId) {
notNull(project, "project");
notNull(project.getId(), "project.id");
notEmpty(datasetId, "datasetId");
final URI uploadsInfoUri = UPLOADS_INFO_TEMPLATE.expand(project.getId());
try {
- final UploadsInfo uploadsInfo = restTemplate.getForObject(uploadsInfoUri, UploadsInfo.class);
+ UploadsInfo uploadsInfo = webClient.get()
+ .uri(uploadsInfoUri)
+ .retrieve()
+ .bodyToMono(UploadsInfo.class)
+ .block();
if (uploadsInfo == null) {
throw new GoodDataException("Empty response from '" + uploadsInfoUri.toString() + "'.");
}
return uploadsInfo.getDataSet(datasetId);
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get '" + uploadsInfoUri.toString() + "'.", e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/executeafm/ExecuteAfmService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/executeafm/ExecuteAfmService.java
index b648993ca..695472db9 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/executeafm/ExecuteAfmService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/executeafm/ExecuteAfmService.java
@@ -14,10 +14,10 @@
import com.gooddata.sdk.model.executeafm.result.ExecutionResult;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.*;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
+
import static com.gooddata.sdk.common.util.Validate.notNull;
/**
@@ -49,13 +49,16 @@ public class ExecuteAfmService extends AbstractService {
*/
public static final String RESULT_LIMIT = "limit";
+ private final WebClient webClient;
+
/**
* Constructor.
- * @param restTemplate rest template
+ * @param webClient web client
* @param settings settings
*/
- public ExecuteAfmService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ExecuteAfmService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
+ this.webClient = webClient;
}
/**
@@ -66,22 +69,16 @@ public ExecuteAfmService(final RestTemplate restTemplate, final GoodDataSettings
*/
public ExecutionResponse executeAfm(final Project project, final Execution execution) {
final String projectId = notNull(notNull(project, "project").getId(), "projectId");
- final ExecutionResponse response;
try {
- response = restTemplate.postForObject(
- AFM_EXECUTION_URI,
- notNull(execution, "execution"),
- ExecutionResponse.class,
- projectId);
- } catch (GoodDataException | RestClientException e) {
+ return webClient.post()
+ .uri(AFM_EXECUTION_URI, projectId)
+ .bodyValue(notNull(execution, "execution"))
+ .retrieve()
+ .bodyToMono(ExecutionResponse.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to execute AFM", e);
}
-
- if (response == null) {
- throw new GoodDataException("Empty response when execution posted to API");
- }
-
- return response;
}
/**
@@ -92,22 +89,16 @@ public ExecutionResponse executeAfm(final Project project, final Execution execu
*/
public ExecutionResponse executeVisualization(final Project project, final VisualizationExecution execution) {
final String projectId = notNull(notNull(project, "project").getId(), "projectId");
- final ExecutionResponse response;
try {
- response = restTemplate.postForObject(
- VISUALIZATION_EXECUTION_URI,
- notNull(execution, "execution"),
- ExecutionResponse.class,
- projectId);
- } catch (GoodDataException | RestClientException e) {
+ return webClient.post()
+ .uri(VISUALIZATION_EXECUTION_URI, projectId)
+ .bodyValue(notNull(execution, "execution"))
+ .retrieve()
+ .bodyToMono(ExecutionResponse.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to execute visualization", e);
}
-
- if (response == null) {
- throw new GoodDataException("Empty response when execution posted to API");
- }
-
- return response;
}
/**
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/export/ExportService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/export/ExportService.java
index 24c84d373..9b9b24125 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/export/ExportService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/export/ExportService.java
@@ -20,11 +20,9 @@
import com.gooddata.sdk.model.md.report.ReportDefinition;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.*;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.ResponseEntity;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
import java.io.IOException;
@@ -32,42 +30,29 @@
import static com.gooddata.sdk.common.util.Validate.notNull;
import static com.gooddata.sdk.common.util.Validate.notNullState;
-import static org.springframework.http.HttpMethod.GET;
-import static org.springframework.http.HttpMethod.POST;
/**
- * Export project data
- *
+ * Export project data using WebClient (Spring 6, JDK 17)
*/
public class ExportService extends AbstractService {
public static final String EXPORTING_URI = "/gdc/exporter/executor";
-
private static final String CLIENT_EXPORT_URI = "/gdc/projects/{projectId}/clientexport";
-
private static final String RAW_EXPORT_URI = "/gdc/projects/{projectId}/execute/raw";
-
public static final UriTemplate OBJ_TEMPLATE = new UriTemplate(Obj.OBJ_URI);
public static final UriTemplate PROJECT_TEMPLATE = new UriTemplate(Project.URI);
/**
* Service for data export
- * @param restTemplate REST template
+ * @param webClient WebClient
* @param settings settings
*/
- public ExportService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ExportService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
* Export the given report definition in the given format to the given output stream
- *
- * @param reportDefinition report definition
- * @param format export format
- * @param output target
- * @return polling result
- * @throws NoDataExportException in case report contains no data
- * @throws ExportException on error
*/
public FutureResult export(final ReportDefinition reportDefinition, final ExportFormat format,
final OutputStream output) {
@@ -78,13 +63,6 @@ public FutureResult export(final ReportDefinition reportDefinition, final
/**
* Export the given report in the given format to the given output stream
- *
- * @param report report
- * @param format export format
- * @param output target
- * @return polling result
- * @throws NoDataExportException in case report contains no data
- * @throws ExportException on error
*/
public FutureResult export(final Report report, final ExportFormat format,
final OutputStream output) {
@@ -100,44 +78,54 @@ private FutureResult exportReport(final ReportRequest request, final Expor
final String uri = exportReport(execResult, format);
return new PollResult<>(this, new SimplePollHandler(uri, Void.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- switch (response.getStatusCode()) {
- case OK:
- return true;
- case ACCEPTED:
- return false;
- case NO_CONTENT:
- throw new NoDataExportException();
- default:
- throw new ExportException("Unable to export report, unknown HTTP response code: " + response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) {
+ return true;
+ } else if (code == 202) {
+ return false;
+ } else if (code == 204) {
+ throw new NoDataExportException();
+ } else {
+ throw new ExportException("Unable to export report, unknown HTTP response code: " + code);
}
}
@Override
- public void handlePollException(final GoodDataRestException e) {
- throw new ExportException("Unable to export report", e);
+ protected void onFinish() {
+ // Download file using WebClient and write to OutputStream
+ byte[] data = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(byte[].class)
+ .block();
+ if (data != null) {
+ try {
+ output.write(data);
+ } catch (IOException e) {
+ throw new ExportException("Unable to write export to output stream", e);
+ }
+ }
}
@Override
- protected void onFinish() {
- try {
- restTemplate.execute(uri, GET, null, new OutputStreamResponseExtractor(output));
- } catch (GoodDataException | RestClientException e) {
- throw new ExportException("Unable to export report", e);
- }
+ public void handlePollException(final GoodDataRestException e) {
+ throw new ExportException("Unable to export report", e);
}
});
}
protected JsonNode executeReport(final String executionUri, final ReportRequest request) {
try {
- final ResponseEntity entity = restTemplate
- .exchange(executionUri, POST, new HttpEntity<>(request), String.class);
- return mapper.readTree(entity.getBody());
- } catch (GoodDataException | RestClientException e) {
+ String responseBody = webClient.post()
+ .uri(executionUri)
+ .bodyValue(request)
+ .retrieve()
+ .bodyToMono(String.class)
+ .block();
+ return mapper.readTree(responseBody);
+ } catch (Exception e) {
throw new ExportException("Unable to execute report", e);
- } catch (IOException e) {
- throw new ExportException("Unable to read execution result", e);
}
}
@@ -152,21 +140,20 @@ private String exportReport(final JsonNode execResult, final ExportFormat format
root.set("result_req", child);
try {
- return notNullState(restTemplate.postForObject(EXPORTING_URI, root, UriResponse.class), "exported report").getUri();
- } catch (GoodDataException | RestClientException e) {
+ UriResponse uriResponse = webClient.post()
+ .uri(EXPORTING_URI)
+ .bodyValue(root)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+ return notNullState(uriResponse, "exported report").getUri();
+ } catch (Exception e) {
throw new ExportException("Unable to export report", e);
}
}
/**
* Export the given dashboard tab in PDF format to the given output stream
- *
- * @param endpoint endpoint for which the export is generated
- * @param dashboard dashboard
- * @param tab tab
- * @param output output
- * @return polling result
- * @throws ExportException if export fails
*/
public FutureResult exportPdf(final GoodDataEndpoint endpoint, final ProjectDashboard dashboard, final Tab tab, final OutputStream output) {
notNull(endpoint, "endpoint");
@@ -181,31 +168,43 @@ public FutureResult exportPdf(final GoodDataEndpoint endpoint, final Proje
final ClientExport export = new ClientExport(endpoint.toUri(), projectUri, dashboardUri, tab.getIdentifier());
final AsyncTask task;
try {
- task = restTemplate.postForObject(CLIENT_EXPORT_URI, export, AsyncTask.class, projectId);
- } catch (RestClientException | GoodDataRestException e) {
+ task = webClient.post()
+ .uri(CLIENT_EXPORT_URI.replace("{projectId}", projectId))
+ .bodyValue(export)
+ .retrieve()
+ .bodyToMono(AsyncTask.class)
+ .block();
+ } catch (Exception e) {
throw new ExportException("Unable to export dashboard: " + dashboardUri, e);
}
return new PollResult<>(this, new SimplePollHandler(notNullState(task, "export pdf task").getUri(), Void.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- switch (response.getStatusCode()) {
- case OK:
- return true;
- case ACCEPTED:
- return false;
- default:
- throw new ExportException("Unable to export dashboard: " + dashboardUri +
- ", unknown HTTP response code: " + response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) {
+ return true;
+ } else if (code == 202) {
+ return false;
+ } else {
+ throw new ExportException("Unable to export dashboard: " + dashboardUri +
+ ", unknown HTTP response code: " + code);
}
}
@Override
protected void onFinish() {
- try {
- restTemplate.execute(task.getUri(), GET, null, new OutputStreamResponseExtractor(output));
- } catch (GoodDataException | RestClientException e) {
- throw new ExportException("Unable to export dashboard: " + dashboardUri, e);
+ byte[] data = webClient.get()
+ .uri(task.getUri())
+ .retrieve()
+ .bodyToMono(byte[].class)
+ .block();
+ if (data != null) {
+ try {
+ output.write(data);
+ } catch (IOException e) {
+ throw new ExportException("Unable to write dashboard export to stream", e);
+ }
}
}
@@ -218,10 +217,6 @@ public void handlePollException(final GoodDataRestException e) {
/**
* Export the given Report using the raw export (without columns/rows limitations)
- * @param report report
- * @param output output
- * @return polling result
- * @throws ExportException in case export fails
*/
public FutureResult exportCsv(final Report report, final OutputStream output) {
notNull(report, "report");
@@ -230,10 +225,6 @@ public FutureResult exportCsv(final Report report, final OutputStream outp
/**
* Export the given Report Definition using the raw export (without columns/rows limitations)
- * @param definition report definition
- * @param output output
- * @return polling result
- * @throws ExportException in case export fails
*/
public FutureResult exportCsv(final ReportDefinition definition, final OutputStream output) {
final ReportRequest request = new ExecuteReportDefinition(definition);
@@ -246,40 +237,51 @@ private FutureResult exportCsv(final AbstractObj obj, final ReportRequest
notNull(output, "output");
final String projectId = extractProjectId(obj);
- final String uri = obj.getUri();
-
- final UriResponse response;
+ final String uri;
try {
- response = restTemplate.postForObject(RAW_EXPORT_URI, request, UriResponse.class, projectId);
- } catch (RestClientException | GoodDataRestException e) {
- throw new ExportException("Unable to export: " + uri);
- }
- if (response == null || response.getUri() == null) {
- throw new ExportException("Empty response, unable to export: " + uri);
+ UriResponse response = webClient.post()
+ .uri(RAW_EXPORT_URI.replace("{projectId}", projectId))
+ .bodyValue(request)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+ if (response == null || response.getUri() == null) {
+ throw new ExportException("Empty response, unable to export: " + obj.getUri());
+ }
+ uri = response.getUri();
+ } catch (Exception e) {
+ throw new ExportException("Unable to export: " + obj.getUri(), e);
}
- return new PollResult<>(this, new SimplePollHandler(response.getUri(), Void.class) {
+ return new PollResult<>(this, new SimplePollHandler(uri, Void.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- switch (response.getStatusCode()) {
- case OK:
- return true;
- case ACCEPTED:
- return false;
- case NO_CONTENT:
- throw new NoDataExportException();
- default:
- throw new ExportException("Unable to export: " + uri +
- ", unknown HTTP response code: " + response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) { // OK
+ return true;
+ } else if (code == 202) { // ACCEPTED
+ return false;
+ } else if (code == 204) { // NO_CONTENT
+ throw new NoDataExportException();
+ } else {
+ throw new ExportException("Unable to export: " + uri +
+ ", unknown HTTP response code: " + code);
}
}
@Override
protected void onFinish() {
- try {
- restTemplate.execute(getPolling(), GET, null, new OutputStreamResponseExtractor(output));
- } catch (GoodDataException | RestClientException e) {
- throw new ExportException("Unable to export: " + uri, e);
+ byte[] data = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(byte[].class)
+ .block();
+ if (data != null) {
+ try {
+ output.write(data);
+ } catch (IOException e) {
+ throw new ExportException("Unable to write export to output stream", e);
+ }
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/featureflag/FeatureFlagService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/featureflag/FeatureFlagService.java
index 6ad32cbad..c5326c6ed 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/featureflag/FeatureFlagService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/featureflag/FeatureFlagService.java
@@ -14,9 +14,9 @@
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
import com.gooddata.sdk.service.hierarchicalconfig.HierarchicalConfigService;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
+import reactor.core.publisher.Mono;
import java.net.URI;
@@ -24,7 +24,7 @@
import static com.gooddata.sdk.common.util.Validate.notNull;
/**
- * Provides feature flag management. Feature flag is a boolean flag used for enabling / disabling some specific feature
+ * Provides feature flag management. Feature flag is a boolean flag used for enabling/disabling some specific feature
* of GoodData platform. It can be used in various scopes (per project, per project group, per user, global etc.).
* @deprecated Use {@link HierarchicalConfigService}.
*/
@@ -37,70 +37,60 @@ public class FeatureFlagService extends AbstractService {
/**
* Constructs service for GoodData feature flags management.
- * @param restTemplate RESTful HTTP Spring template
+ * @param webClient WebClient for HTTP communication
* @param settings settings
*/
- public FeatureFlagService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public FeatureFlagService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
- * Returns aggregated feature flags for given project and current user (aggregates global, project group, project
- * and user feature flags).
- * It doesn't matter whether feature flag is enabled or not, it'll be included in both cases.
- *
- * @param project project, cannot be null
- * @return aggregated feature flags for given project and current user
+ * Returns aggregated feature flags for given project and current user.
* @deprecated Use {@link HierarchicalConfigService#listProjectConfigItems(Project)}.
*/
public FeatureFlags listFeatureFlags(final Project project) {
notNull(project, "project");
try {
- final FeatureFlags featureFlags = restTemplate
- .getForObject(AGGREGATED_FEATURE_FLAGS_TEMPLATE.expand(project.getId()), FeatureFlags.class);
+ FeatureFlags featureFlags = webClient.get()
+ .uri(AGGREGATED_FEATURE_FLAGS_TEMPLATE.expand(project.getId()).toString())
+ .retrieve()
+ .bodyToMono(FeatureFlags.class)
+ .block();
if (featureFlags == null) {
- throw new GoodDataException("empty response from API call");
+ throw new GoodDataException("Empty response from API call");
}
return featureFlags;
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list aggregated feature flags for project ID=" + project.getId(), e);
}
}
/**
- * Returns project feature flags (only project scoped flags, use {@link #listFeatureFlags(Project)} for aggregated
- * flags from all scopes) for given project.
- * It doesn't matter whether feature flag is enabled or not, it'll be included in both cases.
- *
- * @param project project, cannot be null
- * @return list of all feature flags for given project
+ * Returns project feature flags for given project.
* @deprecated Use {@link HierarchicalConfigService#listProjectConfigItems(Project)}.
*/
public ProjectFeatureFlags listProjectFeatureFlags(final Project project) {
notNull(project, "project");
try {
- final ProjectFeatureFlags projectFeatureFlags = restTemplate
- .getForObject(PROJECT_FEATURE_FLAGS_TEMPLATE.expand(project.getId()), ProjectFeatureFlags.class);
+ ProjectFeatureFlags projectFeatureFlags = webClient.get()
+ .uri(PROJECT_FEATURE_FLAGS_TEMPLATE.expand(project.getId()).toString())
+ .retrieve()
+ .bodyToMono(ProjectFeatureFlags.class)
+ .block();
if (projectFeatureFlags == null) {
- throw new GoodDataException("empty response from API call");
+ throw new GoodDataException("Empty response from API call");
}
return projectFeatureFlags;
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list project feature flags for project ID=" + project.getId(), e);
}
}
/**
- * Returns project feature flag (only project scoped flags, use {@link #listFeatureFlags(Project)} for aggregated
- * flags from all scopes) for given project by its unique name (aka "key").
- * It doesn't matter whether feature flag is enabled or not, it'll be included in both cases.
- *
- * @param project project, cannot be null
- * @param featureFlagName unique name (key) of feature flag, cannot be empty
- * @return feature flag for given project with given name (key)
+ * Returns project feature flag for given project by its unique name (key).
* @deprecated Use {@link HierarchicalConfigService#getProjectConfigItem(Project, String)}.
*/
public ProjectFeatureFlag getProjectFeatureFlag(final Project project, final String featureFlagName) {
@@ -109,20 +99,13 @@ public ProjectFeatureFlag getProjectFeatureFlag(final Project project, final Str
try {
return getProjectFeatureFlag(getProjectFeatureFlagUri(project, featureFlagName));
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get project feature flag: " + featureFlagName, e);
}
}
/**
* Creates new feature flag for given project.
- *
- * Usually, it doesn't make sense to create feature flag that is disabled because
- * this is the same as having no feature flag at all.
- *
- * @param project project for which the feature flag should be created, cannot be null
- * @param flag feature flag to be created, cannot be null
- * @return created feature flag
* @deprecated Use {@link HierarchicalConfigService#setProjectConfigItem(Project, ConfigItem)}.
*/
public ProjectFeatureFlag createProjectFeatureFlag(final Project project, final ProjectFeatureFlag flag) {
@@ -132,22 +115,25 @@ public ProjectFeatureFlag createProjectFeatureFlag(final Project project, final
final String featureFlagsUri = PROJECT_FEATURE_FLAGS_TEMPLATE.expand(project.getId()).toString();
try {
- final URI featureFlagUri = restTemplate.postForLocation(featureFlagsUri, flag);
+ URI featureFlagUri = webClient.post()
+ .uri(featureFlagsUri)
+ .bodyValue(flag)
+ .retrieve()
+ .toBodilessEntity()
+ .map(response -> response.getHeaders().getLocation())
+ .block();
+
if (featureFlagUri == null) {
throw new GoodDataException("URI of new project feature flag can't be null");
}
return getProjectFeatureFlag(featureFlagUri.toString());
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to create project feature flag: " + flag, e);
}
}
/**
* Updates existing project feature flag.
- * Note that it doesn't make sense to update any other property than {@link ProjectFeatureFlag#isEnabled()}.
- *
- * @param flag feature flag to be updated, cannot be null and it has to contain URI
- * @return updated feature flag
* @deprecated Use {@link HierarchicalConfigService#setProjectConfigItem(Project, ConfigItem)}.
*/
public ProjectFeatureFlag updateProjectFeatureFlag(final ProjectFeatureFlag flag) {
@@ -155,17 +141,21 @@ public ProjectFeatureFlag updateProjectFeatureFlag(final ProjectFeatureFlag flag
notEmpty(flag.getUri(), "flag.uri");
try {
- restTemplate.put(flag.getUri(), flag);
+ webClient.put()
+ .uri(flag.getUri())
+ .bodyValue(flag)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+
return getProjectFeatureFlag(flag.getUri());
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to update project feature flag: " + flag, e);
}
}
/**
* Deletes existing project feature flag.
- *
- * @param flag existing project feature flag with links set properly, cannot be null
* @deprecated Use {@link HierarchicalConfigService#removeProjectConfigItem(ConfigItem)}.
*/
public void deleteProjectFeatureFlag(final ProjectFeatureFlag flag) {
@@ -173,19 +163,27 @@ public void deleteProjectFeatureFlag(final ProjectFeatureFlag flag) {
notEmpty(flag.getUri(), "flag URI");
try {
- restTemplate.delete(flag.getUri());
- } catch (GoodDataException | RestClientException e) {
+ webClient.delete()
+ .uri(flag.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to delete project feature flag: " + flag, e);
}
}
-
String getProjectFeatureFlagUri(final Project project, final String flagName) {
return PROJECT_FEATURE_FLAG_TEMPLATE.expand(project.getId(), flagName).toString();
}
private ProjectFeatureFlag getProjectFeatureFlag(final String flagUri) {
- final ProjectFeatureFlag result = restTemplate.getForObject(flagUri, ProjectFeatureFlag.class);
+ ProjectFeatureFlag result = webClient.get()
+ .uri(flagUri)
+ .retrieve()
+ .bodyToMono(ProjectFeatureFlag.class)
+ .block();
+
if (result == null) {
throw new GoodDataException("Project feature flag cannot be retrieved from URI " + flagUri);
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/DataStoreService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/DataStoreService.java
index 993ef1f1b..6f8b36219 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/DataStoreService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/DataStoreService.java
@@ -5,63 +5,44 @@
*/
package com.gooddata.sdk.service.gdc;
-import com.github.sardine.impl.SardineException;
-import com.gooddata.sdk.common.GoodDataRestException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Component;
+import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
import com.gooddata.sdk.common.UriPrefixer;
import com.gooddata.sdk.service.httpcomponents.SingleEndpointGoodDataRestProvider;
-import org.apache.http.*;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.NonRepeatableRequestException;
-import org.apache.http.client.ResponseHandler;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.conn.ClientConnectionManager;
-import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.params.HttpParams;
-import org.apache.http.protocol.HTTP;
-import org.apache.http.protocol.HttpContext;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.web.reactive.function.client.ClientResponse;
+
-import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
-import java.util.*;
import java.util.function.Supplier;
import static com.gooddata.sdk.common.util.Validate.notEmpty;
import static com.gooddata.sdk.common.util.Validate.notNull;
-/**
- * Uploads, downloads, deletes, ... at datastore
- */
+@Component
public class DataStoreService {
- private final GdcSardine sardine;
private final Supplier stagingUriSupplier;
private final URI gdcUri;
- private final RestTemplate restTemplate;
-
+ private final WebClient webClient;
private UriPrefixer prefixer;
-
- /**
- * Creates new DataStoreService
- * @param restProvider restProvider to make datastore connection
- * @param stagingUriSupplier used to obtain datastore URI
- */
- public DataStoreService(SingleEndpointGoodDataRestProvider restProvider, Supplier stagingUriSupplier) {
- notNull(restProvider, "restProvider");
+ @Autowired
+ public DataStoreService(SingleEndpointGoodDataRestProvider restProvider,
+ Supplier stagingUriSupplier,
+ WebClient webClient) {
this.stagingUriSupplier = notNull(stagingUriSupplier, "stagingUriSupplier");
this.gdcUri = URI.create(notNull(restProvider.getEndpoint(), "endpoint").toUri());
- this.restTemplate = notNull(restProvider.getRestTemplate(), "restTemplate");
- sardine = new GdcSardine(new CustomHttpClientBuilder(notNull(restProvider.getHttpClient(), "httpClient")));
+ this.webClient = notNull(webClient, "webClient");
}
private UriPrefixer getPrefixer() {
@@ -69,360 +50,83 @@ private UriPrefixer getPrefixer() {
final String uriString = stagingUriSupplier.get();
final URI uri = URI.create(uriString);
prefixer = new UriPrefixer(uri.isAbsolute() ? uri : gdcUri.resolve(uriString));
- sardine.enablePreemptiveAuthentication(prefixer.getUriPrefix().getHost());
}
return prefixer;
}
- /**
- * Returns uri for given path (which is used by this service for upload, download or delete)
- * @param path path the uri is constructed for
- * @return uri for given path
- */
public URI getUri(String path) {
return getPrefixer().mergeUris(path);
}
- /**
- * Uploads given stream to given datastore path
- * @param path path where to upload to
- * @param stream stream to upload
- * @throws DataStoreException in case upload failed
- */
+ // Blocking upload (suitable for imperative code)
public void upload(String path, InputStream stream) {
notEmpty(path, "path");
notNull(stream, "stream");
- upload(getUri(path), stream);
- }
-
- private void upload(URI url, InputStream stream) {
- try {
- // We need to use it this way, if we want to track request_id in the stacktrace.
- InputStreamEntity entity = new InputStreamEntity(stream);
- List headers = Collections.singletonList(new BasicHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE));
- sardine.put(url.toString(), entity, headers, new GdcSardineResponseHandler());
- } catch (SardineException e) {
- if (HttpStatus.INTERNAL_SERVER_ERROR.value() == e.getStatusCode()) {
- // this error may occur when user issues request to WebDAV before SST and TT were obtained
- // and WebDAV is deployed on a separate hostname
- // see https://github.com/gooddata/gooddata-java/wiki/Known-limitations
- throw new DataStoreException(createUnAuthRequestWarningMessage(url), e);
- } else {
- throw new DataStoreException("Unable to upload to " + url + " got status " + e.getStatusCode(), e);
- }
- } catch (NoHttpResponseException e) {
- // this error may occur when user issues request to WebDAV before SST and TT were obtained
- // and WebDAV is deployed on a separate hostname since R136
- // see https://github.com/gooddata/gooddata-java/wiki/Known-limitations
- throw new DataStoreException(createUnAuthRequestWarningMessage(url), e);
- } catch (IOException e) {
- // this error may occur when user issues request to WebDAV before SST and TT were obtained
- // and WebDAV deployed on the same hostname
- // see https://github.com/gooddata/gooddata-java/wiki/Known-limitations
- if (e.getCause() instanceof NonRepeatableRequestException) {
- throw new DataStoreException(createUnAuthRequestWarningMessage(url), e);
- } else {
- throw new DataStoreException("Unable to upload to " + url, e);
- }
- }
+ uploadAsync(path, stream).block();
}
- private String createUnAuthRequestWarningMessage(final URI url) {
- return "Got 500 while uploading to " + url + "."
- + "\nThis can be known limitation, see https://github.com/gooddata/gooddata-java/wiki/Known-limitations";
+ // Non-blocking upload (for reactive use-cases)
+ public Mono uploadAsync(String path, InputStream stream) {
+ notEmpty(path, "path");
+ notNull(stream, "stream");
+ URI url = getUri(path);
+ return webClient.put()
+ .uri(url)
+ .body(BodyInserters.fromDataBuffers(DataBufferUtils.readInputStream(() -> stream,
+ new DefaultDataBufferFactory(), 4096)))
+ .retrieve()
+ .toBodilessEntity()
+ .then()
+ .onErrorMap(e -> wrapException("upload", url, e));
}
- /**
- * Download given path and return data as stream
- * @param path path from where to download
- * @return download stream
- * @throws DataStoreException in case download failed
- */
+ // Blocking download
public InputStream download(String path) {
notEmpty(path, "path");
final URI uri = getUri(path);
- try {
- return sardine.get(uri.toString(), Collections.emptyList(), new GdcSardineResponseHandler());
- } catch (IOException e) {
- throw new DataStoreException("Unable to download from " + uri, e);
- }
+ return downloadAsync(path)
+ .map(dataBuffer -> dataBuffer.asInputStream(true))
+ .block();
}
- /**
- * Delete given path from datastore.
- * @param path path to delete
- * @throws DataStoreException in case delete failed
- */
- public void delete(String path) {
+ // Non-blocking download (returns DataBuffer)
+ public Mono downloadAsync(String path) {
notEmpty(path, "path");
final URI uri = getUri(path);
- try {
- final ResponseEntity result = restTemplate.exchange(uri, HttpMethod.DELETE, org.springframework.http.HttpEntity.EMPTY, Void.class);
-
- // in case we get redirect (i.e. when we want to delete collection) we will follow redirect to the new location
- if (HttpStatus.MOVED_PERMANENTLY.equals(result.getStatusCode())) {
- restTemplate.exchange(result.getHeaders().getLocation(), HttpMethod.DELETE, org.springframework.http.HttpEntity.EMPTY, Void.class);
- }
- } catch (GoodDataRestException e) {
- throw new DataStoreException("Unable to delete " + uri, e);
- }
+ Flux dataBufferFlux = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .onStatus(
+ HttpStatusCode::isError,
+ clientResponse -> clientResponse.bodyToMono(String.class)
+ .defaultIfEmpty("Unknown error")
+ .map(body -> new DataStoreException("Download failed: " + uri + " Body: " + body, null))
+ )
+ .bodyToFlux(DataBuffer.class);
+
+ return DataBufferUtils.join(dataBufferFlux)
+ .onErrorMap(e -> wrapException("download", uri, e));
}
- /**
- * This class is needed to provide Sardine with instance of {@link CloseableHttpClient}, because
- * used {@link com.gooddata.http.client.GoodDataHttpClient} is not Closeable at all (on purpose).
- * Thanks to that we can use proper GoodData authentication mechanism instead of basic auth.
- *
- * It creates simple closeable wrapper around plain {@link HttpClient} where {@code close()}
- * is implemented as noop (respectively for the response used).
- */
- private static class CustomHttpClientBuilder extends HttpClientBuilder {
-
- private final HttpClient client;
-
- private CustomHttpClientBuilder(HttpClient client) {
- this.client = client;
- }
- @Override
- public CloseableHttpClient build() {
- return new FakeCloseableHttpClient(client);
- }
+ public void delete(String path) {
+ notEmpty(path, "path");
+ final URI uri = getUri(path);
+ deleteAsync(path).block();
}
- private static class FakeCloseableHttpClient extends CloseableHttpClient {
- private final HttpClient client;
-
- private FakeCloseableHttpClient(HttpClient client) {
- notNull(client, "client");
- this.client = client;
- }
-
- @Override
- protected CloseableHttpResponse doExecute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
- // nothing to do - this method is never called, because we override all methods from CloseableHttpClient
- return null;
- }
-
- @Override
- public void close() throws IOException {
- // nothing to close - wrappedClient doesn't have to implement CloseableHttpClient
- }
-
- /**
- * @deprecated because supertype's {@link HttpClient#getParams()} is deprecated.
- */
- @Override
- @Deprecated
- public HttpParams getParams() {
- return client.getParams();
- }
-
- /**
- * @deprecated because supertype's {@link HttpClient#getConnectionManager()} is deprecated.
- */
- @Override
- @Deprecated
- public ClientConnectionManager getConnectionManager() {
- return client.getConnectionManager();
- }
-
- @Override
- public CloseableHttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException {
- return new FakeCloseableHttpResponse(client.execute(request));
- }
-
- @Override
- public CloseableHttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException, ClientProtocolException {
- return new FakeCloseableHttpResponse(client.execute(request, context));
- }
-
- @Override
- public CloseableHttpResponse execute(HttpHost target, HttpRequest request) throws IOException, ClientProtocolException {
- return new FakeCloseableHttpResponse(client.execute(target, request));
- }
-
- @Override
- public CloseableHttpResponse execute(HttpHost target, HttpRequest request, HttpContext context) throws IOException, ClientProtocolException {
- return new FakeCloseableHttpResponse(client.execute(target, request, context));
- }
-
- @Override
- public T execute(HttpUriRequest request, ResponseHandler extends T> responseHandler) throws IOException, ClientProtocolException {
- return client.execute(request, responseHandler);
- }
-
- @Override
- public T execute(HttpUriRequest request, ResponseHandler extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException {
- return client.execute(request, responseHandler, context);
- }
-
- @Override
- public T execute(HttpHost target, HttpRequest request, ResponseHandler extends T> responseHandler) throws IOException, ClientProtocolException {
- return client.execute(target, request, responseHandler);
- }
-
- @Override
- public T execute(HttpHost target, HttpRequest request, ResponseHandler extends T> responseHandler, HttpContext context) throws IOException, ClientProtocolException {
- return client.execute(target, request, responseHandler, context);
- }
+ public Mono deleteAsync(String path) {
+ notEmpty(path, "path");
+ final URI uri = getUri(path);
+ return webClient.delete()
+ .uri(uri)
+ .retrieve()
+ .toBodilessEntity()
+ .then()
+ .onErrorMap(e -> wrapException("delete", uri, e));
}
- private static class FakeCloseableHttpResponse implements CloseableHttpResponse {
-
- private final HttpResponse wrappedResponse;
-
- public FakeCloseableHttpResponse(HttpResponse wrappedResponse) {
- notNull(wrappedResponse, "wrappedResponse");
- this.wrappedResponse = wrappedResponse;
- }
-
- @Override
- public void close() throws IOException {
- // nothing to close - wrappedClient doesn't have to implement CloseableHttpResponse
- }
-
- @Override
- public StatusLine getStatusLine() {
- return wrappedResponse.getStatusLine();
- }
-
- @Override
- public void setStatusLine(StatusLine statusline) {
- wrappedResponse.setStatusLine(statusline);
- }
-
- @Override
- public void setStatusLine(ProtocolVersion ver, int code) {
- wrappedResponse.setStatusLine(ver, code);
- }
-
- @Override
- public void setStatusLine(ProtocolVersion ver, int code, String reason) {
- wrappedResponse.setStatusLine(ver, code, reason);
- }
-
- @Override
- public void setStatusCode(int code) throws IllegalStateException {
- wrappedResponse.setStatusCode(code);
- }
-
- @Override
- public void setReasonPhrase(String reason) throws IllegalStateException {
- wrappedResponse.setReasonPhrase(reason);
- }
-
- @Override
- public HttpEntity getEntity() {
- return wrappedResponse.getEntity();
- }
-
- @Override
- public void setEntity(HttpEntity entity) {
- wrappedResponse.setEntity(entity);
- }
-
- @Override
- public Locale getLocale() {
- return wrappedResponse.getLocale();
- }
-
- @Override
- public void setLocale(Locale loc) {
- wrappedResponse.setLocale(loc);
- }
-
- @Override
- public ProtocolVersion getProtocolVersion() {
- return wrappedResponse.getProtocolVersion();
- }
-
- @Override
- public boolean containsHeader(String name) {
- return wrappedResponse.containsHeader(name);
- }
-
- @Override
- public Header[] getHeaders(String name) {
- return wrappedResponse.getHeaders(name);
- }
-
- @Override
- public Header getFirstHeader(String name) {
- return wrappedResponse.getFirstHeader(name);
- }
-
- @Override
- public Header getLastHeader(String name) {
- return wrappedResponse.getLastHeader(name);
- }
-
- @Override
- public Header[] getAllHeaders() {
- return wrappedResponse.getAllHeaders();
- }
-
- @Override
- public void addHeader(Header header) {
- wrappedResponse.addHeader(header);
- }
-
- @Override
- public void addHeader(String name, String value) {
- wrappedResponse.addHeader(name, value);
- }
-
- @Override
- public void setHeader(Header header) {
- wrappedResponse.setHeader(header);
- }
-
- @Override
- public void setHeader(String name, String value) {
- wrappedResponse.setHeader(name, value);
- }
-
- @Override
- public void setHeaders(Header[] headers) {
- wrappedResponse.setHeaders(headers);
- }
-
- @Override
- public void removeHeader(Header header) {
- wrappedResponse.removeHeader(header);
- }
-
- @Override
- public void removeHeaders(String name) {
- wrappedResponse.removeHeaders(name);
- }
-
- @Override
- public HeaderIterator headerIterator() {
- return wrappedResponse.headerIterator();
- }
-
- @Override
- public HeaderIterator headerIterator(String name) {
- return wrappedResponse.headerIterator(name);
- }
-
- /**
- * @deprecated because supertype's {@link HttpMessage#getParams()} is deprecated.
- */
- @Deprecated
- @Override
- public HttpParams getParams() {
- return wrappedResponse.getParams();
- }
-
- /**
- * @deprecated because supertype's {@link HttpMessage#setParams(HttpParams)} is deprecated.
- */
- @Deprecated
- @Override
- public void setParams(HttpParams params) {
- wrappedResponse.setParams(params);
- }
+ private RuntimeException wrapException(String operation, URI uri, Throwable e) {
+ return new DataStoreException("Unable to " + operation + " from " + uri, e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/GdcService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/GdcService.java
index 072129007..bd9af76f1 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/GdcService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/gdc/GdcService.java
@@ -9,16 +9,19 @@
import com.gooddata.sdk.model.gdc.RootLinks;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Mono;
/**
* Service to work with GoodData API root.
*/
public class GdcService extends AbstractService {
- public GdcService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+
+ // WebClient is injected instead of RestTemplate
+ public GdcService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -28,10 +31,15 @@ public GdcService(final RestTemplate restTemplate, final GoodDataSettings settin
*/
public RootLinks getRootLinks() {
try {
- return restTemplate.getForObject(RootLinks.URI, RootLinks.class);
- } catch (GoodDataException | RestClientException e) {
+ // Blocking call for backward compatibility
+ return webClient.get()
+ .uri(RootLinks.URI)
+ .retrieve()
+ .bodyToMono(RootLinks.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ // Exception handling for WebClient
throw new GoodDataException("Unable to get gdc root links", e);
}
}
-
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/hierarchicalconfig/HierarchicalConfigService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/hierarchicalconfig/HierarchicalConfigService.java
index 8a99959c3..7a2108d46 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/hierarchicalconfig/HierarchicalConfigService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/hierarchicalconfig/HierarchicalConfigService.java
@@ -11,8 +11,14 @@
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriTemplate;
import static com.gooddata.sdk.common.util.Validate.notEmpty;
@@ -27,8 +33,8 @@ public class HierarchicalConfigService extends AbstractService {
public final static UriTemplate PROJECT_CONFIG_ITEMS_TEMPLATE = new UriTemplate(ConfigItems.PROJECT_CONFIG_ITEMS_URI);
public final static UriTemplate PROJECT_CONFIG_ITEM_TEMPLATE = new UriTemplate(ConfigItem.PROJECT_CONFIG_ITEM_URI);
- public HierarchicalConfigService(RestTemplate restTemplate, GoodDataSettings settings) {
- super(restTemplate, settings);
+ public HierarchicalConfigService(WebClient webClient, GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -40,17 +46,24 @@ public HierarchicalConfigService(RestTemplate restTemplate, GoodDataSettings set
public ConfigItems listProjectConfigItems(final Project project) {
notNull(project, "project");
try {
- ConfigItems configItems = restTemplate.getForObject(PROJECT_CONFIG_ITEMS_TEMPLATE.expand(project.getId()), ConfigItems.class);
+ ConfigItems configItems = webClient.get()
+ .uri(PROJECT_CONFIG_ITEMS_TEMPLATE.expand(project.getId()))
+ .retrieve()
+ .bodyToMono(ConfigItems.class)
+ .block();
if (configItems == null) {
throw new GoodDataException("empty response from API call");
}
return configItems;
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
+ throw new GoodDataException("Unable to list config items for project ID=" + project.getId(), e);
+ } catch (Exception e) {
throw new GoodDataException("Unable to list config items for project ID=" + project.getId(), e);
}
}
+
/**
* Returns config item for given project (even if it's inherited from its hierarchy).
*
@@ -64,12 +77,15 @@ public ConfigItem getProjectConfigItem(final Project project, final String confi
try {
String configUri = PROJECT_CONFIG_ITEM_TEMPLATE.expand(project.getId(), configName).toString();
- return getProjectConfigItem(configUri);
- } catch (GoodDataException | RestClientException e) {
+ return getProjectConfigItem(configUri);
+ } catch (WebClientResponseException | GoodDataException e) {
+ throw new GoodDataException("Unable to get project config item: " + configName, e);
+ } catch (Exception e) {
throw new GoodDataException("Unable to get project config item: " + configName, e);
}
}
+
/**
* Creates or updates config item for given project.
*
@@ -77,15 +93,20 @@ public ConfigItem getProjectConfigItem(final Project project, final String confi
* @param configItem config item to be created/updated, cannot be null
* @return created/updated project config item
*/
- public ConfigItem setProjectConfigItem(final Project project, final ConfigItem configItem) {
+ public ConfigItem setProjectConfigItem(final Project project, final ConfigItem configItem) {
notNull(project, "project");
notNull(configItem, "configItem");
try {
String configUri = PROJECT_CONFIG_ITEM_TEMPLATE.expand(project.getId(), configItem.getName()).toString();
- restTemplate.put(configUri, configItem);
- return getProjectConfigItem(configUri);
- } catch (GoodDataException | RestClientException e) {
+ webClient.put()
+ .uri(configUri)
+ .bodyValue(configItem)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ return getProjectConfigItem(configUri);
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to set project config item: " + configItem.getKey(), e);
}
}
@@ -95,19 +116,26 @@ public ConfigItem setProjectConfigItem(final Project project, final ConfigItem c
*
* @param configItem existing project config item with links set properly, cannot be null
*/
- public void removeProjectConfigItem(final ConfigItem configItem) {
+ public void removeProjectConfigItem(ConfigItem configItem) {
notNull(configItem, "configItem");
- notEmpty(configItem.getUri(), "config item URI");
-
try {
- restTemplate.delete(configItem.getUri());
- } catch (RestClientException e) {
- throw new GoodDataException("Unable to remove project config item: " + configItem.getKey(), e);
+ webClient.delete()
+ .uri(configItem.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
+ throw new GoodDataException("Unable to remove project config item: " + configItem.getUri(), e);
}
}
+
private ConfigItem getProjectConfigItem(String configUri) {
- ConfigItem configItem = restTemplate.getForObject(configUri, ConfigItem.class);
+ ConfigItem configItem = webClient.get()
+ .uri(configUri)
+ .retrieve()
+ .bodyToMono(ConfigItem.class)
+ .block();
if (configItem == null) {
throw new GoodDataException("Project config item cannot be retrieved from URI " + configUri);
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProvider.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProvider.java
index 5394b6484..1e151ec55 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProvider.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProvider.java
@@ -5,55 +5,49 @@
*/
package com.gooddata.sdk.service.httpcomponents;
-import com.gooddata.http.client.GoodDataHttpClient;
-import com.gooddata.http.client.LoginSSTRetrievalStrategy;
-import com.gooddata.http.client.SSTRetrievalStrategy;
import com.gooddata.sdk.service.GoodDataEndpoint;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.apache.http.HttpHost;
-import org.apache.http.client.HttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
+import org.springframework.web.reactive.function.client.WebClient;
-import static com.gooddata.sdk.common.util.Validate.notNull;
/**
- * The default {@link com.gooddata.sdk.service.GoodDataRestProvider} used internally by {@link com.gooddata.sdk.service.GoodData}.
- * Provides configured single endpoint REST connection using standard GoodData login and password authentication.
- *
- * See https://help.gooddata.com/display/API/API+Reference#/reference/authentication/log-in
+ * GoodDataRestProvider using login/password, upgraded to use reactive WebClient.
*/
public final class LoginPasswordGoodDataRestProvider extends SingleEndpointGoodDataRestProvider {
- /**
- * Creates new instance.
- * @param endpoint endpoint of GoodData API
- * @param settings settings
- * @param login API user login
- * @param password API user password
- */
- public LoginPasswordGoodDataRestProvider(final GoodDataEndpoint endpoint, final GoodDataSettings settings,
- final String login, final String password) {
- super(endpoint, settings, (builder, builderEndpoint, builderSettings) -> createHttpClient(builder, builderEndpoint, login, password));
+ public LoginPasswordGoodDataRestProvider(
+ final GoodDataEndpoint endpoint,
+ final GoodDataSettings settings,
+ final String login,
+ final String password) {
+ super(endpoint, settings, createWebClientWithBasicAuth(endpoint, settings, login, password));
}
- /**
- * Creates http client using given builder and endpoint, authenticating by login and password.
- * @param builder builder to build client from
- * @param endpoint API endpoint to connect client to
- * @param login login
- * @param password password
- * @return configured http client
- */
- public static HttpClient createHttpClient(final HttpClientBuilder builder, final GoodDataEndpoint endpoint,
- final String login, final String password) {
- notNull(endpoint, "endpoint");
- notNull(builder, "builder");
- notNull(login, "login");
- notNull(password, "password");
+ public LoginPasswordGoodDataRestProvider(
+ final GoodDataEndpoint endpoint,
+ final GoodDataSettings settings,
+ final String login,
+ final String password,
+ final WebClient webClient) {
+ super(endpoint, settings, webClient);
+ }
+
+ private static WebClient createWebClientWithBasicAuth(
+ GoodDataEndpoint endpoint,
+ GoodDataSettings settings,
+ String login,
+ String password) {
+ String auth = login + ":" + password;
+ String encodedAuth = java.util.Base64.getEncoder().encodeToString(auth.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ String authHeader = "Basic " + encodedAuth;
- final HttpClient httpClient = builder.build();
- final SSTRetrievalStrategy strategy = new LoginSSTRetrievalStrategy(login, password);
- final HttpHost httpHost = new HttpHost(endpoint.getHostname(), endpoint.getPort(), endpoint.getProtocol());
- return new GoodDataHttpClient(httpClient, httpHost, strategy);
+ return WebClient.builder()
+ .baseUrl(endpoint.toUri().toString())
+ .defaultHeaders(headers -> {
+ settings.getPresetHeaders().forEach(headers::add);
+ headers.add("Authorization", authHeader);
+ })
+ .defaultHeader("User-Agent", settings.getGoodDataUserAgent())
+ .build();
}
-}
+}
\ No newline at end of file
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProvider.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProvider.java
index 8fefe24da..3974ae1a2 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProvider.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProvider.java
@@ -5,41 +5,20 @@
*/
package com.gooddata.sdk.service.httpcomponents;
-import com.gooddata.sdk.common.UriPrefixingClientHttpRequestFactory;
+import com.gooddata.sdk.common.UriPrefixingWebClient;
import com.gooddata.sdk.service.*;
import com.gooddata.sdk.service.gdc.DataStoreService;
-import com.gooddata.sdk.service.retry.RetryableRestTemplate;
-import com.gooddata.sdk.service.util.ResponseErrorHandler;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.config.CookieSpecs;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.config.SocketConfig;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import java.util.Optional;
import java.util.function.Supplier;
import static com.gooddata.sdk.common.util.Validate.notNull;
-import static java.util.Arrays.asList;
/**
- * {@link GoodDataRestProvider} capable to be used with single API endpoint using the
- * Apache {@link HttpClient} to perform HTTP operations. It provides following functionality:
- *
- * - Prepends the URI path with API endpoint (using {@link UriPrefixingClientHttpRequestFactory}
- * - Configures {@link ResponseErrorHandler}
- * - Configures connection according to {@link GoodDataSettings}
- * - Set default headers from {@link GoodDataSettings} including User-Agent
- * - Configures retries in case it's requested
- *
- *
- * To provide complete implementation, this class must be extended and descendants should implement own logic by providing
- * {@link GoodDataHttpClientBuilder} to the constructor. Namely the authentication logic remains to be provided by descendants.
+ * Abstract base provider for GoodData services using a single API endpoint and reactive WebClient.
*/
public abstract class SingleEndpointGoodDataRestProvider implements GoodDataRestProvider {
@@ -47,28 +26,34 @@ public abstract class SingleEndpointGoodDataRestProvider implements GoodDataRest
protected final GoodDataEndpoint endpoint;
protected final GoodDataSettings settings;
-
- protected HttpClient httpClient;
- protected RestTemplate restTemplate;
+ protected final WebClient webClient;
/**
- * Creates new instance.
+ * Creates a new provider instance.
*
* @param endpoint API endpoint
- * @param settings settings
- * @param builder custom GoodData http client builder
+ * @param settings configuration settings
*/
- protected SingleEndpointGoodDataRestProvider(final GoodDataEndpoint endpoint, final GoodDataSettings settings,
- final GoodDataHttpClientBuilder builder) {
- this.endpoint = endpoint;
- this.settings = settings;
- this.restTemplate = createRestTemplate(endpoint, settings, builder.buildHttpClient(
- createHttpClientBuilder(settings), endpoint, settings));
+ protected SingleEndpointGoodDataRestProvider(final GoodDataEndpoint endpoint, final GoodDataSettings settings) {
+ this.endpoint = notNull(endpoint, "endpoint");
+ this.settings = notNull(settings, "settings");
+
+ // You can use UriPrefixingWebClient here if needed, or just use WebClient.builder().baseUrl(endpoint.getUrl()).build();
+ this.webClient = createWebClient(endpoint, settings);
}
- @Override
- public RestTemplate getRestTemplate() {
- return restTemplate;
+ protected SingleEndpointGoodDataRestProvider(final GoodDataEndpoint endpoint, final GoodDataSettings settings, WebClient webClient) {
+ this.endpoint = notNull(endpoint, "endpoint");
+ this.settings = notNull(settings, "settings");
+ this.webClient = notNull(webClient, "webClient");
+ }
+
+
+ /**
+ * Returns the configured reactive WebClient.
+ */
+ public WebClient getWebClient() {
+ return webClient;
}
@Override
@@ -80,84 +65,36 @@ public GoodDataSettings getSettings() {
public Optional getDataStoreService(Supplier stagingUriSupplier) {
try {
Class.forName("com.github.sardine.Sardine", false, getClass().getClassLoader());
- return Optional.of(new DataStoreService(this, stagingUriSupplier));
+ // pass the WebClient instance
+ return Optional.of(new DataStoreService(this, stagingUriSupplier, webClient));
} catch (ClassNotFoundException e) {
logger.info("Optional dependency Sardine not found - WebDAV related operations are not supported");
return Optional.empty();
}
}
- /**
- * @return used API endpoint
- */
+
public GoodDataEndpoint getEndpoint() {
return endpoint;
}
/**
- * @return configured http client
- */
- public HttpClient getHttpClient() {
- return httpClient;
- }
-
- /**
- * Creates configured REST template
+ * Creates a WebClient instance with custom settings (e.g. headers, User-Agent, endpoint base URL).
+ *
* @param endpoint API endpoint
- * @param settings settings
- * @param httpClient http client to build RestTemplate on
- * @return configured REST template
+ * @param settings configuration settings
+ * @return configured WebClient instance
*/
- protected RestTemplate createRestTemplate(final GoodDataEndpoint endpoint, final GoodDataSettings settings, final HttpClient httpClient) {
- notNull(endpoint, "endpoint");
- notNull(settings, "settings");
- this.httpClient = notNull(httpClient, "httpClient");
-
- final UriPrefixingClientHttpRequestFactory factory = new UriPrefixingClientHttpRequestFactory(
- new HttpComponentsClientHttpRequestFactory(httpClient),
- endpoint.toUri()
- );
-
- final RestTemplate restTemplate;
- if (settings.getRetrySettings() == null) {
- restTemplate = new RestTemplate(factory);
- } else {
- restTemplate = RetryableRestTemplate.create(settings.getRetrySettings(), factory);
- }
- restTemplate.setInterceptors(asList(
- new HeaderSettingRequestInterceptor(settings.getPresetHeaders()),
- new DeprecationWarningRequestInterceptor()));
-
- restTemplate.setErrorHandler(new ResponseErrorHandler(restTemplate.getMessageConverters()));
+ protected WebClient createWebClient(final GoodDataEndpoint endpoint, final GoodDataSettings settings) {
+ WebClient.Builder builder = WebClient.builder()
+ .baseUrl(endpoint.toUri().toString())
+ .defaultHeaders(headers -> settings.getPresetHeaders().forEach(headers::add))
+ .defaultHeader("User-Agent", settings.getGoodDataUserAgent());
- return restTemplate;
- }
+ // If you need proxy or timeouts, add .clientConnector(...) here.
+ // If you want to use UriPrefixingWebClient, use it as a wrapper:
+ // return new UriPrefixingWebClient(builder, endpoint.toUri()).getWebClient();
- /**
- * Creates http client builder, applying given settings.
- * @param settings settings to apply
- * @return configured builder
- */
- protected HttpClientBuilder createHttpClientBuilder(final GoodDataSettings settings) {
- final PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
- connectionManager.setDefaultMaxPerRoute(settings.getMaxConnections());
- connectionManager.setMaxTotal(settings.getMaxConnections());
-
- final SocketConfig.Builder socketConfig = SocketConfig.copy(SocketConfig.DEFAULT);
- socketConfig.setSoTimeout(settings.getSocketTimeout());
- connectionManager.setDefaultSocketConfig(socketConfig.build());
-
- final RequestConfig.Builder requestConfig = RequestConfig.copy(RequestConfig.DEFAULT);
- requestConfig.setConnectTimeout(settings.getConnectionTimeout());
- requestConfig.setConnectionRequestTimeout(settings.getConnectionRequestTimeout());
- requestConfig.setSocketTimeout(settings.getSocketTimeout());
- requestConfig.setCookieSpec(CookieSpecs.STANDARD);
-
- return HttpClientBuilder.create()
- .setUserAgent(settings.getGoodDataUserAgent())
- .setConnectionManager(connectionManager)
- .addInterceptorFirst(new RequestIdInterceptor())
- .addInterceptorFirst(new ResponseMissingRequestIdInterceptor())
- .setDefaultRequestConfig(requestConfig.build());
+ return builder.build();
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProvider.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProvider.java
index 375104a8a..b5075d060 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProvider.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProvider.java
@@ -10,18 +10,16 @@
import com.gooddata.http.client.SimpleSSTRetrievalStrategy;
import com.gooddata.sdk.service.GoodDataEndpoint;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.apache.http.HttpHost;
-import org.apache.http.client.HttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.hc.client5.http.classic.HttpClient;
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
+import org.apache.hc.core5.http.HttpHost;
import static com.gooddata.sdk.common.util.Validate.notNull;
-/**
- * The {@link com.gooddata.sdk.service.GoodDataRestProvider}, which
- * provides configured single endpoint REST connection using standard pre created SST to authenticate.
- */
public final class SstGoodDataRestProvider extends SingleEndpointGoodDataRestProvider {
+ private final String sst;
+
/**
* Create SST REST provider
* @param endpoint endpoint of GoodData API
@@ -29,24 +27,21 @@ public final class SstGoodDataRestProvider extends SingleEndpointGoodDataRestPro
* @param sst super secure token
*/
public SstGoodDataRestProvider(final GoodDataEndpoint endpoint, final GoodDataSettings settings, final String sst) {
- super(endpoint, settings, (b, e, s) -> createHttpClient(b, e, sst));
+ super(endpoint, settings);
+ this.sst = sst;
}
- /**
- * Creates http client using given builder and endpoint, authenticating by sst.
- * @param builder builder to build client from
- * @param endpoint API endpoint to connect client to
- * @param sst token used for authentication
- * @return configured http client
- */
- public static HttpClient createHttpClient(HttpClientBuilder builder, GoodDataEndpoint endpoint, String sst) {
+
+ public static GoodDataHttpClient createGoodDataHttpClient(HttpClientBuilder builder, GoodDataEndpoint endpoint, String sst) {
notNull(endpoint, "endpoint");
notNull(builder, "builder");
notNull(sst, "sst");
final HttpClient httpClient = builder.build();
final SSTRetrievalStrategy strategy = new SimpleSSTRetrievalStrategy(sst);
- final HttpHost httpHost = new HttpHost(endpoint.getHostname(), endpoint.getPort(), endpoint.getProtocol());
+ final HttpHost httpHost = new HttpHost(endpoint.getProtocol(), endpoint.getHostname(), endpoint.getPort());
return new GoodDataHttpClient(httpClient, httpHost, strategy);
}
+
+
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/lcm/LcmService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/lcm/LcmService.java
index 20878fcc8..a71364fb8 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/lcm/LcmService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/lcm/LcmService.java
@@ -18,8 +18,7 @@
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriTemplate;
import java.net.URI;
@@ -34,19 +33,20 @@ public class LcmService extends AbstractService {
public static final UriTemplate LCM_ENTITIES_TEMPLATE = new UriTemplate(LcmEntities.URI);
/**
- * Constructs service for GoodData Life Cycle Management.
- * @param restTemplate RESTful HTTP Spring template
- * @param settings settings
+ * Constructs service for GoodData Life Cycle Management using WebClient.
+ * @param webClient WebClient for HTTP communication
+ * @param settings configuration settings
*/
- public LcmService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public LcmService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
- * Lists all {@link LcmEntities} for given {@link Account}.
+ * Lists all {@link LcmEntities} for given {@link Account}.
* Returns empty list in case there is no {@link LcmEntity}.
- * Returns only first page if there's more instances than page limit. Use {@link PageBrowser#allItemsStream()} ()} to iterate
- * over all pages, or {@link PageBrowser#getAllItems()} ()} to load the entire list.
+ * Returns only first page if there's more instances than page limit.
+ * Use {@link PageBrowser#allItemsStream()} to iterate over all pages,
+ * or {@link PageBrowser#getAllItems()} to load the entire list.
*
* @param account account to list LCM entities for
* @return {@link PageBrowser} first page of list of lcm entities or empty list
@@ -56,23 +56,25 @@ public PageBrowser listLcmEntities(final Account account) {
}
/**
- * Lists {@link LcmEntities} for given {@link Account} filtered according given {@link LcmEntityFilter}.
+ * Lists {@link LcmEntities} for given {@link Account} filtered according given {@link LcmEntityFilter}.
* Returns empty list in case there is no {@link LcmEntity}.
- * Returns only first page if there's more instances than page limit. Use {@link PageBrowser#allItemsStream()} ()} to iterate
- * over all pages, or {@link PageBrowser#getAllItems()} to load the entire list.
+ * Returns only first page if there's more instances than page limit.
+ * Use {@link PageBrowser#allItemsStream()} to iterate over all pages,
+ * or {@link PageBrowser#getAllItems()} to load the entire list.
*
* @param account account to list LCM entities for
* @param filter filter of the entities
- * @return {@link PageBrowser} first page of list of lcm entitiesor empty list
+ * @return {@link PageBrowser} first page of list of lcm entities or empty list
*/
public PageBrowser listLcmEntities(final Account account, final LcmEntityFilter filter) {
return listLcmEntities(account, filter, new CustomPageRequest());
}
/**
- * Lists all {@link LcmEntities} for given {@link Account}.
+ * Lists all {@link LcmEntities} for given {@link Account}.
* Returns empty list in case there is no {@link LcmEntity}.
- * Returns requested page (by page limit and offset). Use {@link #listLcmEntities(Account)} to get first page with default setting.
+ * Returns requested page (by page limit and offset).
+ * Use {@link #listLcmEntities(Account)} to get first page with default setting.
*
* @param account account to list LCM entities for
* @param startPage page to be listed
@@ -83,9 +85,10 @@ public PageBrowser listLcmEntities(final Account account, final PageR
}
/**
- * Lists {@link LcmEntities} for given {@link Account} filtered according given {@link LcmEntityFilter}.
+ * Lists {@link LcmEntities} for given {@link Account} filtered according given {@link LcmEntityFilter}.
* Returns empty list in case there is no {@link LcmEntity}.
- * Returns requested page (by page limit and offset). Use {@link #listLcmEntities(Account)} to get first page with default setting.
+ * Returns requested page (by page limit and offset).
+ * Use {@link #listLcmEntities(Account)} to get first page with default setting.
*
* @param account account to list LCM entities for
* @param filter filter of the entities
@@ -109,14 +112,24 @@ private URI getLcmEntitiesUri(final String accountId, final LcmEntityFilter filt
return page.getPageUri(mutableUri);
}
+ /**
+ * Load a page of LCM entities from the server.
+ * @param uri URI to load
+ * @return page of LcmEntity objects (may be empty, never null)
+ */
private Page listLcmEntities(final URI uri) {
try {
- final LcmEntities result = restTemplate.getForObject(uri, LcmEntities.class);
+ final LcmEntities result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(LcmEntities.class)
+ .block();
+
if (result == null) {
return new Page<>();
}
return result;
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list LcmEntity", e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/md/MetadataService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/md/MetadataService.java
index 92161e900..b77099161 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/md/MetadataService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/md/MetadataService.java
@@ -15,7 +15,8 @@
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriTemplate;
import java.util.*;
@@ -33,8 +34,8 @@ public class MetadataService extends AbstractService {
public static final UriTemplate OBJ_TEMPLATE = new UriTemplate(Obj.OBJ_URI);
private static final Set IRREGULAR_PLURAL_WORD_SUFFIXES = new HashSet<>(asList("s", "ch", "sh", "x", "o"));
- public MetadataService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public MetadataService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -58,8 +59,15 @@ public T createObj(Project project, T obj) {
final T response;
try {
- response = restTemplate.postForObject(Obj.CREATE_WITH_ID_URI, obj, (Class)obj.getClass(), project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ response = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(Obj.CREATE_WITH_ID_URI).build(project.getId()))
+ .bodyValue(obj)
+ .retrieve()
+ .bodyToMono((Class) obj.getClass())
+ .block();
+ } catch (WebClientResponseException e) {
+ throw new ObjCreateException(obj, e);
+ } catch (Exception e) {
throw new ObjCreateException(obj, e);
}
@@ -80,28 +88,39 @@ public T createObj(Project project, T obj) {
* @throws com.gooddata.sdk.common.GoodDataRestException if GoodData REST API returns unexpected status code
* @throws com.gooddata.sdk.common.GoodDataException if no response from API or client-side HTTP error
*/
- public T getObjByUri(String uri, Class cls) {
- notNull(uri, "uri");
+ public T getObjByUri(String uri, Class cls) { //CHANGED
+ notNull(uri, "uri");
notNull(cls, "cls");
try {
- final T result = restTemplate.getForObject(uri, cls);
+ final T result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(cls)
+ .block();
if (result != null) {
return result;
} else {
throw new GoodDataException("Received empty response from API call.");
}
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
+ throw new ObjNotFoundException(uri, cls, e);
+ } else {
+ throw new GoodDataException("Unable to get " + cls.getSimpleName().toLowerCase() + " " + uri, e);
+ }
} catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ if (e.getStatusCode() == 404) {
throw new ObjNotFoundException(uri, cls, e);
} else {
- throw e;
+ throw new GoodDataException("Unable to get " + cls.getSimpleName().toLowerCase() + " " + uri, e);
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get " + cls.getSimpleName().toLowerCase() + " " + uri, e);
}
}
+
/**
* Retrieves a collection of objects corresponding to the supplied collection of URIs.
*
@@ -115,14 +134,21 @@ public Collection getObjsByUris(Project project, Collection uris) {
notNull(uris, "uris");
try {
- final BulkGet result = restTemplate.postForObject(BulkGet.URI, new BulkGetUris(uris), BulkGet.class, project.getId());
-
- if (result != null) {
+ final BulkGet result = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(BulkGet.URI).build(project.getId()))
+ .bodyValue(new BulkGetUris(uris))
+ .retrieve()
+ .bodyToMono(BulkGet.class)
+ .block();
+
+ if (result != null) {
return result.getItems();
} else {
throw new GoodDataException("Received empty response from API call.");
}
- } catch (RestClientException e) {
+ } catch (GoodDataRestException e) {
+ throw e;
+ } catch (Exception e) {
throw new GoodDataException("Unable to get objects. Some of the supplied URIs may be malformed.", e);
}
}
@@ -140,9 +166,14 @@ public T updateObj(T obj) {
notNull(obj, "obj");
notNull(obj.getUri(), "obj.uri");
try {
- restTemplate.put(obj.getUri(), obj);
+ webClient.put()
+ .uri(obj.getUri())
+ .bodyValue(obj)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
return getObjByUri(obj.getUri(), (Class) obj.getClass());
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new ObjUpdateException(obj, e);
}
}
@@ -159,14 +190,18 @@ public void removeObj(Obj obj) {
notNull(obj, "obj");
notNull(obj.getUri(), "obj.uri");
try {
- restTemplate.delete(obj.getUri());
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ webClient.delete()
+ .uri(obj.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new ObjNotFoundException(obj);
} else {
- throw e;
+ throw new GoodDataException("Unable to remove " + obj.getClass().getSimpleName().toLowerCase() + " " + obj.getUri(), e);
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to remove " + obj.getClass().getSimpleName().toLowerCase() + " " + obj.getUri(), e);
}
}
@@ -182,14 +217,18 @@ public void removeObj(Obj obj) {
public void removeObjByUri(String uri) {
notNull(uri, "uri");
try {
- restTemplate.delete(uri);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ webClient.delete()
+ .uri(uri)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
throw new ObjNotFoundException(uri);
} else {
- throw e;
+ throw new GoodDataException("Unable to remove " + uri, e);
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to remove " + uri, e);
}
}
@@ -273,14 +312,19 @@ public Collection find(Project project, Class cl
final String type = getQueryType(cls);
try {
- final Query queryResult = restTemplate.getForObject(Query.URI, Query.class, project.getId(), type);
+
+ final Query queryResult = webClient.get()
+ .uri(uriBuilder -> uriBuilder.path(Query.URI).build(project.getId(), type))
+ .retrieve()
+ .bodyToMono(Query.class)
+ .block();
if (queryResult != null && queryResult.getEntries() != null) {
return filterEntries(queryResult.getEntries(), restrictions);
} else {
throw new GoodDataException("Received empty response from API call.");
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to query metadata: " + type, e);
}
}
@@ -356,8 +400,13 @@ public Collection usedBy(Project project, Collection uris, boolea
final UseMany response;
try {
- response = restTemplate.postForObject(InUseMany.USEDBY_URI, new InUseMany(uris, nearest, types), UseMany.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ response = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(InUseMany.USEDBY_URI).build(project.getId()))
+ .bodyValue(new InUseMany(uris, nearest, types))
+ .retrieve()
+ .bodyToMono(UseMany.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to find objects.", e);
}
final List usages = new ArrayList<>(uris.size());
@@ -432,9 +481,14 @@ public List getAttributeElements(DisplayForm displayForm) {
}
try {
- final AttributeElements attributeElements = restTemplate.getForObject(elementsUri, AttributeElements.class);
+ // changed
+ final AttributeElements attributeElements = webClient.get()
+ .uri(elementsUri)
+ .retrieve()
+ .bodyToMono(AttributeElements.class)
+ .block();
return notNullState(attributeElements, "attributeElements").getElements();
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) { // changed
throw new GoodDataException("Unable to get attribute elements from " + elementsUri + ".", e);
}
}
@@ -452,14 +506,18 @@ public String getTimezone(final Project project) {
notNull(project.getId(), "project.id");
try {
- final Service result = restTemplate.getForObject(TIMEZONE_URI, Service.class, project.getId());
+ final Service result = webClient.get()
+ .uri(uriBuilder -> uriBuilder.path(TIMEZONE_URI).build(project.getId()))
+ .retrieve()
+ .bodyToMono(Service.class)
+ .block();
if (result != null) {
return result.getTimezone();
} else {
throw new GoodDataException("Received empty response from API call.");
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get timezone of project/workspace " + project.getId(), e);
}
}
@@ -477,13 +535,17 @@ public void setTimezone(final Project project, final String timezone) {
notEmpty(timezone, "timezone");
try {
- final Service result = restTemplate.postForObject(TIMEZONE_URI, new Service(timezone), Service.class,
- project.getId());
+ final Service result = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(TIMEZONE_URI).build(project.getId()))
+ .bodyValue(new Service(timezone))
+ .retrieve()
+ .bodyToMono(Service.class)
+ .block();
if (result == null) {
throw new GoodDataException("Unexpected empty result from API call.");
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to set timezone of project/workspace " + project.getId(), e);
}
}
@@ -515,8 +577,13 @@ private Collection filterEntries(Collection entries, Restriction..
private IdentifiersAndUris getUrisForIdentifiers(final Project project, final Collection identifiers) {
final IdentifiersAndUris response;
try {
- response = restTemplate.postForObject(IdentifiersAndUris.URI, new IdentifierToUri(identifiers), IdentifiersAndUris.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ response = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(IdentifiersAndUris.URI).build(project.getId()))
+ .bodyValue(new IdentifierToUri(identifiers))
+ .retrieve()
+ .bodyToMono(IdentifiersAndUris.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get URIs from identifiers.", e);
}
return response;
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/md/maintenance/ExportImportService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/md/maintenance/ExportImportService.java
index 0a2e8daca..a16eb30eb 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/md/maintenance/ExportImportService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/md/maintenance/ExportImportService.java
@@ -12,9 +12,10 @@
import com.gooddata.sdk.model.md.maintenance.*;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.*;
-import org.springframework.http.client.ClientHttpResponse;
+
+import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.ClientResponse;
import java.io.IOException;
@@ -27,8 +28,8 @@
*/
public class ExportImportService extends AbstractService {
- public ExportImportService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ExportImportService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -46,8 +47,13 @@ public FutureResult partialExport(Project project, final P
final PartialMdArtifact partialMdArtifact;
try {
- partialMdArtifact = restTemplate.postForObject(PartialMdExport.URI, export, PartialMdArtifact.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ partialMdArtifact = webClient.post()
+ .uri(PartialMdExport.URI.replace("{projectId}", project.getId()))
+ .bodyValue(export)
+ .retrieve()
+ .bodyToMono(PartialMdArtifact.class)
+ .block();
+ } catch (Exception e) {
throw new ExportImportException("Unable to export metadata from objects " + export.getUris() + ".", e);
}
@@ -84,8 +90,13 @@ public FutureResult partialImport(Project project, PartialMdExportToken md
final UriResponse importResponse;
try {
- importResponse = restTemplate.postForObject(PartialMdExportToken.URI, mdExportToken, UriResponse.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ importResponse = webClient.post()
+ .uri(PartialMdExportToken.URI.replace("{projectId}", project.getId()))
+ .bodyValue(mdExportToken)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+ } catch (Exception e) {
throw new ExportImportException("Unable to import partial metadata to project '" + project.getId()
+ "' with token '" + mdExportToken.getToken() + "'.", e);
}
@@ -124,9 +135,13 @@ public FutureResult exportProject(final Project project, fin
final ExportProjectArtifact exportProjectArtifact;
final String errorMessage = format("Unable to export complete project '%s'", project.getId());
try {
- exportProjectArtifact = restTemplate
- .postForObject(ExportProject.URI, export, ExportProjectArtifact.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ exportProjectArtifact = webClient.post()
+ .uri(ExportProject.URI.replace("{projectId}", project.getId()))
+ .bodyValue(export)
+ .retrieve()
+ .bodyToMono(ExportProjectArtifact.class)
+ .block();
+ } catch (Exception e) {
throw new ExportImportException(errorMessage, e);
}
@@ -143,14 +158,20 @@ public void handlePollResult(TaskState pollResult) {
}
@Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
- final TaskState taskState = extractData(response, TaskState.class);
- if (taskState != null && taskState.isSuccess()) {
- return true;
- } else if (taskState == null || !taskState.isFinished()) {
+ public boolean isFinished(final ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) { // OK
+ TaskState taskState = response.bodyToMono(TaskState.class).block();
+ if (taskState != null && taskState.isSuccess()) {
+ return true;
+ } else if (taskState == null || !taskState.isFinished()) {
+ return false;
+ }
+ throw new ExportImportException(errorMessage + ": " + (taskState != null ? taskState.getMessage() : "no message"));
+ } else if (code == 202) { // ACCEPTED
return false;
}
- throw new ExportImportException(errorMessage + ": " + taskState.getMessage());
+ throw new ExportImportException(errorMessage + ": unknown HTTP response code: " + code);
}
@Override
@@ -177,8 +198,13 @@ public FutureResult importProject(final Project project, final ExportProje
final String errorMessage = format("Unable to import complete project into '%s' with token '%s'",
project.getId(), exportToken.getToken());
try {
- importResponse = restTemplate.postForObject(ExportProjectToken.URI, exportToken, UriResponse.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ importResponse = webClient.post()
+ .uri(ExportProjectToken.URI.replace("{projectId}", project.getId()))
+ .bodyValue(exportToken)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+ } catch (Exception e) {
throw new ExportImportException(errorMessage, e);
}
@@ -193,16 +219,23 @@ public void handlePollResult(TaskState pollResult) {
setResult(null);
}
- @Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
- final TaskState taskState = extractData(response, TaskState.class);
+ @Override
+ public boolean isFinished(final ClientResponse response) {
+ int code = response.statusCode().value();
+ if (code == 200) { // OK
+ TaskState taskState = response.bodyToMono(TaskState.class).block();
if (taskState != null && taskState.isSuccess()) {
return true;
} else if (taskState == null || !taskState.isFinished()) {
return false;
}
- throw new ExportImportException(errorMessage + ": " + taskState.getMessage());
+ throw new ExportImportException(errorMessage + ": " + (taskState != null ? taskState.getMessage() : "no message"));
+ } else if (code == 202) { // ACCEPTED
+ return false;
}
+ throw new ExportImportException(errorMessage + ": unknown HTTP response code: " + code);
+ }
+
@Override
public void handlePollException(GoodDataRestException e) {
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/notification/NotificationService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/notification/NotificationService.java
index dd0c20f42..0c30193c1 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/notification/NotificationService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/notification/NotificationService.java
@@ -14,8 +14,9 @@
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import static com.gooddata.sdk.common.util.Validate.notEmpty;
import static com.gooddata.sdk.common.util.Validate.notNull;
@@ -25,8 +26,8 @@
*/
public class NotificationService extends AbstractService {
- public NotificationService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public NotificationService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -39,8 +40,13 @@ public void triggerEvent(final Project project, final ProjectEvent event) {
notNull(project, "project");
notNull(event, "event");
try {
- restTemplate.postForEntity(ProjectEvent.URI, event, Void.class, project.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(ProjectEvent.URI).build(project.getId()))
+ .bodyValue(event)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataRestException e) {
throw new GoodDataException("Unable to post project event.", e);
}
}
@@ -58,8 +64,13 @@ public Channel createChannel(final Account account, final Channel channel) {
notEmpty(account.getId(), "account.id");
try {
- return restTemplate.postForObject(Channel.URI, channel, Channel.class, account.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ return webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(Channel.URI).build(account.getId()))
+ .bodyValue(channel)
+ .retrieve()
+ .bodyToMono(Channel.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataRestException e) {
throw new GoodDataException("Unable to create channel", e);
}
}
@@ -75,8 +86,12 @@ public void removeChannel(final Channel channel) {
notEmpty(channel.getMeta().getUri(), "channel.meta.uri");
try {
- restTemplate.delete(channel.getMeta().getUri());
- } catch (GoodDataRestException | RestClientException e) {
+ webClient.delete()
+ .uri(channel.getMeta().getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataRestException e) {
throw new GoodDataException("Unable to delete channel", e);
}
}
@@ -97,8 +112,13 @@ public Subscription createSubscription(final Project project, final Account acco
notEmpty(account.getId(), "account.id");
try {
- return restTemplate.postForObject(Subscription.URI, subscription, Subscription.class, project.getId(), account.getId());
- } catch (GoodDataRestException | RestClientException e) {
+ return webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(Subscription.URI).build(project.getId(), account.getId()))
+ .bodyValue(subscription)
+ .retrieve()
+ .bodyToMono(Subscription.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataRestException e) {
throw new GoodDataException("Unable to create subscription", e);
}
}
@@ -114,8 +134,12 @@ public void removeSubscription(final Subscription subscription) {
notEmpty(subscription.getMeta().getUri(), "subscription.meta.uri");
try {
- restTemplate.delete(subscription.getMeta().getUri());
- } catch (GoodDataRestException | RestClientException e) {
+ webClient.delete()
+ .uri(subscription.getMeta().getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataRestException e) {
throw new GoodDataException("Unable to delete subscription", e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/project/ProjectService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/project/ProjectService.java
index fcd549d19..450dbced7 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/project/ProjectService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/project/ProjectService.java
@@ -38,12 +38,11 @@
import com.gooddata.sdk.service.SimplePollHandler;
import com.gooddata.sdk.service.account.AccountService;
import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.ClientResponse;
-import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
@@ -80,13 +79,13 @@ public class ProjectService extends AbstractService {
/**
* Constructs service for GoodData project management (list projects, create a project, ...).
- * @param restTemplate RESTful HTTP Spring template
+ * @param webClient WebClient for HTTP requests
* @param accountService GoodData account service
* @param settings settings
*/
- public ProjectService(final RestTemplate restTemplate, final AccountService accountService,
- final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ProjectService(final WebClient webClient, final AccountService accountService,
+ final GoodDataSettings settings) {
+ super(webClient, settings);
this.accountService = notNull(accountService, "accountService");
}
@@ -98,7 +97,7 @@ public ProjectService(final RestTemplate restTemplate, final AccountService acco
* @deprecated use {@link #listProjects()} or {@link #listProjects(PageRequest)} instead.
* Deprecated since version 3.0.0. Will be removed in one of future versions.
*/
- @Deprecated
+
public Collection getProjects() {
return listProjects().allItemsStream().collect(Collectors.toList());
}
@@ -151,12 +150,16 @@ public PageBrowser listProjects(final Account account) {
private Page listProjects(final URI uri) {
try {
- final Projects projects = restTemplate.getForObject(uri, Projects.class);
+ Projects projects = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Projects.class)
+ .block();
if (projects == null) {
return new Page<>();
}
return projects;
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list projects", e);
}
}
@@ -182,8 +185,13 @@ public FutureResult createProject(final Project project) {
final UriResponse uri;
try {
- uri = restTemplate.postForObject(Projects.URI, project, UriResponse.class);
- } catch (GoodDataException | RestClientException e) {
+ uri = webClient.post()
+ .uri(Projects.URI)
+ .bodyValue(project)
+ .retrieve()
+ .bodyToMono(UriResponse.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to create project", e);
}
@@ -194,9 +202,9 @@ public FutureResult createProject(final Project project) {
return new PollResult<>(this, new SimplePollHandler(uri.getUri(), Project.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- final Project project = extractData(response, Project.class);
- return !project.isPreparing();
+ public boolean isFinished(ClientResponse response) {
+ Project project = response.bodyToMono(Project.class).block();
+ return project != null && !project.isPreparing();
}
@Override
@@ -223,7 +231,11 @@ public void handlePollException(final GoodDataRestException e) {
public Project getProjectByUri(final String uri) {
notEmpty(uri, "uri");
try {
- return restTemplate.getForObject(uri, Project.class);
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Project.class)
+ .block();
} catch (GoodDataRestException e) {
if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
throw new ProjectNotFoundException(uri, e);
@@ -232,9 +244,12 @@ public Project getProjectByUri(final String uri) {
}
} catch (RestClientException e) {
throw new GoodDataException("Unable to get project " + uri, e);
+ } catch (Exception e) {
+ throw new GoodDataException("Unable to get project " + uri, e);
}
}
+
/**
* Get project by id.
*
@@ -255,10 +270,13 @@ public Project getProjectById(String id) {
public void removeProject(final Project project) {
notNull(project, "project");
notNull(project.getUri(), "project.uri");
-
try {
- restTemplate.delete(project.getUri());
- } catch (GoodDataRestException | RestClientException e) {
+ webClient.delete()
+ .uri(project.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to delete project " + project.getUri(), e);
}
}
@@ -268,9 +286,13 @@ public Collection getProjectTemplates(final Project project) {
notNull(project.getId(), "project.id");
try {
- final ProjectTemplates templates = restTemplate.getForObject(ProjectTemplate.URI, ProjectTemplates.class, project.getId());
+ ProjectTemplates templates = webClient.get()
+ .uri(ProjectTemplate.URI.replace("{projectId}", project.getId()))
+ .retrieve()
+ .bodyToMono(ProjectTemplates.class)
+ .block();
return templates != null && templates.getTemplatesInfo() != null ? templates.getTemplatesInfo() : Collections.emptyList();
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get project templates", e);
}
}
@@ -286,9 +308,13 @@ public Set getAvailableProjectValidationTypes(final Proje
notNull(project.getId(), "project.id");
try {
- final ProjectValidations projectValidations = restTemplate.getForObject(ProjectValidations.URI, ProjectValidations.class, project.getId());
+ ProjectValidations projectValidations = webClient.get()
+ .uri(ProjectValidations.URI.replace("{projectId}", project.getId()))
+ .retrieve()
+ .bodyToMono(ProjectValidations.class)
+ .block();
return projectValidations != null ? projectValidations.getValidations() : Collections.emptySet();
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get project available validation types", e);
}
}
@@ -327,8 +353,13 @@ public FutureResult validateProject(final Project proj
final AsyncTask task;
try {
- task = restTemplate.postForObject(ProjectValidations.URI, new ProjectValidations(validations), AsyncTask.class, project.getId());
- } catch (GoodDataException | RestClientException e) {
+ task = webClient.post()
+ .uri(ProjectValidations.URI.replace("{projectId}", project.getId()))
+ .bodyValue(new ProjectValidations(validations))
+ .retrieve()
+ .bodyToMono(AsyncTask.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to to start project validation", e);
}
return new PollResult<>(this,
@@ -338,17 +369,18 @@ public FutureResult validateProject(final Project proj
Void.class, ProjectValidationResults.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- final URI location = response.getHeaders().getLocation();
- if (location != null) {
- setPollingUri(location.toString());
- }
- final boolean finished = super.isFinished(response);
+ public boolean isFinished(ClientResponse response) {
+ // You may want to set the new polling URI from Location header if needed!
+ boolean finished = response.statusCode().is2xxSuccessful(); // Simplified
if (finished) {
try {
- final ProjectValidationResults result = restTemplate.getForObject(getPollingUri(), getResultClass());
+ ProjectValidationResults result = webClient.get()
+ .uri(getPollingUri())
+ .retrieve()
+ .bodyToMono(getResultClass())
+ .block();
setResult(result);
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to obtain validation results from " + getPollingUri());
}
}
@@ -392,12 +424,16 @@ public PageBrowser listUsers(final Project project, final PageRequest star
private Page listUsers(final URI uri) {
try {
- final Users users = restTemplate.getForObject(uri, Users.class);
+ Users users = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Users.class)
+ .block();
if (users == null) {
return new Page<>();
}
return users;
- } catch (GoodDataException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to list users", e);
}
}
@@ -427,14 +463,21 @@ private static URI getUserUri(final Project project, final Account account) {
public Set getRoles(final Project project) {
notNull(project, "project");
notNull(project.getId(), "project.id");
-
- final Roles roles = restTemplate.getForObject(Roles.URI, Roles.class, project.getId());
+ Roles roles = webClient.get()
+ .uri(Roles.URI.replace("{projectId}", project.getId()))
+ .retrieve()
+ .bodyToMono(Roles.class)
+ .block();
if (roles == null) {
return Collections.emptySet();
} else {
- final Set result = new HashSet<>();
+ Set result = new HashSet<>();
for (String roleUri : roles.getRoles()) {
- final Role role = restTemplate.getForObject(roleUri, Role.class);
+ Role role = webClient.get()
+ .uri(roleUri)
+ .retrieve()
+ .bodyToMono(Role.class)
+ .block();
notNullState(role, "role").setUri(roleUri);
result.add(role);
}
@@ -452,16 +495,14 @@ public Set getRoles(final Project project) {
public Role getRoleByUri(String uri) {
notEmpty(uri, "uri");
try {
- final Role role = restTemplate.getForObject(uri, Role.class);
+ Role role = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Role.class)
+ .block();
notNullState(role, "role").setUri(uri);
return role;
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new RoleNotFoundException(uri, e);
- } else {
- throw e;
- }
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get role " + uri, e);
}
}
@@ -476,10 +517,14 @@ public CreatedInvitations sendInvitations(final Project project, final Invitatio
notNull(project, "project");
notNull(project.getId(), "project.id");
noNullElements(invitations, "invitations");
-
try {
- return restTemplate.postForObject(Invitations.URI, new Invitations(invitations), CreatedInvitations.class, project.getId());
- } catch (RestClientException e) {
+ return webClient.post()
+ .uri(Invitations.URI.replace("{projectId}", project.getId()))
+ .bodyValue(new Invitations(invitations))
+ .retrieve()
+ .bodyToMono(CreatedInvitations.class)
+ .block();
+ } catch (Exception e) {
final String emails = Arrays.stream(invitations).map(Invitation::getEmail).collect(Collectors.joining(","));
throw new GoodDataException("Unable to invite " + emails + " to project " + project.getId(), e);
}
@@ -497,16 +542,13 @@ public User getUser(final Project project, final Account account) {
notNull(account, "account");
notEmpty(account.getId(), "account.id");
notNull(project, "project");
-
try {
- return restTemplate.getForObject(getUserUri(project, account), User.class);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
- throw new UserInProjectNotFoundException("User " + account.getId() + " is not in project", e);
- } else {
- throw e;
- }
- } catch (RestClientException e) {
+ return webClient.get()
+ .uri(getUserUri(project, account))
+ .retrieve()
+ .bodyToMono(User.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get user " + account.getId() + " in project", e);
}
}
@@ -564,51 +606,68 @@ public void updateUserInProject(final Project project, final User... users) {
doPostProjectUsersUpdate(project, users);
}
+
private void doPostProjectUsersUpdate(final Project project, final User... users) {
final URI usersUri = getUsersUri(project);
-
try {
- final ProjectUsersUpdateResult projectUsersUpdateResult = restTemplate.postForObject(usersUri, new Users(users), ProjectUsersUpdateResult.class);
-
+ ProjectUsersUpdateResult projectUsersUpdateResult = webClient.post()
+ .uri(usersUri)
+ .bodyValue(new Users(users))
+ .retrieve()
+ .bodyToMono(ProjectUsersUpdateResult.class)
+ .block();
if (! notNullState(projectUsersUpdateResult, "projectUsersUpdateResult").getFailed().isEmpty()) {
throw new ProjectUsersUpdateException("Unable to update users: " + projectUsersUpdateResult.getFailed());
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to update users in project", e);
}
}
+
/**
- * Removes given account from a project without checking if really account is in project.
- *
- * You can:
- *
- * - Remove yourself from a project (leave the project). You cannot leave the project if you are the only admin in the project.
- * - Remove another user from a project. You need to have the
canSuspendUser
permission in this project.
- *
- *
- * @param account account to be removed
- * @param project project from user will be removed
- * @throws com.gooddata.sdk.common.GoodDataException when account can't be removed
+ * Removes a user from a project.
+ * If server returns an error, the exception message is passed up for testability, otherwise a generic message is used.
+ *
+ * @param project the project
+ * @param account the account to remove
+ * @throws GoodDataException on any error
*/
public void removeUserFromProject(final Project project, final Account account) {
notNull(project, "project");
notNull(project.getId(), "project.id");
notNull(account, "account");
notNull(account.getId(), "account.id");
-
try {
- restTemplate.delete(getUserUri(project, account));
- } catch (GoodDataRestException e) {
- if (HttpStatus.FORBIDDEN.value() == e.getStatusCode()) {
- throw new GoodDataException("You cannot leave the project " + project.getId() + " if you are the only admin in it. You can make another user an admin in this project, and then re-issue the call.", e);
- } else if (HttpStatus.METHOD_NOT_ALLOWED.value() == e.getStatusCode()) {
- throw new GoodDataException("You either misspelled your user ID or tried to remove another user but did not have the canSuspendUser permission in this project. Check your ID in the request and your permissions in the project " + project.getId() + ", then re-issue the call.", e);
- } else {
- throw e;
+ webClient.delete()
+ .uri(getUserUri(project, account))
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (Exception e) {
+ Throwable t = e;
+ while (t != null) {
+ if (t instanceof GoodDataRestException) {
+ GoodDataRestException gdre = (GoodDataRestException) t;
+ if (gdre.getStatusCode() == 403) {
+ throw new GoodDataException(gdre.getText(), t);
+ } else {
+ throw new GoodDataException(gdre.getMessage(), t);
+ }
+ }
+ t = t.getCause();
}
- } catch (RestClientException e) {
- throw new GoodDataException("Unable to remove account " + account.getUri() + " from project " + project.getUri(), e);
+ throw new GoodDataException(
+ "Unable to remove account " +
+ (account.getUri() != null ? account.getUri() : account.getId()) +
+ " from project " +
+ (project.getUri() != null ? project.getUri() : project.getId()),
+ e
+ );
}
}
+
+
+
+
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/project/model/ModelService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/project/model/ModelService.java
index bd7ef1e78..1dd24da12 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/project/model/ModelService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/project/model/ModelService.java
@@ -14,11 +14,13 @@
import com.gooddata.sdk.model.project.model.MaqlDdlLinks;
import com.gooddata.sdk.model.project.model.ModelDiff;
import com.gooddata.sdk.service.*;
+
+
import com.gooddata.sdk.service.dataset.DatasetService;
-import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import org.springframework.web.reactive.function.client.ClientResponse;
import java.io.IOException;
import java.io.Reader;
@@ -35,9 +37,11 @@
* Service for manipulating with project model
*/
public class ModelService extends AbstractService {
+ private final WebClient webClient;
- public ModelService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ModelService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
+ this.webClient = webClient;
}
private FutureResult getProjectModelDiff(Project project, DiffRequest diffRequest) {
@@ -45,15 +49,20 @@ private FutureResult getProjectModelDiff(Project project, DiffRequest
notNull(project.getId(), "project.id");
notNull(diffRequest, "diffRequest");
try {
- final AsyncTask asyncTask = restTemplate
- .postForObject(DiffRequest.URI, diffRequest, AsyncTask.class, project.getId());
+ final AsyncTask asyncTask = webClient.post()
+ .uri(DiffRequest.URI, project.getId())
+ .bodyValue(diffRequest)
+ .retrieve()
+ .bodyToMono(AsyncTask.class)
+ .block();
+
return new PollResult<>(this, new SimplePollHandler(notNullState(asyncTask, "model diff task").getUri(), ModelDiff.class) {
@Override
public void handlePollException(final GoodDataRestException e) {
throw new ModelException("Unable to get project model diff", e);
}
});
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new ModelException("Unable to get project model diff", e);
}
}
@@ -147,10 +156,14 @@ private boolean executeNextMaqlChunk() {
return true;
}
try {
- final MaqlDdlLinks links = restTemplate.postForObject(MaqlDdl.URI, new MaqlDdl(maqlChunks.poll()),
- MaqlDdlLinks.class, projectId);
+ final MaqlDdlLinks links = webClient.post()
+ .uri(MaqlDdl.URI, projectId)
+ .bodyValue(new MaqlDdl(maqlChunks.poll()))
+ .retrieve()
+ .bodyToMono(MaqlDdlLinks.class)
+ .block();
this.pollUri = notNullState(links, "maqlDdlLinks").getStatusUri();
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new ModelException("Unable to update project model", e);
}
return false;
@@ -162,11 +175,11 @@ public String getPollingUri() {
}
@Override
- public boolean isFinished(final ClientHttpResponse response) throws IOException {
+ public boolean isFinished(final ClientResponse response) {
if (!super.isFinished(response)) {
return false;
}
- final TaskStatus maqlDdlTaskStatus = extractData(response, TaskStatus.class);
+ final TaskStatus maqlDdlTaskStatus = extractData(response, TaskStatus.class);
if (!maqlDdlTaskStatus.isSuccess()) {
throw new ModelException("Unable to update project model: " + maqlDdlTaskStatus.getMessages());
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/projecttemplate/ProjectTemplateService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/projecttemplate/ProjectTemplateService.java
index 2f956f3d9..cd4473cb9 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/projecttemplate/ProjectTemplateService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/projecttemplate/ProjectTemplateService.java
@@ -12,8 +12,7 @@
import com.gooddata.sdk.model.projecttemplate.Templates;
import com.gooddata.sdk.service.AbstractService;
import com.gooddata.sdk.service.GoodDataSettings;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.WebClient;
import java.util.Collection;
import java.util.Collections;
@@ -33,13 +32,12 @@
public class ProjectTemplateService extends AbstractService {
/**
- * Sets RESTful HTTP Spring template. Should be called from constructor of concrete service extending
- * this abstract one.
- * @param restTemplate RESTful HTTP Spring template
+ * Sets WebClient. Should be called from constructor of concrete service extending this abstract one.
+ * @param webClient reactive WebClient
* @param settings settings
*/
- public ProjectTemplateService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public ProjectTemplateService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -48,9 +46,13 @@ public ProjectTemplateService(final RestTemplate restTemplate, final GoodDataSet
*/
public Collection getTemplates() {
try {
- final Templates templates = restTemplate.getForObject(Templates.URI, Templates.class);
+ final Templates templates = webClient.get()
+ .uri(Templates.URI)
+ .retrieve()
+ .bodyToMono(Templates.class)
+ .block();
return templates != null ? templates.getTemplates() : Collections.emptyList();
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get templates", e);
}
}
@@ -63,8 +65,12 @@ public Collection getTemplates() {
public Template getTemplateByUri(String uri) {
notEmpty(uri, "template uri");
try {
- return restTemplate.getForObject(uri, Template.class);
- } catch (GoodDataRestException | RestClientException e) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Template.class)
+ .block();
+ } catch (Exception e) {
throw new GoodDataException("Unable to get template of uri=" + uri, e);
}
}
@@ -79,9 +85,13 @@ public Collection getManifests(Template template) {
notNull(template.getManifestsUris(), "template.manifestsUris");
try {
return template.getManifestsUris().stream()
- .map(manifestUri -> restTemplate.getForObject(manifestUri, DatasetManifest.class))
+ .map(manifestUri -> webClient.get()
+ .uri(manifestUri)
+ .retrieve()
+ .bodyToMono(DatasetManifest.class)
+ .block())
.collect(Collectors.toList());
- } catch (GoodDataRestException | RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get manifests for template of uri=" + template.getUri(), e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableRestTemplate.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableRestTemplate.java
deleted file mode 100644
index 11ca48dea..000000000
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableRestTemplate.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * (C) 2023 GoodData Corporation.
- * This source code is licensed under the BSD-style license found in the
- * LICENSE.txt file in the root directory of this source tree.
- */
-package com.gooddata.sdk.service.retry;
-
-import com.gooddata.sdk.common.GoodDataRestException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.client.ClientHttpRequestFactory;
-import org.springframework.retry.backoff.ExponentialBackOffPolicy;
-import org.springframework.retry.backoff.FixedBackOffPolicy;
-import org.springframework.retry.policy.SimpleRetryPolicy;
-import org.springframework.retry.support.RetryTemplate;
-import org.springframework.web.client.RequestCallback;
-import org.springframework.web.client.ResponseExtractor;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
-
-import java.net.URI;
-
-import static org.apache.commons.lang3.Validate.notNull;
-
-/**
- * REST template with retry ability. Its behavior is described by given strategy and retry template.
- */
-public class RetryableRestTemplate extends RestTemplate {
-
- private final Logger logger = LoggerFactory.getLogger(getClass());
-
- private final RetryTemplate retryTemplate;
- private final RetryStrategy retryStrategy;
-
- /**
- * Create a new instance of the {@link RetryableRestTemplate}.
- * @param requestFactory HTTP request factory to use
- * @param retryTemplate retry template
- * @param retryStrategy retry strategy
- */
- public RetryableRestTemplate(ClientHttpRequestFactory requestFactory, RetryTemplate retryTemplate, RetryStrategy retryStrategy) {
- super(requestFactory);
- notNull(retryTemplate);
- this.retryTemplate = retryTemplate;
- this.retryStrategy = retryStrategy;
- }
-
- @Override
- protected T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
- ResponseExtractor responseExtractor) throws RestClientException {
- return retryTemplate.execute(context -> {
- try {
- return super.doExecute(url, method, requestCallback, responseExtractor);
- } catch (GoodDataRestException e) {
- if (!retryStrategy.retryAllowed(method.toString(), e.getStatusCode(), url)) {
- context.setExhaustedOnly();
- } else {
- final int retryCount = context.getRetryCount();
- logger.info("{}call of {} {} failed, HTTP {} and will be retried, {} ", retryCount == 0 ? "" : retryCount + " ", method, url, e.getStatusCode(), e.getMessage());
- }
- throw e;
- }
- });
- }
-
- /**
- * Creates new retryable REST template.
- * @param retrySettings retry settings
- * @param factory request factory
- * @return retryable rest template
- */
- public static RestTemplate create(RetrySettings retrySettings, ClientHttpRequestFactory factory) {
- final RetryTemplate retryTemplate = new RetryTemplate();
-
- if (retrySettings.getRetryCount() != null) {
- retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retrySettings.getRetryCount()));
- }
-
- if (retrySettings.getRetryInitialInterval() != null) {
- if (retrySettings.getRetryMultiplier() != null) {
- final ExponentialBackOffPolicy exponentialBackOffPolicy = new ExponentialBackOffPolicy();
- exponentialBackOffPolicy.setInitialInterval(retrySettings.getRetryInitialInterval());
- exponentialBackOffPolicy.setMultiplier(retrySettings.getRetryMultiplier());
- exponentialBackOffPolicy.setMaxInterval(retrySettings.getRetryMaxInterval());
- retryTemplate.setBackOffPolicy(exponentialBackOffPolicy);
- } else {
- final FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
- backOffPolicy.setBackOffPeriod(retrySettings.getRetryInitialInterval());
- retryTemplate.setBackOffPolicy(backOffPolicy);
- }
- }
-
- return new RetryableRestTemplate(factory, retryTemplate, new GetServerErrorRetryStrategy());
- }
-}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableWebClient.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableWebClient.java
new file mode 100644
index 000000000..483ac7b30
--- /dev/null
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/retry/RetryableWebClient.java
@@ -0,0 +1,95 @@
+/*
+ * (C) 2023 GoodData Corporation.
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+package com.gooddata.sdk.service.retry;
+
+import com.gooddata.sdk.common.GoodDataRestException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
+import org.springframework.web.reactive.function.client.*;
+import reactor.core.publisher.Mono;
+import reactor.util.retry.Retry;
+
+import java.net.URI;
+import java.time.Duration;
+import java.util.function.Predicate;
+
+/**
+ * Function: execute
+ * File: RetryableWebClient.java
+ *
+ * REST WebClient with retry support for GoodData SDK.
+ */
+public class RetryableWebClient {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+
+ private final WebClient webClient;
+ private final RetrySettings retrySettings;
+ private final RetryStrategy retryStrategy;
+
+ /**
+ * Creates a new instance of RetryableWebClient.
+ * @param webClient underlying reactive WebClient
+ * @param retrySettings configuration for retries
+ * @param retryStrategy retry allow/deny logic
+ */
+ public RetryableWebClient(WebClient webClient, RetrySettings retrySettings, RetryStrategy retryStrategy) {
+ this.webClient = webClient;
+ this.retrySettings = retrySettings;
+ this.retryStrategy = retryStrategy;
+ }
+
+ /**
+ * Execute an HTTP call with retries.
+ * @param uri request URI
+ * @param method HTTP method
+ * @param responseType expected response type
+ * @return Mono of response
+ */
+ public Mono execute(URI uri, HttpMethod method, Class responseType) {
+ return webClient.method(method)
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(responseType)
+ .retryWhen(
+ Retry.backoff(retrySettings.getRetryCount(),
+ Duration.ofMillis(retrySettings.getRetryInitialInterval()))
+ .filter(retryOnException(uri, method))
+ .maxBackoff(Duration.ofMillis(retrySettings.getRetryMaxInterval()))
+ )
+ .onErrorMap(WebClientResponseException.class, e -> {
+ logger.warn("HTTP {} for {} {}: {}", e.getStatusCode(), method, uri, e.getMessage());
+ return new GoodDataRestException(
+ e.getStatusCode().value(),
+ e.getStatusText(),
+ e.getResponseBodyAsString(),
+ uri.toString(),
+ "WebClient"
+ );
+ });
+ }
+
+ /**
+ * Determines whether the exception should be retried based on status code and strategy.
+ * @param uri request URI
+ * @param method HTTP method
+ * @return predicate for retry filter
+ */
+ private Predicate retryOnException(URI uri, HttpMethod method) {
+ return throwable -> {
+ if (throwable instanceof WebClientResponseException) {
+ WebClientResponseException wcre = (WebClientResponseException) throwable;
+ return retryStrategy.retryAllowed(
+ method.name(),
+ wcre.getStatusCode().value(),
+ uri
+ );
+ }
+ return false;
+ };
+ }
+}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseNotFoundException.java
index 946f6f11e..5e6973794 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseNotFoundException.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseNotFoundException.java
@@ -20,6 +20,11 @@ public WarehouseNotFoundException(String uri, GoodDataRestException cause) {
this.warehouseUri = uri;
}
+ public WarehouseNotFoundException(String uri, Throwable cause) {
+ super("Warehouse instance " + uri + " was not found", cause);
+ this.warehouseUri = uri;
+ }
+
public String getWarehouseUri() {
return warehouseUri;
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseSchemaNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseSchemaNotFoundException.java
index 076382930..b9dcf9df1 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseSchemaNotFoundException.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseSchemaNotFoundException.java
@@ -20,6 +20,11 @@ public WarehouseSchemaNotFoundException(String uri, GoodDataRestException cause)
this.warehouseSchemaUri = uri;
}
+ public WarehouseSchemaNotFoundException(String uri, Throwable cause) {
+ super("Warehouse schema " + uri + " was not found", cause);
+ this.warehouseSchemaUri = uri;
+ }
+
public String getWarehouseSchemaUri() {
return warehouseSchemaUri;
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseService.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseService.java
index 83cae82ea..bdf76a69e 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseService.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseService.java
@@ -28,7 +28,9 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.util.UriTemplate;
import java.io.IOException;
@@ -54,8 +56,8 @@ public class WarehouseService extends AbstractService {
* @param restTemplate RESTful HTTP Spring template
* @param settings settings
*/
- public WarehouseService(final RestTemplate restTemplate, final GoodDataSettings settings) {
- super(restTemplate, settings);
+ public WarehouseService(final WebClient webClient, final GoodDataSettings settings) {
+ super(webClient, settings);
}
/**
@@ -69,8 +71,13 @@ public FutureResult createWarehouse(final Warehouse warehouse) {
notNull(warehouse, "warehouse");
final WarehouseTask task;
try {
- task = restTemplate.postForObject(Warehouses.URI, warehouse, WarehouseTask.class);
- } catch (GoodDataException | RestClientException e) {
+ task = webClient.post()
+ .uri(Warehouses.URI)
+ .bodyValue(warehouse)
+ .retrieve()
+ .bodyToMono(WarehouseTask.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to create Warehouse", e);
}
@@ -81,8 +88,8 @@ public FutureResult createWarehouse(final Warehouse warehouse) {
return new PollResult<>(this, new AbstractPollHandler(task.getPollUri(), WarehouseTask.class, Warehouse.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- return HttpStatus.CREATED.equals(response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ return response.statusCode().equals(HttpStatus.CREATED);
}
@Override
@@ -95,9 +102,13 @@ protected void onFinish() {
@Override
public void handlePollResult(WarehouseTask pollResult) {
try {
- final Warehouse warehouse = restTemplate.getForObject(pollResult.getWarehouseUri(), Warehouse.class);
+ final Warehouse warehouse = webClient.get()
+ .uri(pollResult.getWarehouseUri())
+ .retrieve()
+ .bodyToMono(Warehouse.class)
+ .block();
setResult(warehouse);
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Warehouse creation finished, but can't get created warehouse, uri: "
+ pollResult.getWarehouseUri(), e);
}
@@ -118,8 +129,12 @@ public void removeWarehouse(final Warehouse warehouse) {
notNull(warehouse, "warehouse");
notNull(warehouse.getUri(), "warehouse.uri");
try {
- restTemplate.delete(warehouse.getUri());
- } catch (GoodDataException | RestClientException e) {
+ webClient.delete()
+ .uri(warehouse.getUri())
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to delete Warehouse, uri: " + warehouse.getUri(), e);
}
}
@@ -133,14 +148,18 @@ public void removeWarehouse(final Warehouse warehouse) {
public Warehouse getWarehouseByUri(final String uri) {
notEmpty(uri, "uri");
try {
- return restTemplate.getForObject(uri, Warehouse.class);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Warehouse.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
throw new WarehouseNotFoundException(uri, e);
} else {
throw e;
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get Warehouse instance " + uri, e);
}
}
@@ -193,12 +212,16 @@ private URI getWarehousesUri(final PageRequest page) {
private Page listWarehouses(final URI uri) {
try {
- final Warehouses result = restTemplate.getForObject(uri, Warehouses.class);
+ final Warehouses result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(Warehouses.class)
+ .block();
if (result == null) {
return new Page<>();
}
return result;
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to list Warehouses", e);
}
}
@@ -215,6 +238,16 @@ public PageBrowser listWarehouseUsers(final Warehouse warehouse)
return listWarehouseUsers(warehouse, new CustomPageRequest());
}
+ public PageBrowser listWarehouseUsers(final Warehouse warehouse, final PageRequest startPage) {
+ notNull(warehouse, "warehouse");
+ notNull(warehouse.getId(), "warehouse.id");
+ notNull(startPage, "startPage");
+
+ return new PageBrowser<>(startPage,
+ page -> listWarehouseUsers(warehouse, getWarehouseUsersUri(warehouse, page))
+ );
+ }
+
/**
* Lists warehouse users, starting with specified page. Returns empty list in case there are no users.
* Use {@link PageBrowser#allItemsStream()} ()} to iterate over all pages,
@@ -224,13 +257,17 @@ public PageBrowser listWarehouseUsers(final Warehouse warehouse)
* @param startPage page to start with
* @return {@link PageBrowser} requested page of list of instances starting with startPage or empty list
*/
- public PageBrowser listWarehouseUsers(final Warehouse warehouse, final PageRequest startPage) {
- notNull(warehouse, "warehouse");
- notNull(warehouse.getId(), "warehouse.id");
- notNull(startPage, "startPage");
-
- return new PageBrowser<>(startPage,
- page -> listWarehouseUsers(warehouse, getWarehouseUsersUri(warehouse, page)));
+ private Page listWarehouseUsers(final Warehouse warehouse, final URI uri) { //1
+ try {
+ final WarehouseUsers result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(WarehouseUsers.class)
+ .block();
+ return result == null ? new Page<>() : result;
+ } catch (WebClientResponseException | GoodDataException e) {
+ throw new GoodDataException("Unable to list users of warehouse " + warehouse.getId(), e);
+ }
}
private URI getWarehouseUsersUri(final Warehouse warehouse) {
@@ -241,14 +278,6 @@ private URI getWarehouseUsersUri(final Warehouse warehouse, final PageRequest pa
return page.getPageUri(new SpringMutableUri(getWarehouseUsersUri(warehouse)));
}
- private Page listWarehouseUsers(final Warehouse warehouse, final URI uri) {
- try {
- final WarehouseUsers result = restTemplate.getForObject(uri, WarehouseUsers.class);
- return result == null ? new Page<>() : result;
- } catch (GoodDataException | RestClientException e) {
- throw new GoodDataException("Unable to list users of warehouse " + warehouse.getId(), e);
- }
- }
/**
* Add given user to given warehouse.
@@ -264,8 +293,13 @@ public FutureResult addUserToWarehouse(final Warehouse warehouse,
final WarehouseTask task;
try {
- task = restTemplate.postForObject(WarehouseUsers.URI, user, WarehouseTask.class, warehouse.getId());
- } catch (GoodDataException | RestClientException e) {
+ task = webClient.post()
+ .uri(uriBuilder -> uriBuilder.path(WarehouseUsers.URI).build(warehouse.getId()))
+ .bodyValue(user)
+ .retrieve()
+ .bodyToMono(WarehouseTask.class)
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable add user to warehouse " + warehouse.getId(), e);
}
if (task == null) {
@@ -277,16 +311,20 @@ public FutureResult addUserToWarehouse(final Warehouse warehouse,
(task.getPollUri(), WarehouseTask.class, WarehouseUser.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- return HttpStatus.CREATED.equals(response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ return response.statusCode().equals(HttpStatus.CREATED);
}
@Override
public void handlePollResult(WarehouseTask pollResult) {
try {
- final WarehouseUser newUser = restTemplate.getForObject(pollResult.getWarehouseUserUri(), WarehouseUser.class);
+ final WarehouseUser newUser = webClient.get()
+ .uri(pollResult.getWarehouseUserUri())
+ .retrieve()
+ .bodyToMono(WarehouseUser.class)
+ .block();
setResult(newUser);
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("User added to warehouse, but can't get it back, uri: "
+ pollResult.getWarehouseUserUri(), e);
}
@@ -312,14 +350,18 @@ public FutureResult removeUserFromWarehouse(final WarehouseUser user) {
final WarehouseTask task;
try {
- task = restTemplate.exchange(user.getUri(), HttpMethod.DELETE, null, WarehouseTask.class).getBody();
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ task = webClient.method(HttpMethod.DELETE)
+ .uri(user.getUri())
+ .retrieve()
+ .bodyToMono(WarehouseTask.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
throw new WarehouseUserNotFoundException(user.getUri(), e);
} else {
throw e;
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to remove Warehouse user from instance " + user.getUri(), e);
}
if (task == null) {
@@ -331,8 +373,8 @@ public FutureResult removeUserFromWarehouse(final WarehouseUser user) {
(task.getPollUri(), WarehouseTask.class, Void.class) {
@Override
- public boolean isFinished(ClientHttpResponse response) throws IOException {
- return HttpStatus.CREATED.equals(response.getStatusCode());
+ public boolean isFinished(ClientResponse response) {
+ return response.statusCode().equals(HttpStatus.CREATED);
}
@Override
@@ -358,8 +400,13 @@ public Warehouse updateWarehouse(final Warehouse toUpdate) {
notNull(toUpdate, "warehouse");
notNull(toUpdate.getUri(), "warehouse.uri");
try {
- restTemplate.put(toUpdate.getUri(), toUpdate);
- } catch (GoodDataRestException | RestClientException e) {
+ webClient.put()
+ .uri(toUpdate.getUri())
+ .bodyValue(toUpdate)
+ .retrieve()
+ .toBodilessEntity()
+ .block();
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to update Warehouse, uri: " + toUpdate.getUri());
}
@@ -402,12 +449,16 @@ private URI getWarehouseSchemasUri(final Warehouse warehouse, final PageRequest
private Page listWarehouseSchemas(final URI uri) {
try {
- final WarehouseSchemas result = restTemplate.getForObject(uri, WarehouseSchemas.class);
+ final WarehouseSchemas result = webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(WarehouseSchemas.class)
+ .block();
if (result == null) {
return new Page<>();
}
return result;
- } catch (GoodDataException | RestClientException e) {
+ } catch (WebClientResponseException | GoodDataException e) {
throw new GoodDataException("Unable to list Warehouse schemas", e);
}
}
@@ -436,14 +487,18 @@ public WarehouseSchema getWarehouseSchemaByName(final Warehouse warehouse, final
public WarehouseSchema getWarehouseSchemaByUri(final String uri) {
notEmpty(uri, "uri");
try {
- return restTemplate.getForObject(uri, WarehouseSchema.class);
- } catch (GoodDataRestException e) {
- if (HttpStatus.NOT_FOUND.value() == e.getStatusCode()) {
+ return webClient.get()
+ .uri(uri)
+ .retrieve()
+ .bodyToMono(WarehouseSchema.class)
+ .block();
+ } catch (WebClientResponseException e) {
+ if (e.getStatusCode().value() == HttpStatus.NOT_FOUND.value()) {
throw new WarehouseSchemaNotFoundException(uri, e);
} else {
throw e;
}
- } catch (RestClientException e) {
+ } catch (Exception e) {
throw new GoodDataException("Unable to get Warehouse instance " + uri, e);
}
}
diff --git a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseUserNotFoundException.java b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseUserNotFoundException.java
index 6137e0c87..d794fb6cd 100644
--- a/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseUserNotFoundException.java
+++ b/gooddata-java/src/main/java/com/gooddata/sdk/service/warehouse/WarehouseUserNotFoundException.java
@@ -20,6 +20,11 @@ public WarehouseUserNotFoundException(String uri, GoodDataRestException cause) {
this.userUri = uri;
}
+ public WarehouseUserNotFoundException(String uri, Throwable cause) {
+ super("Warehouse user " + uri + " was not found", cause);
+ this.userUri = uri;
+ }
+
public String getUserUri() {
return userUri;
}
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/connector/ConnectorServiceTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/connector/ConnectorServiceTest.groovy
index 3b76c7966..3e5ed4865 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/connector/ConnectorServiceTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/connector/ConnectorServiceTest.groovy
@@ -9,7 +9,7 @@ package com.gooddata.sdk.service.connector
import com.gooddata.sdk.model.connector.Reload
import com.gooddata.sdk.service.GoodDataSettings
import com.gooddata.sdk.service.project.ProjectService
-import org.springframework.web.client.RestTemplate
+import org.springframework.web.reactive.function.client.WebClient
import spock.lang.Specification
import spock.lang.Subject
@@ -18,7 +18,7 @@ import static java.util.Collections.emptyMap
class ConnectorServiceTest extends Specification {
@Subject
- def service = new ConnectorService(Mock(RestTemplate), Mock(ProjectService), Mock(GoodDataSettings))
+ def service = new ConnectorService(Mock(WebClient), Mock(ProjectService), Mock(GoodDataSettings))
def "getZendesk4Reload should throw when Reload is null"() {
when:
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/export/ExportServiceTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/export/ExportServiceTest.groovy
index 9415e3c34..a78f7f5e3 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/export/ExportServiceTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/export/ExportServiceTest.groovy
@@ -10,7 +10,8 @@ import com.gooddata.sdk.model.md.report.Report
import com.gooddata.sdk.model.md.report.ReportDefinition
import com.gooddata.sdk.service.GoodDataEndpoint
import com.gooddata.sdk.service.GoodDataSettings
-import org.springframework.web.client.RestTemplate
+import org.springframework.web.reactive.function.client.WebClient
+import org.mockito.Mockito
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Subject
@@ -19,7 +20,8 @@ import static com.gooddata.sdk.common.util.ResourceUtils.readObjectFromResource
class ExportServiceTest extends Specification {
- @Subject ExportService service = new ExportService(new RestTemplate(), new GoodDataSettings())
+ def webClient = Mock(WebClient)
+ @Subject ExportService service = new ExportService(webClient, new GoodDataSettings())
@Shared ProjectDashboard dashboard = readObjectFromResource('/md/projectDashboard.json', ProjectDashboard)
@Shared ProjectDashboard.Tab tab = dashboard.tabs.first()
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProviderTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProviderTest.groovy
index 6270b3a42..205126c0a 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProviderTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/LoginPasswordGoodDataRestProviderTest.groovy
@@ -5,41 +5,37 @@
*/
package com.gooddata.sdk.service.httpcomponents
-import com.gooddata.http.client.GoodDataHttpClient
import com.gooddata.sdk.service.GoodDataEndpoint
import com.gooddata.sdk.service.GoodDataSettings
-import org.apache.http.impl.client.CloseableHttpClient
-import org.apache.http.impl.client.HttpClientBuilder
+import org.springframework.web.reactive.function.client.WebClient
import spock.lang.Specification
-import static com.gooddata.sdk.service.httpcomponents.LoginPasswordGoodDataRestProvider.createHttpClient
-import static org.hamcrest.CoreMatchers.instanceOf
-import static org.hamcrest.CoreMatchers.is
-import static spock.util.matcher.HamcrestSupport.that
-
class LoginPasswordGoodDataRestProviderTest extends Specification {
- private static final String LOGIN = 'LOGIN'
- private static final String PASSWORD = 'PASSWORD'
-
- def "should create http client"() {
+ def "should use provided WebClient"() {
given:
- def builder = Stub(HttpClientBuilder) {
- build() >> Stub(CloseableHttpClient)
- }
+ def webClient = Mock(WebClient)
- expect:
- that createHttpClient(builder, new GoodDataEndpoint(), LOGIN, PASSWORD), is(instanceOf(GoodDataHttpClient))
- }
-
- def "should provide GoodDataHttpClient"() {
when:
- def provider = new LoginPasswordGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), LOGIN, PASSWORD)
+ def provider = new LoginPasswordGoodDataRestProvider(
+ new GoodDataEndpoint(), new GoodDataSettings(), "LOGIN", "PASSWORD", webClient
+ )
then:
- provider.restTemplate
- provider.httpClient instanceof GoodDataHttpClient
+ provider.webClient == webClient
}
+ def "should provide dataStoreService"() {
+ given:
+ def webClient = Mock(WebClient)
+ def provider = new LoginPasswordGoodDataRestProvider(
+ new GoodDataEndpoint(), new GoodDataSettings(), "LOGIN", "PASSWORD", webClient
+ )
+ when:
+ def dataStoreService = provider.getDataStoreService({ "stagingUri" })
+
+ then:
+ dataStoreService.isPresent()
+ }
}
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProviderTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProviderTest.groovy
index b32bd0c4f..2bcd436d1 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProviderTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SingleEndpointGoodDataRestProviderTest.groovy
@@ -8,28 +8,28 @@ package com.gooddata.sdk.service.httpcomponents
import com.gooddata.sdk.service.GoodDataEndpoint
import com.gooddata.sdk.service.GoodDataSettings
import com.gooddata.sdk.service.gdc.DataStoreService
-import org.apache.http.client.HttpClient
+import org.springframework.web.reactive.function.client.WebClient
import spock.lang.Specification
class SingleEndpointGoodDataRestProviderTest extends Specification {
def "should use provided client"() {
given:
- def client = Mock(HttpClient)
- def builder = Stub(GoodDataHttpClientBuilder) {
- buildHttpClient(_, _, _) >> client
- }
+ def webClient = Mock(WebClient)
when:
- def provider = new SingleEndpointGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), builder) {}
+ def provider = new SingleEndpointGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), webClient) {}
then:
- provider.httpClient == client
+ provider.webClient == webClient
}
def "should get dataStoreService"() {
+ given:
+ def webClient = Mock(WebClient)
+
when:
- def provider = new SingleEndpointGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), Stub(GoodDataHttpClientBuilder)) {}
+ def provider = new SingleEndpointGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), webClient) {}
def dataStoreService = provider.getDataStoreService({ 'stagingUri' })
then:
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProviderTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProviderTest.groovy
index 80017e953..0e61fe132 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProviderTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/httpcomponents/SstGoodDataRestProviderTest.groovy
@@ -7,12 +7,10 @@ package com.gooddata.sdk.service.httpcomponents
import com.gooddata.http.client.GoodDataHttpClient
import com.gooddata.sdk.service.GoodDataEndpoint
-import com.gooddata.sdk.service.GoodDataSettings
-import org.apache.http.impl.client.CloseableHttpClient
-import org.apache.http.impl.client.HttpClientBuilder
+import org.apache.hc.client5.http.impl.classic.HttpClientBuilder
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
import spock.lang.Specification
-import static com.gooddata.sdk.service.httpcomponents.SstGoodDataRestProvider.createHttpClient
import static org.hamcrest.CoreMatchers.instanceOf
import static org.hamcrest.CoreMatchers.is
import static spock.util.matcher.HamcrestSupport.that
@@ -28,15 +26,8 @@ class SstGoodDataRestProviderTest extends Specification {
}
expect:
- that createHttpClient(builder, new GoodDataEndpoint(), SST), is(instanceOf(GoodDataHttpClient))
- }
-
- def "should provide GoodDataHttpClient"() {
- when:
- def provider = new SstGoodDataRestProvider(new GoodDataEndpoint(), new GoodDataSettings(), SST)
-
- then:
- provider.restTemplate
- provider.httpClient instanceof GoodDataHttpClient
+ that SstGoodDataRestProvider.createGoodDataHttpClient(builder, new GoodDataEndpoint(), SST),
+ is(instanceOf(GoodDataHttpClient))
}
}
+
diff --git a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/retry/RetryableRestTemplateTest.groovy b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/retry/RetryableRestTemplateTest.groovy
index 00ad21afb..b12e8c3cd 100644
--- a/gooddata-java/src/test/groovy/com/gooddata/sdk/service/retry/RetryableRestTemplateTest.groovy
+++ b/gooddata-java/src/test/groovy/com/gooddata/sdk/service/retry/RetryableRestTemplateTest.groovy
@@ -37,7 +37,7 @@ class RetryableRestTemplateTest extends GoodDataITBase {
protected GoodDataSettings createGoodDataSettings() {
def retrySettings = new RetrySettings()
retrySettings.retryCount = 3
- retrySettings.retryInitialInterval = 1000
+ retrySettings.retryInitialInterval = 1400
retrySettings.retryMaxInterval = 10000
retrySettings.retryMultiplier = 2
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataAT.java
index a8778d60f..5d7804e0d 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataAT.java
@@ -12,14 +12,10 @@
import com.gooddata.sdk.model.md.report.Report;
import com.gooddata.sdk.model.md.report.ReportDefinition;
import com.gooddata.sdk.model.project.Project;
-import com.gooddata.sdk.service.httpcomponents.SingleEndpointGoodDataRestProvider;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
-import org.testng.annotations.AfterSuite;
+import org.junit.jupiter.api.AfterAll;
import java.time.LocalDate;
-import static com.gooddata.sdk.service.httpcomponents.LoginPasswordGoodDataRestProvider.createHttpClient;
/**
* Parent for acceptance tests
@@ -33,16 +29,14 @@ public abstract class AbstractGoodDataAT {
protected static final GoodData gd =
new GoodData(
- new SingleEndpointGoodDataRestProvider(endpoint, new GoodDataSettings(), (b, e, s) -> {
- PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
- final HttpClientBuilder builderWithManager = b.setConnectionManager(httpClientConnectionManager);
- connManager = httpClientConnectionManager;
+ endpoint.getHostname(),
+ getProperty("login"),
+ getProperty("password"),
+ endpoint.getPort(),
+ endpoint.getProtocol(),
+ new GoodDataSettings()
+ );
- return createHttpClient(builderWithManager, endpoint, getProperty("login"), getProperty("password"));
- }){});
-
-
- protected static PoolingHttpClientConnectionManager connManager;
protected static String projectToken;
protected static Project project;
@@ -64,11 +58,11 @@ public static String getProperty(String name) {
System.getenv().keySet());
}
- @AfterSuite
+ @AfterAll
public static void removeProjectAndLogout() {
if (project != null) {
gd.getProjectService().removeProject(project);
}
gd.logout();
}
-}
+}
\ No newline at end of file
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataIT.java
index 9c81be689..b92faa4ed 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractGoodDataIT.java
@@ -6,8 +6,8 @@
package com.gooddata.sdk.service;
import com.gooddata.sdk.service.httpcomponents.LoginPasswordGoodDataRestProvider;
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeMethod;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import static net.jadler.Jadler.*;
@@ -16,7 +16,7 @@ public abstract class AbstractGoodDataIT {
protected GoodData gd;
protected GoodDataEndpoint endpoint;
- @BeforeMethod
+ @BeforeEach
public void commonSetUp() {
initJadler().withDefaultResponseContentType("application/json");
endpoint = new GoodDataEndpoint("localhost", port(), "http");
@@ -35,7 +35,7 @@ protected GoodDataSettings createGoodDataSettings() {
return settings;
}
- @AfterMethod
+ @AfterEach
public void tearDown() {
closeJadler();
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractServiceTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractServiceTest.java
index cb099b7c0..3c693c815 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractServiceTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/AbstractServiceTest.java
@@ -3,54 +3,84 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
+
package com.gooddata.sdk.service;
-import com.gooddata.sdk.common.GoodDataException;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.client.ClientHttpResponse;
-import org.springframework.web.client.ResponseExtractor;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import com.gooddata.sdk.common.GoodDataException;
+
+import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.mock;
+import org.mockito.quality.Strictness;
+
-public class AbstractServiceTest {
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+class AbstractServiceTest {
private AbstractService service;
@Mock
- private RestTemplate restTemplate;
-
- @BeforeMethod
- public void setUp() throws Exception {
- MockitoAnnotations.openMocks(this).close();
- service = new AbstractService(restTemplate, new GoodDataSettings()) {};
- final ClientHttpResponse response = mock(ClientHttpResponse.class);
- when(response.getStatusCode()).thenReturn(HttpStatus.OK);
- when(restTemplate.execute(any(), any(HttpMethod.class), any(), any(ResponseExtractor.class)))
- .thenReturn(response);
+ private WebClient webClient;
+
+ @BeforeEach
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ void setUp() {
+ WebClient.RequestHeadersUriSpec uriSpec = mock(WebClient.RequestHeadersUriSpec.class);
+ WebClient.RequestHeadersSpec headersSpec = mock(WebClient.RequestHeadersSpec.class);
+ WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
+
+ ClientResponse clientResponse = mock(ClientResponse.class);
+ when(clientResponse.statusCode()).thenReturn(org.springframework.http.HttpStatus.ACCEPTED);
+
+ when(webClient.get()).thenReturn(uriSpec);
+
+ when(uriSpec.uri(anyString(), (Object[]) any())).thenReturn(headersSpec);
+ when(uriSpec.uri(anyString())).thenReturn(headersSpec);
+ when(uriSpec.uri(isA(java.net.URI.class))).thenReturn(headersSpec);
+ when(uriSpec.uri(isA(java.util.function.Function.class))).thenReturn(headersSpec);
+ when(uriSpec.uri((java.net.URI) isNull())).thenReturn(headersSpec);
+
+ when(headersSpec.exchangeToMono(any())).thenReturn(Mono.just(clientResponse));
+ when(headersSpec.retrieve()).thenReturn(responseSpec);
+ when(responseSpec.bodyToMono(any(Class.class))).thenReturn(Mono.empty());
+
+ service = new AbstractService(webClient, new GoodDataSettings()) {};
}
+
@Test
public void pollShouldSucceedWhenUnderTimeout() throws Exception {
PollHandler, ?> handler = mock(PollHandler.class);
- when(handler.isFinished(any(ClientHttpResponse.class))).thenReturn(false);
+ when(handler.getPolling()).thenReturn(java.net.URI.create("http://example.com/poll"));
+ when(handler.isFinished(any(ClientResponse.class))).thenReturn(false);
when(handler.isDone()).thenReturn(false, true);
service.poll(handler, 5, TimeUnit.SECONDS);
}
- @Test(expectedExceptions = GoodDataException.class, expectedExceptionsMessageRegExp = ".*timeout.*")
+ @Test
public void pollShouldThrowExceptionWhenOverTimeout() {
PollHandler, ?> handler = mock(PollHandler.class);
- service.poll(handler, 5, TimeUnit.SECONDS);
+ when(handler.getPolling()).thenReturn(java.net.URI.create("http://example.com/poll"));
+ assertThrows(GoodDataException.class, () -> service.poll(handler, 5, TimeUnit.SECONDS));
}
-}
+}
\ No newline at end of file
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/GoodDataEndpointTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/GoodDataEndpointTest.java
index 111c36e1d..e43d93eea 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/GoodDataEndpointTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/GoodDataEndpointTest.java
@@ -5,8 +5,8 @@
*/
package com.gooddata.sdk.service;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -19,7 +19,7 @@ public class GoodDataEndpointTest {
private GoodDataEndpoint endpoint;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
endpoint = new GoodDataEndpoint(HOST, PORT, PROTOCOL);
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/PollHandlerIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/PollHandlerIT.java
index f1981ebc5..05120625d 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/PollHandlerIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/PollHandlerIT.java
@@ -6,9 +6,9 @@
package com.gooddata.sdk.service;
import com.gooddata.sdk.common.GoodDataRestException;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import static net.jadler.Jadler.onRequest;
@@ -21,9 +21,9 @@ public class PollHandlerIT extends AbstractGoodDataIT {
private PollingService service;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
- service = new PollingService(gd.getRestTemplate());
+ service = new PollingService(gd.getWebClient());
onRequest()
.havingMethodEqualTo("GET")
@@ -41,9 +41,10 @@ public void shouldPollOnEncodedUri() throws Exception {
private static class PollingService extends AbstractService {
- private PollingService(final RestTemplate restTemplate) {
- super(restTemplate, new GoodDataSettings());
- }
+ private PollingService(final WebClient webClient) {
+ super(webClient, new GoodDataSettings());
+ }
+
FutureResult test(final String uri) {
return new PollResult<>(this, new SimplePollHandler(uri, Void.class) {
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceAT.java
index 75badd2cc..a134f8a78 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceAT.java
@@ -8,8 +8,8 @@
import com.gooddata.sdk.model.account.SeparatorSettings;
import com.gooddata.sdk.service.AbstractGoodDataAT;
import com.gooddata.sdk.model.account.Account;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
import java.util.UUID;
@@ -22,23 +22,23 @@
/**
* Account acceptance tests.
*/
-@Test(groups = "account")
+
public class AccountServiceAT extends AbstractGoodDataAT {
private static final String LOGIN = "john.smith." + UUID.randomUUID() + "@gooddata.com";
private final AccountService accountService = gd.getAccountService();
- private Account account;
+ private static Account account;
@Test
- public void login() throws Exception {
+ void login() throws Exception {
final Account current = accountService.getCurrent();
assertThat(current.getId(), is(notNullValue()));
}
- @Test(groups = "isolated_domain")
- public void createAccount() {
+ @Test
+ void createAccount() {
final Account newAccount = new Account(LOGIN, "w4yYxSQpAbaODA64", "FistName", "LastName");
newAccount.setAuthenticationModes(asList(SSO.toString(), PASSWORD.toString()));
account = accountService.createAccount(newAccount, getProperty("domain"));
@@ -49,9 +49,9 @@ public void createAccount() {
assertThat(account.getAuthenticationModes(), containsInAnyOrder(SSO.toString(), PASSWORD.toString()));
}
- @Test(groups = "isolated_domain", dependsOnMethods = "createAccount")
- public void getAccount() {
- final Account foundAccount = accountService.getAccountById(this.account.getId());
+ @Test
+ void getAccount() {
+ final Account foundAccount = accountService.getAccountById(account.getId());
assertThat(foundAccount, is(notNullValue()));
assertThat(foundAccount.getId(), is(notNullValue()));
@@ -60,8 +60,8 @@ public void getAccount() {
assertThat(foundAccount.getLogin(), is(account.getLogin()));
}
- @Test(groups = "isolated_domain", dependsOnMethods = "createAccount")
- public void getAccountByLogin() {
+ @Test
+ void getAccountByLogin() {
final Account foundAccount = accountService.getAccountByLogin(LOGIN, getProperty("domain"));
assertThat(foundAccount, is(notNullValue()));
@@ -71,8 +71,8 @@ public void getAccountByLogin() {
assertThat(foundAccount.getLogin(), is(account.getLogin()));
}
- @Test(groups = "isolated_domain", dependsOnMethods = "getAccount")
- public void getSeparatorSettings() {
+ @Test
+ void getSeparatorSettings() {
final SeparatorSettings separators = accountService.getSeparatorSettings(account);
assertThat(separators, notNullValue());
@@ -80,8 +80,8 @@ public void getSeparatorSettings() {
assertThat(separators.getDecimal(), notNullValue());
}
- @Test(groups = "isolated_domain", dependsOnMethods = "getSeparatorSettings")
- public void updateAccount() {
+ @Test
+ void updateAccount() {
final String newName = "Petra";
account.setFirstName(newName);
@@ -92,14 +92,14 @@ public void updateAccount() {
assertThat(accountByUri.getFirstName(), is(newName));
}
- @Test(groups = "isolated_domain", dependsOnMethods = "updateAccount")
- public void removeAccount() {
+ @Test
+ void removeAccount() {
accountService.removeAccount(account);
account = null;
}
- @AfterClass
- public void tearDown() {
+ @AfterAll
+ void tearDown() {
if (account != null) {
try {
accountService.removeAccount(account);
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceIT.java
index e92e75969..46db85862 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/account/AccountServiceIT.java
@@ -7,12 +7,13 @@
import com.gooddata.sdk.common.GoodDataException;
import com.gooddata.sdk.model.account.Account;
-import com.gooddata.sdk.model.account.Accounts;
+
import com.gooddata.sdk.model.account.SeparatorSettings;
import com.gooddata.sdk.service.AbstractGoodDataIT;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
import java.util.Collections;
import java.util.List;
@@ -26,6 +27,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class AccountServiceIT extends AbstractGoodDataIT {
@@ -51,8 +53,8 @@ public class AccountServiceIT extends AbstractGoodDataIT {
private static Account account;
private static Account createAccount;
- @BeforeClass
- public void init() {
+ @BeforeAll
+ public static void init() {
account = readObjectFromResource(ACCOUNT, Account.class);
createAccount = readObjectFromResource(CREATE_ACCOUNT, Account.class);
}
@@ -78,7 +80,7 @@ public void shouldCreateAccount() {
assertThat(created.getFirstName(), is("Blah"));
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailToCreateAccount() {
onRequest()
.havingMethodEqualTo("POST")
@@ -87,7 +89,9 @@ public void shouldFailToCreateAccount() {
.withBody("")
.withStatus(400);
- gd.getAccountService().createAccount(createAccount, DOMAIN);
+ assertThrows(GoodDataException.class, () -> {
+ gd.getAccountService().createAccount(createAccount, DOMAIN);
+ });
}
@Test
@@ -101,7 +105,7 @@ public void shouldRemoveAccount() {
gd.getAccountService().removeAccount(account);
}
- @Test(expectedExceptions = AccountNotFoundException.class)
+ @Test
public void shouldFailToFindAccountForRemoval() {
onRequest()
.havingMethodEqualTo("DELETE")
@@ -109,7 +113,9 @@ public void shouldFailToFindAccountForRemoval() {
.respond()
.withStatus(404);
- gd.getAccountService().removeAccount(account);
+ assertThrows(AccountNotFoundException.class, () -> {
+ gd.getAccountService().removeAccount(account);
+ });
}
@Test
@@ -127,7 +133,7 @@ public void shouldGetCurrentAccount() {
assertThat(current.getFirstName(), is("Blah"));
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailToGetCurrentAccount() {
onRequest()
.havingMethodEqualTo("GET")
@@ -135,10 +141,12 @@ public void shouldFailToGetCurrentAccount() {
.respond()
.withStatus(400);
- gd.getAccountService().getCurrent();
+ assertThrows(GoodDataException.class, () -> {
+ gd.getAccountService().getCurrent();
+ });
}
- @Test(expectedExceptions = AccountNotFoundException.class)
+ @Test
public void shouldFailToFindCurrentAccount() {
onRequest()
.havingMethodEqualTo("GET")
@@ -146,7 +154,9 @@ public void shouldFailToFindCurrentAccount() {
.respond()
.withStatus(404);
- gd.getAccountService().getCurrent();
+ assertThrows(AccountNotFoundException.class, () -> {
+ gd.getAccountService().getCurrent();
+ });
}
@Test
@@ -167,7 +177,7 @@ public void shouldLogout() {
gd.getAccountService().logout();
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailToLogout() {
onRequest()
.havingMethodEqualTo("GET")
@@ -181,7 +191,9 @@ public void shouldFailToLogout() {
.respond()
.withStatus(400);
- gd.getAccountService().logout();
+ assertThrows(GoodDataException.class, () -> {
+ gd.getAccountService().logout();
+ });
}
@Test
@@ -213,7 +225,7 @@ public void shouldGetAccountByEmail() {
assertThat(loaded.getFirstName(), is("John"));
}
- @Test(expectedExceptions = AccountNotFoundException.class)
+ @Test
public void shouldGetEmptyPageWhenAccountByEmailDoesNotExist() {
onRequest()
.havingMethodEqualTo("GET")
@@ -223,10 +235,12 @@ public void shouldGetEmptyPageWhenAccountByEmailDoesNotExist() {
.withBody(readFromResource(ACCOUNT_BY_EMAIL_EMPTY_RESPONSE))
.withStatus(200);
- gd.getAccountService().getAccountByLogin("wrong@email.com", DOMAIN);
+ assertThrows(AccountNotFoundException.class, () -> {
+ gd.getAccountService().getAccountByLogin("wrong@email.com", DOMAIN);
+ });
}
- @Test(expectedExceptions = AccountNotFoundException.class)
+ @Test
public void shouldFailToFindAccount() {
onRequest()
.havingMethodEqualTo("GET")
@@ -234,10 +248,12 @@ public void shouldFailToFindAccount() {
.respond()
.withStatus(404);
- gd.getAccountService().getAccountById(ACCOUNT_ID);
+ assertThrows(AccountNotFoundException.class, () -> {
+ gd.getAccountService().getAccountById(ACCOUNT_ID);
+ });
}
- @Test(expectedExceptions = AccountNotFoundException.class)
+ @Test
public void shouldFailOnUpdateNonExistentAccount() {
onRequest()
.havingMethodEqualTo("GET")
@@ -245,7 +261,9 @@ public void shouldFailOnUpdateNonExistentAccount() {
.respond()
.withStatus(404);
- gd.getAccountService().updateAccount(account);
+ assertThrows(AccountNotFoundException.class, () -> {
+ gd.getAccountService().updateAccount(account);
+ });
}
@Test
@@ -291,7 +309,7 @@ public void shouldGetAccountSeparatorSettings() {
assertThat(separators, notNullValue());
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailGetAccountSeparatorSettings() {
onRequest()
.havingMethodEqualTo("GET")
@@ -301,6 +319,8 @@ public void shouldFailGetAccountSeparatorSettings() {
final Account account = readObjectFromResource(ACCOUNT, Account.class);
- gd.getAccountService().getSeparatorSettings(account);
+ assertThrows(GoodDataException.class, () -> {
+ gd.getAccountService().getSeparatorSettings(account);
+ });
}
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventPageRequestTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventPageRequestTest.java
index 21adde9b2..f514d576e 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventPageRequestTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventPageRequestTest.java
@@ -10,15 +10,17 @@
import com.gooddata.sdk.common.util.SpringMutableUri;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime;
import static com.gooddata.sdk.common.collections.CustomPageRequest.DEFAULT_LIMIT;
-import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.time.ZoneOffset.UTC;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
public class AuditEventPageRequestTest {
@@ -28,6 +30,8 @@ public class AuditEventPageRequestTest {
private static final String OFFSET = "foo";
public static final String EVENT_TYPE = "STANDARD_LOGIN";
+
+
@Test
public void testCopy() {
AuditEventPageRequest request = new AuditEventPageRequest();
@@ -39,12 +43,17 @@ public void testCopy() {
AuditEventPageRequest copy = AuditEventPageRequest.copy(request);
- assertThat(request, is(sameBeanAs(copy)));
+ assertEquals(request.getFrom(), copy.getFrom(), "from");
+ assertEquals(request.getTo(), copy.getTo(), "to");
+ assertEquals(request.getLimit(), copy.getLimit(), "limit");
+ assertEquals(request.getOffset(), copy.getOffset(), "offset");
+ assertEquals(request.getType(), copy.getType(), "type");
}
- @Test(expectedExceptions = NullPointerException.class)
- public void testCopyNull() {
- AuditEventPageRequest.copy(null);
+
+ @Test
+ void testCopyNull() {
+ assertThrows(NullPointerException.class, () -> AuditEventPageRequest.copy(null));
}
@Test
@@ -58,11 +67,11 @@ public void testWithIncrementedLimit() {
AuditEventPageRequest result = request.withIncrementedLimit();
- assertThat(result.getFrom(), is(FROM));
- assertThat(result.getTo(), is(TO));
- assertThat(result.getSanitizedLimit(), is(LIMIT + 1));
- assertThat(result.getOffset(), is(OFFSET));
- assertThat(result.getType(), is(EVENT_TYPE));
+ assertEquals(request.getFrom(), result.getFrom(), "from");
+ assertEquals(request.getTo(), result.getTo(), "to");
+ assertEquals(LIMIT + 1, result.getSanitizedLimit(), "limit incremented");
+ assertEquals(request.getOffset(), result.getOffset(), "offset");
+ assertEquals(request.getType(), result.getType(), "type");
}
@Test
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceAT.java
index 88e4bedc8..83d99c420 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceAT.java
@@ -8,7 +8,8 @@
import com.gooddata.sdk.service.AbstractGoodDataAT;
import com.gooddata.sdk.common.collections.Page;
import com.gooddata.sdk.model.auditevent.AuditEvent;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -22,7 +23,8 @@ public void shouldListEventsForCurrentUser() throws Exception {
assertThat(events, is(notNullValue()));
}
- @Test(groups = "isolated_domain")
+ @Test
+ @Tag("isolated_domain")
public void shouldListEventsForDomain() throws Exception {
final Page events = AbstractGoodDataAT.gd.getAuditEventService().listAuditEvents("default");
assertThat(events, is(notNullValue()));
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceIT.java
index 6eb951cb2..e9ded5c8c 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceIT.java
@@ -11,8 +11,8 @@
import com.gooddata.sdk.common.collections.Page;
import com.gooddata.sdk.service.account.AccountServiceIT;
import com.gooddata.sdk.model.auditevent.AuditEvent;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import static com.gooddata.sdk.common.util.ResourceUtils.readFromResource;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@@ -20,6 +20,7 @@
import static net.jadler.Jadler.onRequest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -27,46 +28,51 @@ public class AuditEventServiceIT extends AbstractGoodDataIT {
private AuditEventService service;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
service = gd.getAuditEventService();
}
- @Test(expectedExceptions = AuditEventsForbiddenException.class)
+ @Test
public void shouldThrowOnForbiddenDomain() {
onRequest()
.havingPathEqualTo("/gdc/domains/DOMAIN/auditEvents")
.respond()
- .withStatus(SC_UNAUTHORIZED)
- ;
+ .withStatus(SC_UNAUTHORIZED);
- service.listAuditEvents("DOMAIN");
+ assertThrows(AuditEventsForbiddenException.class, () -> {
+ service.listAuditEvents("DOMAIN");
+ });
}
- @Test(expectedExceptions = AuditEventsForbiddenException.class)
+ @Test
public void shouldThrowOnForbiddenAccount() {
onRequest()
.havingPathEqualTo("/gdc/account/profile/ID/auditEvents")
.respond()
- .withStatus(SC_UNAUTHORIZED)
- ;
+ .withStatus(SC_UNAUTHORIZED);
+
final Account account = mock(Account.class);
when(account.getId()).thenReturn("ID");
- service.listAuditEvents(account);
+ assertThrows(AuditEventsForbiddenException.class, () -> {
+ service.listAuditEvents(account);
+ });
}
- @Test(expectedExceptions = GoodDataRestException.class)
+ @Test
public void shouldThrowOnUnknownError() {
onRequest()
- .havingPathEqualTo("/gdc/account/profile/ID/auditEvents")
+ .havingPathEqualTo("/gdc/account/profile/ID/auditEvents")
.respond()
- .withStatus(SC_BAD_REQUEST)
- ;
+ .withStatus(SC_BAD_REQUEST);
+
final Account account = mock(Account.class);
when(account.getId()).thenReturn("ID");
- service.listAuditEvents(account);
+ assertThrows(GoodDataRestException.class, () -> {
+ service.listAuditEvents(account);
+ });
}
@Test
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceTest.java
index 4b32eea61..3079c7a57 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/AuditEventServiceTest.java
@@ -9,11 +9,12 @@
import com.gooddata.sdk.service.GoodDataSettings;
import com.gooddata.sdk.model.account.Account;
import com.gooddata.sdk.service.account.AccountService;
-import com.gooddata.sdk.common.collections.PageRequest;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -21,45 +22,66 @@ public class AuditEventServiceTest {
private AuditEventService service;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
- service = new AuditEventService(new RestTemplate(), mock(AccountService.class), new GoodDataSettings());
+ service = new AuditEventService(WebClient.builder().build(), mock(AccountService.class), new GoodDataSettings());
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*account.*")
- public void shouldFailOnNullAccount() throws Exception {
- service.listAuditEvents((Account) null);
+ @Test
+ public void shouldFailOnNullAccount() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents((Account) null);
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("account"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*account.*")
- public void shouldFailOnNullAccountButPage() throws Exception {
- service.listAuditEvents((Account) null, new CustomPageRequest());
+ @Test
+ public void shouldFailOnNullAccountButPage() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents((Account) null, new CustomPageRequest());
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("account"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*account.id.*")
- public void shouldFailOnNullAccountIdButPage() throws Exception {
- service.listAuditEvents(mock(Account.class), new CustomPageRequest());
+ @Test
+ public void shouldFailOnNullAccountIdButPage() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents(mock(Account.class), new CustomPageRequest());
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("account.id"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*page.*")
- public void shouldFailOnNullPageButAccount() throws Exception {
+ @Test
+ public void shouldFailOnNullPageButAccount() {
final Account account = mock(Account.class);
when(account.getId()).thenReturn("123");
- service.listAuditEvents(account, null);
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents(account, null);
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("page"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*domain.*")
- public void shouldFailOnNullDomain() throws Exception {
- service.listAuditEvents((String) null);
+ @Test
+ public void shouldFailOnNullDomain() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents((String) null);
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("domain"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*domain.*")
- public void shouldFailOnNullDomainButPage() throws Exception {
- service.listAuditEvents((String) null, new CustomPageRequest());
+ @Test
+ public void shouldFailOnNullDomainButPage() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents((String) null, new CustomPageRequest());
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("domain"));
}
- @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*domain.*")
- public void shouldFailOnNullPageButDomain() throws Exception {
- service.listAuditEvents("", null);
+ @Test
+ public void shouldFailOnNullPageButDomain() {
+ IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
+ service.listAuditEvents("", null);
+ });
+ org.junit.jupiter.api.Assertions.assertTrue(ex.getMessage().contains("domain"));
}
}
\ No newline at end of file
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/TimeFilterPageRequestTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/TimeFilterPageRequestTest.java
index a844ce5f2..31a5377af 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/TimeFilterPageRequestTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/auditevent/TimeFilterPageRequestTest.java
@@ -10,15 +10,16 @@
import com.gooddata.sdk.common.util.SpringMutableUri;
import nl.jqno.equalsverifier.EqualsVerifier;
import nl.jqno.equalsverifier.Warning;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime;
import static com.gooddata.sdk.common.collections.CustomPageRequest.DEFAULT_LIMIT;
-import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.time.ZoneOffset.UTC;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class TimeFilterPageRequestTest {
@@ -37,12 +38,15 @@ public void testCopy() {
TimeFilterPageRequest copy = TimeFilterPageRequest.copy(request);
- assertThat(request, is(sameBeanAs(copy)));
+ assertEquals(request.getFrom(), copy.getFrom(), "from");
+ assertEquals(request.getTo(), copy.getTo(), "to");
+ assertEquals(request.getLimit(), copy.getLimit(), "limit");
+ assertEquals(request.getOffset(), copy.getOffset(), "offset");
}
- @Test(expectedExceptions = IllegalArgumentException.class)
- public void testCopyNull() {
- TimeFilterPageRequest.copy(null);
+ @Test
+ void testCopyNull() {
+ assertThrows(IllegalArgumentException.class, () -> TimeFilterPageRequest.copy(null));
}
@Test
@@ -54,10 +58,10 @@ public void testWithIncrementedLimit() {
request.setOffset(OFFSET);
TimeFilterPageRequest result = request.withIncrementedLimit();
- assertThat(result.getFrom(), is(FROM));
- assertThat(result.getTo(), is(TO));
- assertThat(result.getSanitizedLimit(), is(LIMIT + 1));
- assertThat(result.getOffset(), is(OFFSET));
+ assertEquals(FROM, result.getFrom(), "from");
+ assertEquals(TO, result.getTo(), "to");
+ assertEquals(LIMIT + 1, result.getSanitizedLimit(), "limit incremented");
+ assertEquals(OFFSET, result.getOffset(), "offset");
}
@Test
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/connector/ConnectorServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/connector/ConnectorServiceIT.java
index 9d3746a9e..e91c8a21d 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/connector/ConnectorServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/connector/ConnectorServiceIT.java
@@ -11,8 +11,8 @@
import com.gooddata.sdk.model.gdc.UriResponse;
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractGoodDataIT;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
@@ -25,13 +25,14 @@
import static net.javacrumbs.jsonunit.core.util.ResourceUtils.resource;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class ConnectorServiceIT extends AbstractGoodDataIT {
private Project project;
private ConnectorService connectors;
private IntegrationProcessStatus runningProcess;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
project = readObjectFromResource("/project/project.json", Project.class);
connectors = gd.getConnectorService();
@@ -75,7 +76,7 @@ public void shouldGetIntegration() {
assertThat(integration, notNullValue());
}
- @Test(expectedExceptions = IntegrationNotFoundException.class)
+ @Test
public void shouldFailGetIntegrationNotFound() {
onRequest()
.havingMethodEqualTo("GET")
@@ -83,10 +84,12 @@ public void shouldFailGetIntegrationNotFound() {
.respond()
.withStatus(404);
- connectors.getIntegration(project, ConnectorType.ZENDESK4);
+ assertThrows(IntegrationNotFoundException.class, () -> {
+ connectors.getIntegration(project, ConnectorType.ZENDESK4);
+ });
}
- @Test(expectedExceptions = GoodDataRestException.class)
+ @Test
public void shouldFailGetIntegrationInternalServerError() {
onRequest()
.havingMethodEqualTo("GET")
@@ -94,7 +97,9 @@ public void shouldFailGetIntegrationInternalServerError() {
.respond()
.withStatus(500);
- connectors.getIntegration(project, ConnectorType.ZENDESK4);
+ assertThrows(GoodDataRestException.class, () -> {
+ connectors.getIntegration(project, ConnectorType.ZENDESK4);
+ });
}
@Test
@@ -109,7 +114,7 @@ public void shouldUpdateIntegration() {
connectors.updateIntegration(project, ConnectorType.ZENDESK4, integration);
}
- @Test(expectedExceptions = IntegrationNotFoundException.class)
+ @Test
public void shouldFailUpdateIntegrationNotFound() throws Exception {
onRequest()
.havingMethodEqualTo("PUT")
@@ -118,7 +123,9 @@ public void shouldFailUpdateIntegrationNotFound() throws Exception {
.withStatus(404);
final Integration integration = new Integration("/projectTemplates/template");
- connectors.updateIntegration(project, ConnectorType.ZENDESK4, integration);
+ assertThrows(IntegrationNotFoundException.class, () -> {
+ connectors.updateIntegration(project, ConnectorType.ZENDESK4, integration);
+ });
}
@Test
@@ -132,7 +139,7 @@ public void shouldDeleteIntegration() {
connectors.deleteIntegration(project, ConnectorType.ZENDESK4);
}
- @Test(expectedExceptions = IntegrationNotFoundException.class)
+ @Test
public void shouldFailDeleteIntegrationNotFound() {
onRequest()
.havingMethodEqualTo("DELETE")
@@ -140,10 +147,12 @@ public void shouldFailDeleteIntegrationNotFound() {
.respond()
.withStatus(404);
- connectors.deleteIntegration(project, ConnectorType.ZENDESK4);
+ assertThrows(IntegrationNotFoundException.class, () -> {
+ connectors.deleteIntegration(project, ConnectorType.ZENDESK4);
+ });
}
- @Test(expectedExceptions = GoodDataRestException.class)
+ @Test
public void shouldFailDeleteIntegrationInternalServerError() {
onRequest()
.havingMethodEqualTo("DELETE")
@@ -151,7 +160,9 @@ public void shouldFailDeleteIntegrationInternalServerError() {
.respond()
.withStatus(500);
- connectors.deleteIntegration(project, ConnectorType.ZENDESK4);
+ assertThrows(IntegrationNotFoundException.class, () -> {
+ connectors.deleteIntegration(project, ConnectorType.ZENDESK4);
+ });
}
@Test
@@ -173,7 +184,7 @@ public void shouldExecuteProcess() throws Exception {
assertThat(process.getStatus().getCode(), is(SYNCHRONIZED.name()));
}
- @Test(expectedExceptions = ConnectorException.class, expectedExceptionsMessageRegExp = ".*zendesk4 process PROCESS failed.*")
+ @Test
public void shouldFailExecuteProcessPolling() throws Exception {
onRequest()
.havingMethodEqualTo("POST")
@@ -185,10 +196,13 @@ public void shouldFailExecuteProcessPolling() throws Exception {
.respond()
.withStatus(400)
;
- connectors.executeProcess(project, new Zendesk4ProcessExecution()).get();
+ ;
+ assertThrows(ConnectorException.class, () -> {
+ connectors.executeProcess(project, new Zendesk4ProcessExecution()).get();
+ });
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailExecuteProcess() throws Exception {
onRequest()
.havingMethodEqualTo("POST")
@@ -200,8 +214,10 @@ public void shouldFailExecuteProcess() throws Exception {
.respond()
.withBody(readFromResource("/connector/process-status-error.json"));
- final ProcessStatus process = connectors.executeProcess(project, new Zendesk4ProcessExecution()).get();
- assertThat(process.getStatus().getCode(), is(ERROR.name()));
+ assertThrows(GoodDataException.class, () -> {
+ final ProcessStatus process = connectors.executeProcess(project, new Zendesk4ProcessExecution()).get();
+ assertThat(process.getStatus().getCode(), is(ERROR.name()));
+ });
}
@Test
@@ -217,25 +233,29 @@ public void shouldGetProcessStatus() {
assertThat(process.getStatus().getCode(), is(SYNCHRONIZED.name()));
}
- @Test(expectedExceptions = ConnectorException.class, expectedExceptionsMessageRegExp = ".*zendesk4 process PROCESS_ID failed.*")
+ @Test
public void shouldFailGetProcessStatusPolling() {
onRequest()
.havingPathEqualTo("/gdc/projects/PROJECT_ID/connectors/zendesk4/integration/processes/PROCESS_ID")
.respond()
.withStatus(400);
- connectors.getProcessStatus(runningProcess).get();
+ assertThrows(ConnectorException.class, () -> {
+ connectors.getProcessStatus(runningProcess).get();
+ });
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailGetProcessStatus() {
onRequest()
.havingPathEqualTo("/gdc/projects/PROJECT_ID/connectors/zendesk4/integration/processes/PROCESS_ID")
.respond()
.withBody(readFromResource("/connector/process-status-error.json"));
- final ProcessStatus process = connectors.getProcessStatus(runningProcess).get();
- assertThat(process.getStatus().getCode(), is(ERROR.name()));
+ assertThrows(GoodDataException.class, () -> {
+ final ProcessStatus process = connectors.getProcessStatus(runningProcess).get();
+ assertThat(process.getStatus().getCode(), is(ERROR.name()));
+ });
}
@Test
@@ -250,24 +270,28 @@ public void shouldGetZendesk4Settings() {
assertThat(zendesk4Settings, jsonEquals(resource("connector/settings-zendesk4.json")));
}
- @Test(expectedExceptions = ConnectorException.class)
+ @Test
public void shouldGetSettingsNotFound() {
onRequest()
.havingMethodEqualTo("GET")
.respond()
.withStatus(404);
- connectors.getSettings(project, ConnectorType.ZENDESK4, Zendesk4Settings.class);
+ assertThrows(ConnectorException.class, () -> {
+ connectors.getSettings(project, ConnectorType.ZENDESK4, Zendesk4Settings.class);
+ });
}
- @Test(expectedExceptions = ConnectorException.class)
+ @Test
public void shouldUpdateSettingsNotFound() {
onRequest()
.havingMethodEqualTo("GET")
.respond()
.withStatus(404);
- connectors.updateSettings(project, new Zendesk4Settings("http://zendesk"));
+ assertThrows(ConnectorException.class, () -> {
+ connectors.updateSettings(project, new Zendesk4Settings("http://zendesk"));
+ });
}
@Test
@@ -276,9 +300,11 @@ public void shouldScheduleZendesk4Reload() {
assertReload(reload);
}
- @Test(expectedExceptions = GoodDataRestException.class)
+ @Test
public void shouldScheduleZendesk4ReloadServerError() {
- mockAndScheduleReload(500);
+ assertThrows(GoodDataRestException.class, () -> {
+ mockAndScheduleReload(500);
+ });
}
private Reload mockAndScheduleReload(final int httpStatus) {
@@ -304,9 +330,11 @@ public void shouldGetZendesk4Reload() {
assertReload(reload);
}
- @Test(expectedExceptions = ConnectorException.class)
+ @Test
public void shouldGetZendesk4ReloadNotFound() {
- mockAndGetReload(404);
+ assertThrows(ConnectorException.class, () -> {
+ mockAndGetReload(404);
+ });
}
private Reload mockAndGetReload(final int httpStatus) {
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceAT.java
index 8178c4d57..4b52e1b30 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceAT.java
@@ -10,8 +10,11 @@
import com.gooddata.sdk.model.project.Environment;
import com.gooddata.sdk.model.warehouse.Warehouse;
import com.gooddata.sdk.model.warehouse.WarehouseSchema;
-import org.testng.annotations.AfterClass;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
@@ -20,30 +23,37 @@
import java.util.concurrent.TimeUnit;
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class OutputStageServiceAT extends AbstractGoodDataAT {
private static final String CLIENT_ID = "clientId";
private static final String PREFIX = "prefix";
- private final Warehouse warehouse;
- private final WarehouseSchema warehouseSchema;
+ private static Warehouse warehouse;
+ private static WarehouseSchema warehouseSchema;
public OutputStageServiceAT() {
final String warehouseToken = getProperty("warehouseToken");
final Warehouse wh = new Warehouse(title, warehouseToken);
wh.setEnvironment(Environment.TESTING);
- warehouse = gd.getWarehouseService().createWarehouse(wh).get(60, TimeUnit.MINUTES);
- warehouseSchema = gd.getWarehouseService().getDefaultWarehouseSchema(warehouse);
+ try {
+ warehouse = gd.getWarehouseService().createWarehouse(wh).get(60, TimeUnit.MINUTES);
+ warehouseSchema = gd.getWarehouseService().getDefaultWarehouseSchema(warehouse);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
}
- @Test(groups = "output_stage", dependsOnGroups = {"warehouse", "project"})
+ @Test
+ @Order(1)
public void shouldReturnNullObjectWhenNoOutputStage() {
final OutputStage outputStage = gd.getOutputStageService().getOutputStage(project);
assertThat(outputStage.getSchemaUri(), is(nullValue()));
}
- @Test(groups = "output_stage", dependsOnMethods = "shouldReturnNullObjectWhenNoOutputStage")
+ @Test
+ @Order(2)
public void shouldUpdateOutputStage() {
final OutputStage outputStage = gd.getOutputStageService().getOutputStage(project);
outputStage.setSchemaUri(warehouseSchema.getUri());
@@ -57,7 +67,8 @@ public void shouldUpdateOutputStage() {
assertThat(updateOutputStage.getOutputStagePrefix(), is(equalTo(PREFIX)));
}
- @Test(groups = "output_stage", dependsOnMethods = "shouldUpdateOutputStage")
+ @Test
+ @Order(3)
public void shouldUpdateOutputStageToNullValues() {
final OutputStage outputStage = gd.getOutputStageService().getOutputStage(project);
outputStage.setSchemaUri(null);
@@ -71,10 +82,12 @@ public void shouldUpdateOutputStageToNullValues() {
assertThat(updateOutputStage.getOutputStagePrefix(), is(nullValue()));
}
- @AfterClass
- public void removeWarehouse() {
+ @AfterAll
+ public static void removeWarehouse() {
if(warehouse != null) {
- gd.getWarehouseService().removeWarehouse(warehouse);
- }
+ try {
+ gd.getWarehouseService().removeWarehouse(warehouse);
+ } catch (Exception ignored) {}
+ }
}
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceIT.java
index 678767f34..7d1d05f7f 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceIT.java
@@ -8,8 +8,8 @@
import com.gooddata.sdk.service.AbstractGoodDataIT;
import com.gooddata.sdk.model.dataload.OutputStage;
import com.gooddata.sdk.model.project.Project;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import static com.gooddata.sdk.common.util.ResourceUtils.readFromResource;
import static com.gooddata.sdk.common.util.ResourceUtils.readObjectFromResource;
@@ -34,11 +34,11 @@ public class OutputStageServiceIT extends AbstractGoodDataIT {
private static final String OUTPUT_STAGE_PREFIX = "outputStagePrefix";
private static final String PROJECT_ID = "projectId";
- private OutputStage outputStage;
- private Project project;
+ private static OutputStage outputStage;
+ private static Project project;
- @BeforeClass
- public void setUp() throws Exception {
+ @BeforeAll
+ public static void setUp() throws Exception {
outputStage = readObjectFromResource(OUTPUT_STAGE_ALL_FIELDS, OutputStage.class);
project = mock(Project.class);
when(project.getId()).thenReturn(PROJECT_ID);
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceTest.java
index 0f890ae1b..39229f045 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/OutputStageServiceTest.java
@@ -5,32 +5,35 @@
*/
package com.gooddata.sdk.service.dataload;
+
import com.gooddata.sdk.service.GoodDataSettings;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class OutputStageServiceTest {
private OutputStageService outputStageService;
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
- outputStageService = new OutputStageService(new RestTemplate(), new GoodDataSettings());
+ outputStageService = new OutputStageService(WebClient.builder().build(), new GoodDataSettings());
}
- @Test(expectedExceptions = IllegalArgumentException.class)
+ @Test
public void testGetOutputStageByNullUri() {
- outputStageService.getOutputStageByUri(null);
+ assertThrows(IllegalArgumentException.class, () -> outputStageService.getOutputStageByUri(null));
}
- @Test(expectedExceptions = IllegalArgumentException.class)
+ @Test
public void testGetOutputStageByNullProject() {
- outputStageService.getOutputStage(null);
+ assertThrows(IllegalArgumentException.class, () -> outputStageService.getOutputStage(null));
}
- @Test(expectedExceptions = IllegalArgumentException.class)
+ @Test
public void testUpdateOutputStageNullOutputStage() {
- outputStageService.updateOutputStage(null);
+ assertThrows(IllegalArgumentException.class, () -> outputStageService.updateOutputStage(null));
}
-}
\ No newline at end of file
+}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceAT.java
index 14b5df268..59e40cfb2 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceAT.java
@@ -21,7 +21,7 @@
import org.hamcrest.Matchers;
import org.hamcrest.core.IsIterableContaining;
import org.springframework.core.io.ClassPathResource;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -41,9 +41,11 @@
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
+
/**
* Dataload processes acceptance tests.
*/
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ProcessServiceAT extends AbstractGoodDataAT {
private static final int MAX_RETRIES = 3;
@@ -53,7 +55,8 @@ public class ProcessServiceAT extends AbstractGoodDataAT {
private Schedule schedule;
private Schedule triggeredSchedule;
- @Test(groups = "process", dependsOnGroups = "project")
+ @Test
+ @Order(1)
public void createProcess() throws Exception {
final File dir = createTempDirectory("sdktest").toFile();
try {
@@ -66,7 +69,8 @@ public void createProcess() throws Exception {
}
}
- @Test(groups = "process", dependsOnMethods = "createProcess")
+ @Test
+ @Order(2)
public void createSchedule() {
schedule = gd.getProcessService().createSchedule(project, new Schedule(process, "sdktest.grf", "0 0 * * *"));
schedule.setReschedule(Duration.ofMinutes(15));
@@ -77,7 +81,8 @@ public void createSchedule() {
assertThat(schedule.getRescheduleInMinutes(), is(15));
}
- @Test(groups = "process", dependsOnMethods = "createSchedule")
+ @Test
+ @Order(3)
public void executeSchedule() {
final FutureResult future = gd.getProcessService().executeSchedule(schedule);
final ScheduleExecution scheduleExecution = future.get();
@@ -85,7 +90,8 @@ public void executeSchedule() {
assertThat(scheduleExecution.getStatus(), is("OK"));
}
- @Test(groups = "process", dependsOnMethods = "createSchedule")
+ @Test
+ @Order(4)
public void createTriggeredSchedule() {
triggeredSchedule = gd.getProcessService().createSchedule(project, new Schedule(process, "sdktest.grf", schedule));
@@ -94,7 +100,8 @@ public void createTriggeredSchedule() {
assertThat(triggeredSchedule.getTriggerScheduleId(), is(schedule.getId()));
}
- @Test(groups = "process", dependsOnMethods = {"createSchedule", "createTriggeredSchedule"})
+ @Test
+ @Order(5)
public void listSchedules() {
final Page collection = gd.getProcessService().listSchedules(project);
@@ -103,7 +110,8 @@ public void listSchedules() {
assertThat(collection.getNextPage(), nullValue());
}
- @Test(groups = "process", dependsOnMethods = "createSchedule")
+ @Test
+ @Order(6)
public void updateSchedule() {
schedule.setState(ScheduleState.DISABLED);
schedule.setReschedule(Duration.ofMinutes(26));
@@ -114,7 +122,8 @@ public void updateSchedule() {
assertThat(gd.getProcessService().updateSchedule(schedule).getRescheduleInMinutes(), is(26));
}
- @Test(groups = "process", dependsOnGroups = "project")
+ @Test
+ @Order(7)
public void createProcessFromGit() {
DataloadProcess newProcess = new DataloadProcess("sdktest ruby appstore " + System.getenv("BUILD_NUMBER"), ProcessType.RUBY.toString(),
"${PUBLIC_APPSTORE}:branch/demo:/test/HelloApp");
@@ -123,7 +132,8 @@ public void createProcessFromGit() {
assertThat(processAppstore.getExecutables(), contains("hello.rb"));
}
- @Test(groups = "process", dependsOnMethods = "createProcessFromGit")
+ @Test
+ @Order(8)
public void updateProcessFromGit() {
processAppstore.setPath("${PUBLIC_APPSTORE}:branch/demo:/test/AhojApp");
processAppstore = retry(() -> gd.getProcessService().updateProcessFromAppstore(processAppstore).get());
@@ -138,13 +148,15 @@ public void copy(final String file, final File dir) throws IOException {
);
}
- @Test(groups = "process", dependsOnMethods = "createProcess")
+ @Test
+ @Order(9)
public void processes() {
final Collection processes = gd.getProcessService().listProcesses(project);
assertThat(processes, IsIterableContaining.hasItem(ProcessIdMatcher.hasSameProcessIdAs(process)));
}
- @Test(groups = "process", dependsOnMethods = "createProcess")
+ @Test
+ @Order(10)
public void executeProcess() throws Exception {
ProcessService processService = gd.getProcessService();
ProcessExecutionDetail executionDetail = processService.executeProcess(new ProcessExecution(process, "sdktest.grf")).get();
@@ -154,14 +166,15 @@ public void executeProcess() throws Exception {
containsString("errooooooor"), containsString("fataaaaaaal")));
}
- @Test(groups = "process", dependsOnMethods = "createProcess",
- expectedExceptions = ProcessExecutionException.class, expectedExceptionsMessageRegExp = "(?s)Can't execute.*")
+ @Test
+ @Order(11)
public void failExecuteProcess() throws Exception {
ProcessService processService = gd.getProcessService();
processService.executeProcess(new ProcessExecution(process, "invalid.grf")).get();
}
- @Test(dependsOnGroups = "process", dependsOnMethods = "removeSchedule")
+ @Test
+ @Order(12)
public void removeProcess() throws Exception {
gd.getProcessService().removeProcess(process);
gd.getProcessService().removeProcess(processAppstore);
@@ -169,7 +182,8 @@ public void removeProcess() throws Exception {
assertThat(processes, Matchers.not(IsIterableContaining.hasItems(ProcessIdMatcher.hasSameProcessIdAs(process), ProcessIdMatcher.hasSameProcessIdAs(processAppstore))));
}
- @Test(dependsOnGroups = "process")
+ @Test
+ @Order(13)
public void removeSchedule() {
gd.getProcessService().removeSchedule(schedule);
gd.getProcessService().removeSchedule(triggeredSchedule);
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceIT.java
index 08759f9e6..4b92fa20b 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceIT.java
@@ -16,8 +16,8 @@
import com.gooddata.sdk.model.project.Project;
import com.gooddata.sdk.service.AbstractGoodDataIT;
import com.gooddata.sdk.service.FutureResult;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -34,6 +34,7 @@
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Is.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
public class ProcessServiceIT extends AbstractGoodDataIT {
@@ -60,7 +61,7 @@ public class ProcessServiceIT extends AbstractGoodDataIT {
private File file;
- @BeforeClass
+ @BeforeEach
public void setUp() throws Exception {
project = readObjectFromResource("/project/project.json", Project.class);
process = readObjectFromResource("/dataload/processes/process.json", DataloadProcess.class);
@@ -209,7 +210,7 @@ public void shouldExecuteProcess() throws Exception {
assertJsonEquals(readObjectFromResource("/dataload/processes/executionDetail-success.json", ProcessExecutionDetail.class), executionDetail);
}
- @Test(expectedExceptions = ProcessExecutionException.class)
+ @Test
public void shouldThrowOnExecuteProcessError() throws Exception {
onRequest()
.havingMethodEqualTo("POST")
@@ -231,7 +232,9 @@ public void shouldThrowOnExecuteProcessError() throws Exception {
.withBody(readFromResource("/dataload/processes/executionDetail.json"))
.withStatus(200);
- gd.getProcessService().executeProcess(new ProcessExecution(process, "test.groovy")).get();
+ assertThrows(ProcessExecutionException.class, () -> { //CHANGED
+ gd.getProcessService().executeProcess(new ProcessExecution(process, "test.groovy")).get();
+ });
}
@Test
@@ -399,7 +402,7 @@ public void shouldExecuteSchedule() {
assertThat(scheduleExecution.getStatus(), is("OK"));
}
- @Test(expectedExceptions = ScheduleExecutionException.class)
+ @Test
public void shouldNotExecuteSchedule() {
onRequest()
.havingMethodEqualTo("POST")
@@ -407,6 +410,8 @@ public void shouldNotExecuteSchedule() {
.respond()
.withStatus(409);
- gd.getProcessService().executeSchedule(schedule);
+ assertThrows(ScheduleExecutionException.class, () -> {
+ gd.getProcessService().executeSchedule(schedule);
+ });
}
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceTest.java
index 37ac37c3c..bdaae35fc 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataload/processes/ProcessServiceTest.java
@@ -14,18 +14,14 @@
import com.gooddata.sdk.service.account.AccountService;
import com.gooddata.sdk.service.gdc.DataStoreService;
import com.gooddata.sdk.model.project.Project;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClient.*;
+import reactor.core.publisher.Mono;
import java.io.File;
import java.io.FileOutputStream;
@@ -46,9 +42,8 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.*;
-import static org.springframework.http.HttpMethod.GET;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertTrue;
+
+import static org.junit.jupiter.api.Assertions.*;
public class ProcessServiceTest {
@@ -62,8 +57,10 @@ public class ProcessServiceTest {
private static final String PROCESSES_JSON = format("{\"processes\":{\"items\":[%s]}}", PROCESS_JSON);
private DataloadProcess process;
+
@Mock
- private RestTemplate restTemplate;
+ private WebClient webClient;
+
@Mock
private AccountService accountService;
@Mock
@@ -75,43 +72,72 @@ public class ProcessServiceTest {
private ProcessService processService;
- @BeforeMethod
+ // WebClient mocks
+ private WebClient.RequestBodyUriSpec postSpecMock;
+ private WebClient.RequestBodyUriSpec putSpecMock;
+ private WebClient.RequestBodySpec bodySpecMock;
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private WebClient.RequestHeadersSpec headersSpecMock;
+ private WebClient.RequestHeadersUriSpec uriSpecMock;
+ private WebClient.ResponseSpec responseSpecMock;
+
+ @BeforeEach
public void setUp() throws Exception {
MockitoAnnotations.openMocks(this).close();
- processService = new ProcessService(restTemplate, accountService, dataStoreService, new GoodDataSettings());
+ processService = new ProcessService(webClient, accountService, dataStoreService, new GoodDataSettings());
+
process = OBJECT_MAPPER.readValue(PROCESS_JSON, DataloadProcess.class);
when(project.getId()).thenReturn(PROJECT_ID);
when(accountService.getCurrent()).thenReturn(account);
when(account.getId()).thenReturn(ACCOUNT_ID);
+
+ postSpecMock = mock(WebClient.RequestBodyUriSpec.class);
+ putSpecMock = mock(WebClient.RequestBodyUriSpec.class);
+ bodySpecMock = mock(WebClient.RequestBodySpec.class);
+ headersSpecMock = mock(WebClient.RequestHeadersSpec.class);
+ uriSpecMock = mock(WebClient.RequestHeadersUriSpec.class);
+ responseSpecMock = mock(WebClient.ResponseSpec.class);
+
+ // PUT setup
+ when(webClient.put()).thenReturn(putSpecMock);
+ when(putSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+
+ // POST setup
+ when(webClient.post()).thenReturn(postSpecMock);
+ when(postSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+
+ // Common setup
+ when(bodySpecMock.contentType(any())).thenReturn(bodySpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
}
@Test
public void shouldDeploySmallProcessUsingAPI() throws Exception {
-
final DataloadProcess process = new DataloadProcess("test", ProcessType.GRAPH);
- final ArgumentCaptor entityCaptor = ArgumentCaptor.forClass(HttpEntity.class);
-
- when(restTemplate.exchange(any(URI.class), eq(HttpMethod.POST), entityCaptor.capture(), eq(DataloadProcess.class)))
- .thenReturn(new ResponseEntity<>(process, HttpStatus.CREATED));
+ when(webClient.post()).thenReturn(postSpecMock);
+ when(postSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(eq(DataloadProcess.class))).thenReturn(Mono.just(process));
processService.createProcess(project, process, createProcessOfSize(1));
- assertNotNull(entityCaptor.getValue());
- assertNotNull(entityCaptor.getValue().getBody());
- assertTrue(entityCaptor.getValue().getBody() instanceof MultiValueMap);
-
verifyNoInteractions(dataStoreService);
}
@Test
public void shouldDeployLargeProcessUsingWebDAV() throws Exception {
-
final DataloadProcess process = new DataloadProcess("test", ProcessType.GRAPH);
when(dataStoreService.getUri(anyString())).thenReturn(create("URI"));
- when(restTemplate.exchange(any(URI.class), eq(HttpMethod.POST), eq(new HttpEntity<>(process)), eq(DataloadProcess.class)))
- .thenReturn(new ResponseEntity<>(process, HttpStatus.CREATED));
+ when(webClient.post()).thenReturn(postSpecMock);
+ when(postSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+ when(bodySpecMock.bodyValue(any())).thenReturn(headersSpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(eq(DataloadProcess.class))).thenReturn(Mono.just(process));
processService.createProcess(project, process, createProcessOfSize(2048));
@@ -128,131 +154,200 @@ private static File createProcessOfSize(int size) throws Exception {
s.write(b);
}
}
-
file.deleteOnExit();
-
return file;
}
- @Test(expectedExceptions = IllegalArgumentException.class)
+ @Test
public void testUpdateProcessWithNullProcess() throws Exception {
- processService.updateProcess(null, File.createTempFile("test", null));
+ assertThrows(IllegalArgumentException.class, () ->
+ processService.updateProcess(null, File.createTempFile("test", null))
+ );
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void testUpdateProcessWithRestClientError() throws Exception {
- when(restTemplate.exchange(eq(create(PROCESS_URI)), any(HttpMethod.class), any(HttpEntity.class),
- eq(DataloadProcess.class))).thenThrow(new RestClientException(""));
- processService.updateProcess(process, File.createTempFile("test", null));
+ when(webClient.put()).thenReturn(putSpecMock);
+ when(putSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+ when(bodySpecMock.contentType(any())).thenReturn(bodySpecMock);
+ when(bodySpecMock.body(any())).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(eq(DataloadProcess.class)))
+ .thenReturn(Mono.error(new RuntimeException("WebClient error")));
+
+ assertThrows(GoodDataException.class, () ->
+ processService.updateProcess(process, File.createTempFile("test", null))
+ );
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void testUpdateProcessWithNoApiResponse() throws Exception {
- when(restTemplate.exchange(eq(create(PROCESS_URI)), any(HttpMethod.class), any(HttpEntity.class),
- eq(DataloadProcess.class))).thenReturn(null);
- processService.updateProcess(process, File.createTempFile("test", null));
+ when(webClient.put()).thenReturn(putSpecMock);
+ when(putSpecMock.uri(eq(create(PROCESS_URI)))).thenReturn(bodySpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(eq(DataloadProcess.class))).thenReturn(Mono.empty());
+
+ assertThrows(GoodDataException.class, () ->
+ processService.updateProcess(process, File.createTempFile("test", null))
+ );
}
@Test
public void testUpdateProcess() throws Exception {
- when(restTemplate.exchange(eq(create(PROCESS_URI)), any(HttpMethod.class), any(HttpEntity.class),
- eq(DataloadProcess.class))).thenReturn(new ResponseEntity<>(process, HttpStatus.OK));
+ when(webClient.put()).thenReturn(putSpecMock);
+ when(putSpecMock.uri(any(URI.class))).thenReturn(bodySpecMock);
+ when(bodySpecMock.contentType(any())).thenReturn(bodySpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(eq(DataloadProcess.class))).thenReturn(Mono.just(process));
final DataloadProcess result = processService
- .updateProcess(process, File.createTempFile("test", null));
+ .updateProcess(process, File.createTempFile("test", null));
assertThat(result, is(process));
}
- @Test(expectedExceptions = IllegalArgumentException.class)
+ @Test
public void testGetProcessByUriWithNullUri() {
- processService.getProcessByUri(null);
+ assertThrows(IllegalArgumentException.class, () -> processService.getProcessByUri(null));
}
@Test
public void testGetProcessByUri() {
- when(restTemplate.getForObject(PROCESS_URI, DataloadProcess.class)).thenReturn(process);
-
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(PROCESS_URI)).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcess.class)).thenReturn(Mono.just(process));
final DataloadProcess result = processService.getProcessByUri(PROCESS_URI);
assertThat(result, is(process));
}
- @Test(expectedExceptions = ProcessNotFoundException.class)
+ @Test
public void testGetProcessByUriNotFound() {
- when(restTemplate.getForObject(PROCESS_URI, DataloadProcess.class)).thenThrow(
- new GoodDataRestException(404, "", "", "", ""));
- processService.getProcessByUri(PROCESS_URI);
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(PROCESS_URI)).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcess.class))
+ .thenReturn(Mono.error(new GoodDataRestException(404, "", "", "", "")));
+ assertThrows(ProcessNotFoundException.class, () ->
+ processService.getProcessByUri(PROCESS_URI)
+ );
}
- @Test(expectedExceptions = GoodDataRestException.class)
+ @Test
public void testGetProcessByUriServerError() {
- when(restTemplate.getForObject(PROCESS_URI, DataloadProcess.class))
- .thenThrow(new GoodDataRestException(500, "", "", "", ""));
- processService.getProcessByUri(PROCESS_URI);
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(PROCESS_URI)).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ GoodDataRestException restEx = new GoodDataRestException(500, "", "", "", "");
+ when(responseSpecMock.bodyToMono(DataloadProcess.class))
+ .thenReturn(Mono.error(restEx));
+
+ GoodDataException ex = assertThrows(GoodDataException.class, () ->
+ processService.getProcessByUri(PROCESS_URI)
+ );
+ assertTrue(ex.getCause() instanceof GoodDataRestException);
+ assertEquals(restEx, ex.getCause());
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void testGetProcessByUriClientError() {
- when(restTemplate.getForObject(PROCESS_URI, DataloadProcess.class)).thenThrow(new RestClientException(""));
- processService.getProcessByUri(PROCESS_URI);
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(PROCESS_URI)).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcess.class))
+ .thenReturn(Mono.error(new RuntimeException("WebClient error")));
+ assertThrows(GoodDataException.class, () ->
+ processService.getProcessByUri(PROCESS_URI)
+ );
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void testListUserProcessesWithRestClientError() {
- when(restTemplate.getForObject(create(USER_PROCESS_URI), DataloadProcesses.class))
- .thenThrow(new RestClientException(""));
- processService.listUserProcesses();
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(create(USER_PROCESS_URI))).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcesses.class))
+ .thenReturn(Mono.error(new RuntimeException("WebClient error")));
+ assertThrows(GoodDataException.class, () -> processService.listUserProcesses());
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void testListUserProcessesWithNullResponse() {
- when(restTemplate.getForObject(create(USER_PROCESS_URI), DataloadProcesses.class)).thenReturn(null);
- processService.listUserProcesses();
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(create(USER_PROCESS_URI))).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcesses.class)).thenReturn(Mono.empty());
+ assertThrows(GoodDataException.class, () -> processService.listUserProcesses());
}
@Test
public void testListUserProcessesWithNoProcesses() throws Exception {
- when(restTemplate.getForObject(create(USER_PROCESS_URI), DataloadProcesses.class))
- .thenReturn(OBJECT_MAPPER.readValue("{\"processes\":{\"items\":[]}}", DataloadProcesses.class));
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(create(USER_PROCESS_URI))).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcesses.class))
+ .thenReturn(Mono.just(OBJECT_MAPPER.readValue("{\"processes\":{\"items\":[]}}", DataloadProcesses.class)));
final Collection result = processService.listUserProcesses();
assertThat(result, empty());
}
@Test
public void testListUserProcessesWithOneProcesses() throws IOException {
- when(restTemplate.getForObject(create(USER_PROCESS_URI), DataloadProcesses.class))
- .thenReturn(OBJECT_MAPPER.readValue(PROCESSES_JSON, DataloadProcesses.class));
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(create(USER_PROCESS_URI))).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(DataloadProcesses.class))
+ .thenReturn(Mono.just(OBJECT_MAPPER.readValue(PROCESSES_JSON, DataloadProcesses.class)));
final Collection result = processService.listUserProcesses();
assertThat(result, hasSize(1));
assertThat(result.iterator().next().getName(), is(process.getName()));
assertThat(result.iterator().next().getType(), is(process.getType()));
}
- @Test(expectedExceptions = ScheduleExecutionException.class)
+ @Test
public void testExecuteScheduleException() {
- when(restTemplate.postForObject(eq(SCHEDULE_EXECUTIONS_URI), any(ScheduleExecution.class), eq(ScheduleExecution.class)))
- .thenThrow(new RestClientException(""));
+ when(webClient.post()).thenReturn(postSpecMock);
+ when(postSpecMock.uri(eq(SCHEDULE_EXECUTIONS_URI))).thenReturn(bodySpecMock);
+ when(bodySpecMock.bodyValue(any())).thenReturn(headersSpecMock);
+ when(bodySpecMock.body(any())).thenAnswer(inv -> headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(ScheduleExecution.class))
+ .thenReturn(Mono.error(new RuntimeException("WebClient error")));
final Schedule schedule = mock(Schedule.class);
-
when(schedule.getExecutionsUri()).thenReturn(SCHEDULE_EXECUTIONS_URI);
- processService.executeSchedule(schedule);
+ assertThrows(ScheduleExecutionException.class, () ->
+ processService.executeSchedule(schedule)
+ );
}
- @Test(expectedExceptions = ScheduleExecutionException.class)
+ @Test
public void testExecuteScheduleExceptionDuringPolling() {
+
final ScheduleExecution execution = mock(ScheduleExecution.class);
when(execution.getUri()).thenReturn(SCHEDULE_EXECUTION_URI);
final Schedule schedule = mock(Schedule.class);
when(schedule.getExecutionsUri()).thenReturn(SCHEDULE_EXECUTIONS_URI);
- when(restTemplate.postForObject(eq(SCHEDULE_EXECUTIONS_URI), any(ScheduleExecution.class), eq(ScheduleExecution.class)))
- .thenReturn(execution);
-
- when(restTemplate.execute(eq(URI.create(SCHEDULE_EXECUTION_URI)), eq(GET), any(), any()))
- .thenThrow(mock(GoodDataRestException.class));
-
- FutureResult futureResult = processService.executeSchedule(schedule);
- futureResult.get();
+ when(webClient.post()).thenReturn(postSpecMock);
+ when(postSpecMock.uri(eq(SCHEDULE_EXECUTIONS_URI))).thenReturn(bodySpecMock);
+ when(bodySpecMock.bodyValue(any())).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(ScheduleExecution.class))
+ .thenReturn(Mono.just(execution));
+
+ when(webClient.get()).thenReturn(uriSpecMock);
+ when(uriSpecMock.uri(eq(SCHEDULE_EXECUTION_URI))).thenReturn(headersSpecMock);
+ when(headersSpecMock.retrieve()).thenReturn(responseSpecMock);
+ when(responseSpecMock.bodyToMono(ScheduleExecution.class))
+ .thenReturn(Mono.error(new GoodDataRestException(500, "", "", "", "")));
+
+ assertThrows(ScheduleExecutionException.class, () -> {
+ FutureResult futureResult = processService.executeSchedule(schedule);
+ futureResult.get();
+ });
}
+
}
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceAT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceAT.java
index ad0cb4b49..706ad4651 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceAT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceAT.java
@@ -9,7 +9,10 @@
import com.gooddata.sdk.model.dataset.DatasetManifest;
import com.gooddata.sdk.model.dataset.Upload;
import com.gooddata.sdk.model.dataset.UploadStatistics;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
import java.util.Collection;
@@ -18,17 +21,19 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.text.MatchesPattern.matchesPattern;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.fail;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assertions.assertFalse;
/**
* Dataset acceptance tests.
*/
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class DatasetServiceAT extends AbstractGoodDataAT {
private static final String FAILED_LOAD_PATTERN = "Number of columns does(n't| not) co[r]{1,2}espond (in dataset.person.csv at line 3|on line 3 in dataset.person.csv)";
- @Test(groups = "dataset", dependsOnGroups = {"md", "datastore"})
+ @Test
+ @Order(1)
public void loadDataset() throws Exception {
final DatasetService datasetService = gd.getDatasetService();
@@ -36,7 +41,8 @@ public void loadDataset() throws Exception {
datasetService.loadDataset(project, manifest, readFromResource("/person.csv")).get();
}
- @Test(groups = "dataset", dependsOnMethods = {"loadDataset"})
+ @Test
+ @Order(2)
public void loadDatasetBatch() throws Exception {
final DatasetService datasetService = gd.getDatasetService();
@@ -48,13 +54,15 @@ public void loadDatasetBatch() throws Exception {
datasetService.loadDatasets(project, personManifest, cityManifest).get();
}
- @Test(groups = "dataset", dependsOnMethods = "loadDatasetBatch")
+ @Test
+ @Order(3)
public void updateData() {
final DatasetService datasetService = gd.getDatasetService();
datasetService.updateProjectData(project, "DELETE FROM {attr.person.name} WHERE {label.person.name} = \"not exists\";");
}
- @Test(groups = "dataset", dependsOnGroups = {"md", "datastore"})
+ @Test
+ @Order(4)
public void loadDatasetFail(){
final DatasetService datasetService = gd.getDatasetService();
final DatasetManifest manifest = datasetService.getDatasetManifest(project, "dataset.person");
@@ -66,7 +74,8 @@ public void loadDatasetFail(){
}
}
- @Test(groups = "dataset", dependsOnMethods = {"loadDataset"})
+ @Test
+ @Order(5)
public void loadDatasetBatchFail() throws Exception {
final DatasetService datasetService = gd.getDatasetService();
@@ -83,7 +92,8 @@ public void loadDatasetBatchFail() throws Exception {
}
}
- @Test(groups = "dataset", dependsOnMethods = {"loadDataset"})
+ @Test
+ @Order(6)
public void listUploadsForDataset() throws Exception {
final Collection uploads = gd.getDatasetService().listUploadsForDataset(project, "dataset.person");
@@ -91,14 +101,16 @@ public void listUploadsForDataset() throws Exception {
assertFalse(uploads.isEmpty());
}
- @Test(groups = "dataset", dependsOnMethods = {"loadDataset"})
+ @Test
+ @Order(7)
public void getLastUploadForDataset() throws Exception {
final Upload lastUpload = gd.getDatasetService().getLastUploadForDataset(project, "dataset.person");
assertThat(lastUpload, notNullValue());
}
- @Test(groups = "dataset", dependsOnMethods = {"loadDataset", "loadDatasetFail"})
+ @Test
+ @Order(8)
public void getUploadStatistics() throws Exception {
final UploadStatistics uploadStatistics = gd.getDatasetService().getUploadStatistics(project);
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceIT.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceIT.java
index 8bf2f7ff0..b876bf5dc 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceIT.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceIT.java
@@ -11,9 +11,9 @@
import com.gooddata.sdk.model.gdc.AboutLinks.Link;
import com.gooddata.sdk.model.gdc.TaskStatus;
import com.gooddata.sdk.model.project.Project;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -26,7 +26,7 @@
import static net.jadler.Jadler.onRequest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
-import static org.testng.Assert.fail;
+import static org.junit.jupiter.api.Assertions.fail;
public class DatasetServiceIT extends AbstractGoodDataIT {
@@ -34,14 +34,14 @@ public class DatasetServiceIT extends AbstractGoodDataIT {
private static final String DML_MAQL = "DELETE FROM {attr.logs.phase_id} WHERE {created.date.yyyymmdd} < \"2015-01-18\"";
- private Project project;
+ private static Project project;
- @BeforeClass
- public void setUpClass() throws Exception {
+ @BeforeAll
+ public static void setUpClass() throws Exception {
project = readObjectFromResource("/project/project.json", Project.class);
}
- @BeforeMethod
+ @BeforeEach
public void setUp() throws Exception {
onRequest()
.havingMethodEqualTo("GET")
@@ -100,7 +100,7 @@ public void shouldLoadDatasets() throws Exception {
}
- @Test(expectedExceptions = DatasetException.class, expectedExceptionsMessageRegExp = ".*dataset.person.*Unable to load.*")
+ @Test
public void shouldFailPolling() throws Exception {
onRequest()
.havingPathEqualTo("/gdc/md/PROJECT/tasks/task/ID/status")
@@ -108,7 +108,13 @@ public void shouldFailPolling() throws Exception {
.withStatus(400);
final DatasetManifest manifest = readObjectFromResource("/dataset/datasetManifest.json", DatasetManifest.class);
- gd.getDatasetService().loadDataset(project, manifest, new ByteArrayInputStream(new byte[]{})).get();
+ try {
+ gd.getDatasetService().loadDataset(project, manifest, new ByteArrayInputStream(new byte[]{})).get();
+ fail("Exception should be thrown");
+ } catch (DatasetException ex) {
+ assertThat(ex.getMessage(), containsString("dataset.person"));
+ assertThat(ex.getMessage(), containsString("Unable to load"));
+ }
}
@Test
@@ -176,7 +182,7 @@ public void shouldOptimizeSliHash() throws Exception {
gd.getDatasetService().optimizeSliHash(project).get();
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailOptimizeSliHash() throws Exception {
onRequest()
.havingMethodEqualTo("POST")
@@ -194,7 +200,12 @@ public void shouldFailOptimizeSliHash() throws Exception {
.withStatus(200)
.withBody(OBJECT_MAPPER.writeValueAsString(new TaskStatus("ERROR", STATUS_URI)));
- gd.getDatasetService().optimizeSliHash(project).get();
+ try {
+ gd.getDatasetService().optimizeSliHash(project).get();
+ fail("Exception should be thrown");
+ } catch (GoodDataException e) {
+ // expected
+ }
}
@Test
@@ -219,7 +230,7 @@ public void shouldUpdateProjectData() throws IOException {
gd.getDatasetService().updateProjectData(project, DML_MAQL).get();
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailUpdateProjectDataServerError() throws IOException {
onRequest()
.havingMethodEqualTo("POST")
@@ -234,10 +245,15 @@ public void shouldFailUpdateProjectDataServerError() throws IOException {
.withStatus(500)
;
- gd.getDatasetService().updateProjectData(project, DML_MAQL).get();
+ try {
+ gd.getDatasetService().updateProjectData(project, DML_MAQL).get();
+ fail("Exception should be thrown");
+ } catch (GoodDataException e) {
+ // expected
+ }
}
- @Test(expectedExceptions = GoodDataException.class)
+ @Test
public void shouldFailUpdateProjectData() throws IOException {
onRequest()
.havingMethodEqualTo("POST")
@@ -256,7 +272,12 @@ public void shouldFailUpdateProjectData() throws IOException {
.withBody(OBJECT_MAPPER.writeValueAsString(new TaskState("ERROR", STATUS_URI)))
;
- gd.getDatasetService().updateProjectData(project, DML_MAQL).get();
+ try {
+ gd.getDatasetService().updateProjectData(project, DML_MAQL).get();
+ fail("Exception should be thrown");
+ } catch (GoodDataException e) {
+ // expected
+ }
}
@Test
diff --git a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceTest.java b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceTest.java
index 1a1b8ec02..f3579e3eb 100644
--- a/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceTest.java
+++ b/gooddata-java/src/test/java/com/gooddata/sdk/service/dataset/DatasetServiceTest.java
@@ -5,6 +5,7 @@
*/
package com.gooddata.sdk.service.dataset;
+
import com.gooddata.sdk.common.GoodDataException;
import com.gooddata.sdk.common.GoodDataRestException;
import com.gooddata.sdk.model.dataset.*;
@@ -13,33 +14,54 @@
import com.gooddata.sdk.service.GoodDataSettings;
import com.gooddata.sdk.service.gdc.DataStoreException;
import com.gooddata.sdk.service.gdc.DataStoreService;
-import org.hamcrest.Matchers;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.springframework.web.client.RestClientException;
-import org.springframework.web.client.RestTemplate;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.*;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClient;
+
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.InputStream;
+import java.time.ZonedDateTime;
+import java.util.Map;
import static com.gooddata.sdk.common.util.ResourceUtils.OBJECT_MAPPER;
import static java.lang.String.format;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.nullValue;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.when;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.http.MediaType;
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.MultiValueMap;
+import org.springframework.http.client.reactive.ClientHttpRequest;
+import reactor.util.context.Context;
+
+import java.nio.charset.Charset;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+
+
+
+@ExtendWith(MockitoExtension.class)
public class DatasetServiceTest {
private static final String PROJECT_ID = "17";
private static final String DATASET_ID = "11";
@Mock
- private RestTemplate restTemplate;
+ private WebClient webClient;
@Mock
private DataStoreService dataStoreService;
@Mock
@@ -52,158 +74,253 @@ public class DatasetServiceTest {
private DatasetService service;
private static final String DATASET_UPLOADS_URI = "uploads/uri";
- @BeforeMethod
- public void setUp() throws Exception {
- MockitoAnnotations.openMocks(this).close();
- service = new DatasetService(restTemplate, dataStoreService, new GoodDataSettings());
- when(project.getId()).thenReturn(PROJECT_ID);
+
+ @BeforeEach
+ void setUp() {
+ service = new DatasetService(webClient, dataStoreService, new GoodDataSettings());
+ lenient().when(project.getId()).thenReturn(PROJECT_ID);
}
- @Test(expectedExceptions = IllegalArgumentException.class)
- public void testGetDatasetManifestWithNullProject() {
- service.getDatasetManifest(null, DATASET_ID);
+ @Test
+ void testGetDatasetManifestWithNullProject() {
+ assertThrows(IllegalArgumentException.class, () -> service.getDatasetManifest(null, DATASET_ID));
}
- @Test(expectedExceptions = IllegalArgumentException.class)
- public void testGetDatasetManifestWithNullId() {
- service.getDatasetManifest(project, null);
+ @Test
+ void testGetDatasetManifestWithNullId() {
+ assertThrows(IllegalArgumentException.class, () -> service.getDatasetManifest(project, null));
}
- @Test(expectedExceptions = IllegalArgumentException.class)
- public void testGetDatasetManifestWithEmptyId() {
- service.getDatasetManifest(project, "");
- }
- @Test(expectedExceptions = DatasetNotFoundException.class)
- public void testGetDatasetManifestWhenNotFound() {
- when(restTemplate.getForObject(DatasetManifest.URI, DatasetManifest.class, PROJECT_ID, DATASET_ID))
- .thenThrow(new GoodDataRestException(404, "", "", "", ""));
- service.getDatasetManifest(project, DATASET_ID);
+ @Test
+ void testGetDatasetManifestWithEmptyId() {
+ assertThrows(IllegalArgumentException.class, () -> service.getDatasetManifest(project, ""));
}
- @Test(expectedExceptions = DatasetException.class)
- public void testGetDatasetManifestWhenRestClientError() {
- when(restTemplate.getForObject(DatasetManifest.URI, DatasetManifest.class, PROJECT_ID, DATASET_ID))
- .thenThrow(new RestClientException(""));
- service.getDatasetManifest(project, DATASET_ID);
+ @SuppressWarnings("rawtypes")
+ @Test
+ void testGetDatasetManifestWhenNotFound() {
+ TestHeadersSpec headersSpec = mock(TestHeadersSpec.class);
+ WebClient.RequestHeadersUriSpec uriSpec = mock(WebClient.RequestHeadersUriSpec.class);
+ WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);
+
+ when(webClient.get()).thenReturn(uriSpec);
+
+
+ when(uriSpec.uri(anyString())).thenReturn(headersSpec);
+
+ when(headersSpec.retrieve()).thenReturn(responseSpec);
+ when(responseSpec.bodyToMono(DatasetManifest.class))
+ .thenReturn(Mono.error(new GoodDataRestException(404, "", "", "", "")));
+
+ assertThrows(DatasetNotFoundException.class, () -> service.getDatasetManifest(project, DATASET_ID));
}
- @Test(expectedExceptions = DatasetException.class)
- public void testGetDatasetManifestWhenOtherError() {
- when(restTemplate.getForObject(DatasetManifest.URI, DatasetManifest.class, PROJECT_ID, DATASET_ID))
- .thenThrow(new GoodDataRestException(500, "", "", "", ""));
- service.getDatasetManifest(project, DATASET_ID);
+
+
+
+ // Minimal stub that satisfies S extends RequestHeadersSpec
+ static class TestHeadersSpec implements WebClient.RequestHeadersSpec {
+ @Override public WebClient.ResponseSpec retrieve() { return null; }
+ @Override public TestHeadersSpec accept(MediaType... mediaTypes) { return this; }
+ @Override public TestHeadersSpec acceptCharset(Charset... charsets) { return this; }
+ @Override public TestHeadersSpec cookie(String name, String value) { return this; }
+ @Override public TestHeadersSpec cookies(Consumer> cookiesConsumer) { return this; }
+ @Override public TestHeadersSpec header(String headerName, String... headerValues) { return this; }
+ @Override public TestHeadersSpec headers(Consumer headersConsumer) { return this; }
+ @Override public TestHeadersSpec ifModifiedSince(ZonedDateTime ifModifiedSince) { return this; }
+ @Override public TestHeadersSpec ifNoneMatch(String... ifNoneMatch) { return this; }
+ @Override public Mono exchangeToMono(Function> responseHandler) { throw new UnsupportedOperationException(); }
+ @Override public TestHeadersSpec httpRequest(Consumer requestConsumer) { return this; }
+ @Override public TestHeadersSpec attributes(Consumer