com.gooddata.gdc.DataStoreService.java Source code

Java tutorial

Introduction

Here is the source code for com.gooddata.gdc.DataStoreService.java

Source

/**
 * Copyright (C) 2004-2016, 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.gdc;

import com.github.sardine.Sardine;
import com.github.sardine.impl.SardineException;
import com.gooddata.UriPrefixer;
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.StatusLine;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.params.HttpParams;
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.RestClientException;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Locale;

import static com.gooddata.util.Validate.notEmpty;
import static com.gooddata.util.Validate.notNull;

/**
 * Uploads, downloads, deletes, ... at datastore
 */
public class DataStoreService {

    private final Sardine sardine;
    private final GdcService gdcService;
    private final URI gdcUri;
    private final RestTemplate restTemplate;

    private UriPrefixer prefixer;

    /**
     * Creates new DataStoreService
     * @param httpClient httpClient to make datastore connection
     * @param restTemplate restTemplate to make datastore connection
     * @param gdcService used to obtain datastore URI
     * @param gdcUri complete GDC URI used to prefix possibly relative datastore path
     */
    public DataStoreService(HttpClient httpClient, RestTemplate restTemplate, GdcService gdcService,
            String gdcUri) {
        this.gdcService = notNull(gdcService, "gdcService");
        this.gdcUri = URI.create(notEmpty(gdcUri, "gdcUri"));
        this.restTemplate = notNull(restTemplate, "restTemplate");
        sardine = new GdcSardine(new CustomHttpClientBuilder(httpClient));
    }

    private UriPrefixer getPrefixer() {
        if (prefixer == null) {
            final String uriString = gdcService.getRootLinks().getUserStagingUri();
            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 com.gooddata.gdc.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 {
            sardine.put(url.toString(), stream);
        } 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/martiner/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 (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/martiner/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/martiner/gooddata-java/wiki/Known-limitations";
    }

    /**
     * Download given path and return data as stream
     * @param path path from where to download
     * @return download stream
     * @throws com.gooddata.gdc.DataStoreException in case download failed
     */
    public InputStream download(String path) {
        notEmpty(path, "path");
        final URI uri = getUri(path);
        try {
            return sardine.get(uri.toString());
        } catch (IOException e) {
            throw new DataStoreException("Unable to download from " + uri, e);
        }
    }

    /**
     * Delete given path from datastore.
     * @param path path to delete
     * @throws com.gooddata.gdc.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 (RestClientException 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
        }

        @Override
        @Deprecated
        public HttpParams getParams() {
            return client.getParams();
        }

        @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> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler)
                throws IOException, ClientProtocolException {
            return client.execute(request, responseHandler);
        }

        @Override
        public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler,
                HttpContext context) throws IOException, ClientProtocolException {
            return client.execute(request, responseHandler, context);
        }

        @Override
        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
                throws IOException, ClientProtocolException {
            return client.execute(target, request, responseHandler);
        }

        @Override
        public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> 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);
        }

        @Override
        public HttpParams getParams() {
            return wrappedResponse.getParams();
        }

        @Override
        public void setParams(HttpParams params) {
            wrappedResponse.setParams(params);
        }
    }
}