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

com.atlassian.bamboo.specs.util.RestHelper Maven / Gradle / Ivy

There is a newer version: 10.1.0
Show newest version
package com.atlassian.bamboo.specs.util;

import com.atlassian.bamboo.specs.api.exceptions.PropertiesValidationException;
import com.atlassian.bamboo.specs.exceptions.BambooSpecsRestRequestException;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.StreamSupport;

public class RestHelper {
    private static final Logger log = Logger.getLogger(RestHelper.class);

    private static final String MIME_TYPE_APPLICATION_X_YAML = "application/x-yaml";

    private static String sendRequest(final HttpRequestBase request,
                                      final AuthenticationProvider authenticationProvider) throws IOException {
        final Registry registry = RegistryBuilder.create().register(AuthSchemes.DIGEST, new DigestSchemeFactory()).build();
        log.trace("%s - sending", request);

        try {
            authenticationProvider.authenticate(request);
        } catch (final AuthenticationException exception) {
            throw new RuntimeException(exception);
        }

        try (CloseableHttpClient httpClient = HttpClients.custom().setDefaultAuthSchemeRegistry(registry).build()) {
            final ResponseHandler responseHandler = response -> {
                final int status = response.getStatusLine().getStatusCode();
                final Optional responseEntityAsString = getResponseEntityAsString(response);
                if (status >= 200 && status < 300) {
                    log.trace("%s - successful, status: %d", request, status);
                    responseEntityAsString.ifPresent(log::trace);
                    return "Result OK: " + responseEntityAsString.orElse("");
                } else {
                    final Optional errorMessage = responseEntityAsString.flatMap(
                            RestHelper::tryGetErrorMessageFromResponse
                    );
                    log.trace("%s - failed, status: %d, error: %s", request, status, errorMessage.orElse(""));
                    responseEntityAsString.ifPresent(log::trace);
                    throw new BambooSpecsRestRequestException(status, errorMessage.orElse(null), responseEntityAsString.orElse(null));
                }
            };
            return httpClient.execute(request, responseHandler);
        }
    }

    /**
     * Try to extract an error message as a flat String from a JSON response.
     * 

* The JSON may come in different formats, and this method will attempt to extract message for all scenarios, e.g.: *

{@code
     * { "message": "plan with key FOO-BAR not found" }
     * }
* or *
{@code
     * {
     *     "errors": [ "invalid configuration of plan FOO-BAR" ],
     *     "fieldErrors": {
     *         "name": [ "value is required" ]
     *     }
     * }
     * }
* The returned flattened error messages for the above examples would be respectively: *
    *
  • plan with key FOO-BAR not found
  • *
  • invalid configuration of plan FOO-BAR; name: value is required
  • *
* * @param jsonAsString response in JSON format (if it's not a valid JSON object, this method will not fail, but will * simply return no error message) * @return error message extracted from the HTTP response or empty optional if extracting failed */ @NotNull static Optional tryGetErrorMessageFromResponse(final String jsonAsString) { try { final JsonObject jsonObject = new JsonParser().parse(jsonAsString).getAsJsonObject(); String message = null; if (jsonObject.has("message")) { message = jsonObject.get("message").getAsString(); // trim Bamboo server exception class name if it's present final String prefixToRemove = PropertiesValidationException.class.getName() + ":"; if (message.startsWith(prefixToRemove)) { message = StringUtils.substringAfter(message, prefixToRemove).trim(); } } else if (jsonObject.has("errors") && jsonObject.has("fieldErrors")) { message = String.join("; ", extractErrorsFromRestErrorCollection(jsonObject)); } return Optional.ofNullable(message); } catch (final Exception e) { return Optional.empty(); } } /** * Extract list of errors from a JSON object representing Bamboo REST error collection. The JSON should be in * format: *
{@code
     * {
     *     "errors": [ ... ],
     *     "fieldErrors": {
     *         "field1: [ ... ],
     *         "field2: [ ... ],
     *         ...
     *     }
     * }
     * }
* * @param jsonObject JSON object representing a serialised REST error collection from Bamboo * @return list of errors extracted from the JSON response; field errors will be flattened to * "fieldName: errorMessage". */ private static List extractErrorsFromRestErrorCollection(JsonObject jsonObject) { final List messages = new ArrayList<>(); final JsonArray errors = jsonObject.get("errors").getAsJsonArray(); StreamSupport.stream(errors.spliterator(), false) .map(JsonElement::getAsString) .forEach(messages::add); final Set> fieldErrors = jsonObject.get("fieldErrors") .getAsJsonObject() .entrySet(); fieldErrors.forEach((fieldErrorEntry) -> { final String fieldName = fieldErrorEntry.getKey(); final JsonArray fieldErrorsArray = fieldErrorEntry.getValue().getAsJsonArray(); StreamSupport.stream(fieldErrorsArray.spliterator(), false) .map(JsonElement::getAsString) .forEach(fieldError -> messages.add(fieldName + ": " + fieldError)); }); return messages; } private static Optional getResponseEntityAsString(HttpResponse response) throws IOException { final HttpEntity entity = response.getEntity(); return entity != null ? Optional.of(EntityUtils.toString(entity)) : Optional.empty(); } public String post(final URI uri, final AuthenticationProvider authenticationProvider, final String yamlContent) throws IOException { log.trace("Sending the following content to %s via POST:\n%s", uri, yamlContent); final HttpPost httpPost = new HttpPost(uri); setYamlEntity(httpPost, yamlContent); return sendRequest(httpPost, authenticationProvider); } public String put(final URI uri, final AuthenticationProvider authenticationProvider, final String yamlContent) throws IOException { log.trace("Sending the following content to %s via PUT:\n%s", uri, yamlContent); final HttpPut httpPut = new HttpPut(uri); setYamlEntity(httpPut, yamlContent); return sendRequest(httpPut, authenticationProvider); } private void setYamlEntity(final HttpEntityEnclosingRequest request, final String yamlContent) { final NameValuePair version = new BasicNameValuePair("version", BambooSpecVersion.getModelVersion()); final ContentType contentType = ContentType.create(MIME_TYPE_APPLICATION_X_YAML, StandardCharsets.UTF_8) .withParameters(version); final StringEntity entity = new StringEntity(yamlContent, contentType); request.setEntity(entity); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy