All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gooddata.sdk.service.gdc.DataStoreService Maven / Gradle / Ivy

There is a newer version: 3.11.1+api3
Show newest version
/*
 * Copyright (C) 2004-2019, GoodData(R) Corporation. All rights reserved.
 * 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.gdc;

import com.github.sardine.impl.SardineException;
import com.gooddata.sdk.common.GoodDataRestException;
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 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
 */
public class DataStoreService {

    private final GdcSardine sardine;
    private final Supplier stagingUriSupplier;
    private final URI gdcUri;
    private final RestTemplate restTemplate;

    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");
        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")));
    }

    private UriPrefixer getPrefixer() {
        if (prefixer == null) {
            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
     */
    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); } } } 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"; } /** * Download given path and return data as stream * @param path path from where to download * @return download stream * @throws DataStoreException in case download failed */ 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); } } /** * Delete given path from datastore. * @param path path to delete * @throws DataStoreException in case delete failed */ public void delete(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); } } /** * 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); } } 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 responseHandler) throws IOException, ClientProtocolException { return client.execute(request, responseHandler); } @Override public T execute(HttpUriRequest request, ResponseHandler responseHandler, HttpContext context) throws IOException, ClientProtocolException { return client.execute(request, responseHandler, context); } @Override public T execute(HttpHost target, HttpRequest request, ResponseHandler responseHandler) throws IOException, ClientProtocolException { return client.execute(target, request, responseHandler); } @Override public T execute(HttpHost target, HttpRequest request, ResponseHandler responseHandler, HttpContext context) throws IOException, ClientProtocolException { return client.execute(target, request, responseHandler, context); } } 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); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy