alien4cloud.paas.cloudify2.rest.external.RestClientExecutor.java Source code

Java tutorial

Introduction

Here is the source code for alien4cloud.paas.cloudify2.rest.external.RestClientExecutor.java

Source

package alien4cloud.paas.cloudify2.rest.external;

/*******************************************************************************
 * Copyright (c) 2013 GigaSpaces Technologies Ltd. All rights reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.cloudifysource.dsl.internal.CloudifyConstants;
import org.cloudifysource.dsl.internal.CloudifyErrorMessages;
import org.cloudifysource.dsl.rest.response.Response;
import org.cloudifysource.restclient.exceptions.RestClientException;
import org.cloudifysource.restclient.exceptions.RestClientHttpException;
import org.cloudifysource.restclient.exceptions.RestClientIOException;
import org.cloudifysource.restclient.messages.MessagesUtils;
import org.cloudifysource.restclient.messages.RestClientMessageKeys;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

/**
 * Creates all the HTTP requests needed for the RestClient and handles the HTTP responses.
 * 
 * @author yael
 *
 */
public class RestClientExecutor {
    private static final Logger logger = Logger.getLogger(RestClientExecutor.class.getName());

    private static final String FORWARD_SLASH = "/";
    private static final int DEFAULT_TRIALS_NUM = 1;
    private static final int GET_TRIALS_NUM = 3;

    private final DefaultHttpClient httpClient;
    private String urlStr;

    /**
     * C'tor.
     * 
     * @param httpClient .
     * @param url .
     */
    public RestClientExecutor(final DefaultHttpClient httpClient, final URL url) {
        this.httpClient = httpClient;
        this.urlStr = url.toExternalForm();
        if (!this.urlStr.endsWith(FORWARD_SLASH)) {
            this.urlStr += FORWARD_SLASH;
        }
    }

    /**
     * Executes HTTP post over REST on the given (relative) URL with the given postBody.
     *
     * @param url
     *            The URL to post to.
     * @param postBody
     *            The content of the post.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws org.cloudifysource.restclient.exceptions.RestClientException
     *             Reporting failure to post the file.
     */
    public <T> T postObject(final String url, final Object postBody,
            final TypeReference<Response<T>> responseTypeReference) throws RestClientException {
        final HttpEntity stringEntity;
        String jsonStr;
        try {
            jsonStr = new ObjectMapper().writeValueAsString(postBody);
            stringEntity = new StringEntity(jsonStr, "UTF-8");
        } catch (final IOException e) {
            throw MessagesUtils.createRestClientIOException(RestClientMessageKeys.SERIALIZATION_ERROR.getName(), e,
                    url);
        }
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "executing post request to " + url + ", tring to post object " + jsonStr);
        }
        return post(url, responseTypeReference, stringEntity);
    }

    /**
     *
     * @param relativeUrl
     *            The URL to post to.
     * @param fileToPost
     *            The file to post.
     * @param partName
     *            The name of the request parameter (the posted file) to bind to.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws RestClientException
     *             Reporting failure to post the file.
     */
    public <T> T postFile(final String relativeUrl, final File fileToPost, final String partName,
            final TypeReference<Response<T>> responseTypeReference) throws RestClientException {
        final MultipartEntity multipartEntity = new MultipartEntity();
        final FileBody fileBody = new FileBody(fileToPost);
        multipartEntity.addPart(partName, fileBody);
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE,
                    "executing post request to " + relativeUrl + ", tring to post file " + fileToPost.getName());
        }
        return post(relativeUrl, responseTypeReference, multipartEntity);
    }

    /**
     *
     * @param relativeUrl
     *            The URL to send the get request to.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public <T> T get(final String relativeUrl, final TypeReference<Response<T>> responseTypeReference)
            throws RestClientException {
        String fullUrl = getFullUrl(relativeUrl);
        final HttpGet getRequest = new HttpGet(fullUrl);
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "execute get request to " + relativeUrl);
        }
        return executeRequest(getRequest, responseTypeReference);
    }

    /**
     *
     * @param relativeUrl
     *            The URL to send the delete request to.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param params
     *            Request parameters
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public <T> T delete(final String relativeUrl, final Map<String, String> params,
            final TypeReference<Response<T>> responseTypeReference) throws RestClientException {

        URIBuilder builder;

        try {
            builder = new URIBuilder(getFullUrl(relativeUrl));
        } catch (URISyntaxException e) {
            throw MessagesUtils.createRestClientException(RestClientMessageKeys.INVALID_URL.getName(), e,
                    getFullUrl(relativeUrl));
        }

        if (params != null) {
            for (final Map.Entry<String, String> entry : params.entrySet()) {
                builder.addParameter(entry.getKey(), entry.getValue());
            }
        }

        final HttpDelete deleteRequest = new HttpDelete(builder.toString());
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "executing delete request to " + relativeUrl);
        }
        return executeRequest(deleteRequest, responseTypeReference);
    }

    /**
     *
     * @param relativeUrl
     *            The URL to send the delete request to.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    public <T> T delete(final String relativeUrl, final TypeReference<Response<T>> responseTypeReference)
            throws RestClientException {

        final HttpDelete deleteRequest = new HttpDelete(getFullUrl(relativeUrl));
        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "executing delete request to " + relativeUrl);
        }
        return executeRequest(deleteRequest, responseTypeReference);
    }

    /**
     *
     * @param relativeUrl
     *            The URL to send the delete request to.
     * @param responseTypeReference
     *            The type reference of the response.
     * @param <T> The type of the response.
     * @return The response object from the REST server.
     * @throws RestClientException .
     */
    private <T> T post(final String relativeUrl, final TypeReference<Response<T>> responseTypeReference,
            final HttpEntity entity) throws RestClientException {
        final HttpPost postRequest = new HttpPost(getFullUrl(relativeUrl));
        if (entity instanceof StringEntity) {
            postRequest.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
        }
        postRequest.setEntity(entity);
        return executeRequest(postRequest, responseTypeReference);
    }

    /**
     * Return the response's body.
     * 
     * @param response .
     * @return the response's body.
     * @throws RestClientIOException
     *             if failed to transform the response into string.
     */
    public static String getResponseBody(final HttpResponse response) throws RestClientIOException {

        InputStream instream = null;
        try {
            final HttpEntity entity = response.getEntity();
            if (entity == null) {
                return null;
            }
            instream = entity.getContent();
            return StringUtils.getStringFromStream(instream);
        } catch (IOException e) {
            // this means we couldn't transform the response into string, very unlikely
            throw MessagesUtils
                    .createRestClientIOException(RestClientMessageKeys.READ_RESPONSE_BODY_FAILURE.getName(), e);
        } finally {
            if (instream != null) {
                try {
                    instream.close();
                } catch (IOException e) {
                    if (logger.isLoggable(Level.WARNING)) {
                        logger.warning(e.getMessage());
                    }
                }
            }
        }
    }

    private <T> T executeRequest(final HttpRequestBase request,
            final TypeReference<Response<T>> responseTypeReference) throws RestClientException {
        HttpResponse httpResponse = null;
        IOException lastException = null;
        int numOfTrials = DEFAULT_TRIALS_NUM;
        if (HttpGet.METHOD_NAME.equals(request.getMethod())) {
            numOfTrials = GET_TRIALS_NUM;
        }
        for (int i = 0; i < numOfTrials; i++) {
            try {
                httpResponse = httpClient.execute(request);
                lastException = null;
                break;
            } catch (IOException e) {
                if (logger.isLoggable(Level.FINER)) {
                    logger.finer("Execute get request to " + request.getURI() + ". try number " + (i + 1)
                            + " out of " + GET_TRIALS_NUM + ", error is " + e.getMessage());
                }
                lastException = e;
            }
        }
        if (lastException != null) {
            if (logger.isLoggable(Level.WARNING)) {
                logger.warning("Failed executing " + request.getMethod() + " request to " + request.getURI() + " : "
                        + lastException.getMessage());
            }
            throw MessagesUtils.createRestClientIOException(RestClientMessageKeys.EXECUTION_FAILURE.getName(),
                    lastException, request.getURI());
        }
        String url = request.getURI().toString();
        checkForError(httpResponse, url);
        return getResponseObject(responseTypeReference, httpResponse, url);
    }

    private void checkForError(final HttpResponse response, final String requestUri) throws RestClientException {
        StatusLine statusLine = response.getStatusLine();
        final int statusCode = statusLine.getStatusCode();
        String reasonPhrase = statusLine.getReasonPhrase();
        String responseBody;
        if (statusCode != HttpStatus.SC_OK) {
            responseBody = getResponseBody(response);
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE, "[checkForError] - REST request to " + requestUri
                        + "  failed. status code is: " + statusCode + ", response body is: " + responseBody);
            }
            try {
                // this means we managed to read the response
                final Response<Void> entity = new ObjectMapper().readValue(responseBody,
                        new TypeReference<Response<Void>>() {
                        });
                // we also have the response in the proper format.
                // remember, we only got here because some sort of error happened on the server.
                if (logger.isLoggable(Level.FINE)) {
                    logger.log(Level.FINE,
                            "[checkForError] - REST request to " + requestUri
                                    + "  failed. throwing RestClientException: [statusCode " + statusCode
                                    + " reasonPhrase " + reasonPhrase + " defaultMessage " + entity.getMessage()
                                    + " messageCode" + entity.getMessageId() + "]");
                }
                throw MessagesUtils.createRestClientResponseException(statusCode, reasonPhrase, entity.getVerbose(),
                        entity.getMessage(), entity.getMessageId());
            } catch (final IOException e) {

                // this means we got the response, but it is not in the correct format.
                // so some kind of error happened on the spring side.
                if (logger.isLoggable(Level.WARNING)) {
                    logger.log(Level.WARNING, "[checkForError] - failed to read response. responseBody: "
                            + responseBody + ", reasonPhrase:" + reasonPhrase);
                }

                if (statusCode == CloudifyConstants.HTTP_STATUS_NOT_FOUND) {
                    throw MessagesUtils.createRestClientHttpException(e, statusCode, reasonPhrase, responseBody,
                            RestClientMessageKeys.URL_NOT_FOUND.getName(), requestUri);
                } else if (statusCode == CloudifyConstants.HTTP_STATUS_ACCESS_DENIED) {
                    throw MessagesUtils.createRestClientHttpException(e, statusCode, reasonPhrase, responseBody,
                            RestClientMessageKeys.NO_PERMISSION_ACCESS_DENIED.getName());
                } else if (statusCode == CloudifyConstants.HTTP_STATUS_UNAUTHORIZED) {
                    throw MessagesUtils.createRestClientHttpException(e, statusCode, reasonPhrase, responseBody,
                            CloudifyErrorMessages.UNAUTHORIZED.getName(), reasonPhrase, requestUri);
                } else {
                    throw MessagesUtils.createRestClientHttpException(e, statusCode, reasonPhrase, responseBody,
                            RestClientMessageKeys.HTTP_FAILURE.getName(), reasonPhrase, requestUri);
                }
            }
        }
    }

    private <T> T getResponseObject(final TypeReference<Response<T>> typeReference, final HttpResponse httpResponse,
            final String url) throws RestClientIOException, RestClientHttpException {
        final String responseBody = getResponseBody(httpResponse);
        Response<T> response;
        try {
            response = new ObjectMapper().readValue(responseBody, typeReference);
            return response.getResponse();
        } catch (IOException e) {
            if (logger.isLoggable(Level.WARNING)) {
                logger.finer("failed to read the responseBody (of request to " + url + ")." + ", error was "
                        + e.getMessage());
            }
            // this means we got the response, but it is not in the correct format.
            // so some kind of error happened on the spring side.
            StatusLine statusLine = httpResponse.getStatusLine();
            String reasonPhrase = statusLine.getReasonPhrase();
            throw MessagesUtils.createRestClientHttpException(e, statusLine.getStatusCode(), reasonPhrase,
                    responseBody, RestClientMessageKeys.HTTP_FAILURE.getName(), reasonPhrase, url);
        }
    }

    /**
     * Appends the given relative URL to the basic rest-service URL.
     *
     * @param relativeUrl
     *            URL to add to the basic URL
     * @return full URL as as String
     */
    private String getFullUrl(final String relativeUrl) {
        String safeRelativeURL = relativeUrl;
        if (safeRelativeURL.startsWith(FORWARD_SLASH)) {
            safeRelativeURL = safeRelativeURL.substring(1);
        }
        return urlStr + safeRelativeURL;
    }

    /**
     *
     * @param username .
     * @param password .
     */
    public void setCredentials(final String username, final String password) {
        if (StringUtils.notEmpty(username) && StringUtils.notEmpty(password)) {
            httpClient.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY),
                    new UsernamePasswordCredentials(username, password));
        }
    }

    public void shutdown() {
        this.httpClient.getConnectionManager().shutdown();
    }
}