org.ambraproject.wombat.service.remote.AbstractRemoteService.java Source code

Java tutorial

Introduction

Here is the source code for org.ambraproject.wombat.service.remote.AbstractRemoteService.java

Source

/*
 * Copyright (c) 2017 Public Library of Science
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package org.ambraproject.wombat.service.remote;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.ambraproject.wombat.service.EntityNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;

import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.util.Optional;

/**
 * Superclass for service beans that make calls to remote APIs.
 */
abstract class AbstractRemoteService<S extends Closeable> implements RemoteService<S> {
    private static final Logger log = LoggerFactory.getLogger(AbstractRemoteService.class);

    private final Optional<HttpClientConnectionManager> connectionManager;

    protected AbstractRemoteService(HttpClientConnectionManager connectionManager) {
        this.connectionManager = Optional.ofNullable(connectionManager);
    }

    private CloseableHttpClient createClient() {
        HttpClientBuilder clientBuilder = HttpClientBuilder.create();
        if (connectionManager.isPresent()) {
            clientBuilder = clientBuilder.setConnectionManager(connectionManager.get());
        }
        return clientBuilder.build();
    }

    @Override
    public CloseableHttpResponse getResponse(HttpUriRequest target) throws IOException {
        // Don't close the client, as this shuts down the connection pool. Do close every response or its entity stream.
        CloseableHttpClient client = createClient();

        // We want to return an unclosed response, so close the response only if we throw an exception.
        boolean returningResponse = false;
        CloseableHttpResponse response = null;
        try {
            try {
                response = client.execute(target);
            } catch (HttpHostConnectException e) {
                throw new ServiceConnectionException(e);
            }

            StatusLine statusLine = response.getStatusLine();
            int statusCode = statusLine.getStatusCode();
            if (isErrorStatus(statusCode)) {
                URI targetUri = target.getURI();
                String address = targetUri.getPath();
                if (!Strings.isNullOrEmpty(targetUri.getQuery())) {
                    address += "?" + targetUri.getQuery();
                }
                if (statusCode == HttpStatus.NOT_FOUND.value()) {
                    throw new EntityNotFoundException(address);
                } else {
                    String responseBody = IOUtils.toString(response.getEntity().getContent());
                    String message = String.format("Request to \"%s\" failed (%d): %s.", address, statusCode,
                            statusLine.getReasonPhrase());
                    throw new ServiceRequestException(statusCode, message, responseBody);
                }
            }
            returningResponse = true;
            return response;
        } finally {
            if (!returningResponse && response != null) {
                response.close();
            }
        }
    }

    private static boolean isErrorStatus(int statusCode) {
        HttpStatus.Series series = HttpStatus.Series.valueOf(statusCode);
        return (series == HttpStatus.Series.CLIENT_ERROR) || (series == HttpStatus.Series.SERVER_ERROR);
    }

    @Override
    public S request(HttpUriRequest target) throws IOException {
        return open(requestEntity(target));
    }

    /**
     * Get a response and open the response entity.
     *
     * @param target the request to send
     * @return the response entity
     * @throws IOException      on making the request
     * @throws RuntimeException if a response is received but it has no response entity object
     * @see org.apache.http.HttpResponse#getEntity()
     */
    private HttpEntity requestEntity(HttpUriRequest target) throws IOException {
        Preconditions.checkNotNull(target);

        // We must leave the response open if we return a valid stream, but must close it otherwise.
        boolean returningResponse = false;
        CloseableHttpResponse response = null;
        try {
            response = getResponse(target);
            HttpEntity entity = response.getEntity();
            if (entity == null) {
                throw new RuntimeException("No response");
            }
            returningResponse = true;
            return entity;
        } finally {
            if (!returningResponse && response != null) {
                response.close();
            }
        }
    }

}