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

com.offbytwo.jenkins.client.JenkinsHttpClient Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2013 Cosmin Stejerean, Karl Heinz Marbaise, and contributors.
 *
 * Distributed under the MIT license: http://opensource.org/licenses/MIT
 */
package com.offbytwo.jenkins.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.offbytwo.jenkins.client.util.EncodingUtils;
import com.offbytwo.jenkins.client.util.RequestReleasingInputStream;
import com.offbytwo.jenkins.client.validator.HttpResponseValidator;
import com.offbytwo.jenkins.model.BaseModel;
import com.offbytwo.jenkins.model.Crumb;
import com.offbytwo.jenkins.model.ExtractHeader;
import net.sf.json.JSONObject;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import java.util.Map;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import com.offbytwo.jenkins.client.util.ResponseUtils;
import com.offbytwo.jenkins.client.util.UrlUtils;
import static org.apache.commons.lang.StringUtils.isNotBlank;

public class JenkinsHttpClient implements JenkinsHttpConnection {

    private final Logger LOGGER = LoggerFactory.getLogger(getClass());

    private URI uri;
    private CloseableHttpClient client;
    private HttpContext localContext;
    private HttpResponseValidator httpResponseValidator;
    // private HttpResponseContentExtractor contentExtractor;

    private ObjectMapper mapper;
    private String context;

    private String jenkinsVersion;

    public final static String EMPTY_VERSION = "UNKNOWN";

    /**
     * Create an unauthenticated Jenkins HTTP client
     *
     * @param uri Location of the jenkins server (ex. http://localhost:8080)
     * @param client Configured CloseableHttpClient to be used
     */
    public JenkinsHttpClient(URI uri, CloseableHttpClient client) {
        this.context = uri.getPath();
        if (!context.endsWith("/")) {
            context += "/";
        }
        this.uri = uri;
        this.mapper = getDefaultMapper();
        this.client = client;
        this.httpResponseValidator = new HttpResponseValidator();
        // this.contentExtractor = new HttpResponseContentExtractor();
        this.jenkinsVersion = EMPTY_VERSION;
        LOGGER.debug("uri={}", uri.toString());
    }

    /**
     * Create an unauthenticated Jenkins HTTP client
     *
     * @param uri Location of the jenkins server (ex. http://localhost:8080)
     * @param builder Configured HttpClientBuilder to be used
     */
    public JenkinsHttpClient(URI uri, HttpClientBuilder builder) {
        this(uri, builder.build());
    }

    /**
     * Create an unauthenticated Jenkins HTTP client
     *
     * @param uri Location of the jenkins server (ex. http://localhost:8080)
     */
    public JenkinsHttpClient(URI uri) {
        this(uri, HttpClientBuilder.create());
    }

    /**
     * Create an authenticated Jenkins HTTP client
     *
     * @param uri Location of the jenkins server (ex. http://localhost:8080)
     * @param username Username to use when connecting
     * @param password Password or auth token to use when connecting
     */
    public JenkinsHttpClient(URI uri, String username, String password) {
        this(uri, HttpClientBuilder.create(), username, password);
    }

    /**
     * Create an authenticated Jenkins HTTP client
     *
     * @param uri Location of the jenkins server (ex. http://localhost:8080)
     * @param builder Configured HttpClientBuilder to be used
     * @param username Username to use when connecting
     * @param password Password or auth token to use when connecting
     */
    public JenkinsHttpClient(URI uri, HttpClientBuilder builder, String username, String password) {
        this(uri, addAuthentication(builder, uri, username, password));
        if (isNotBlank(username)) {
            localContext = new BasicHttpContext();
            localContext.setAttribute("preemptive-auth", new BasicScheme());
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  T get(String path, Class cls) throws IOException {
        HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path));

        HttpResponse response = client.execute(getMethod, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        try {
            httpResponseValidator.validateResponse(response);
            return objectFromResponse(cls, response);
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(getMethod);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String get(String path) throws IOException {
        HttpGet getMethod = new HttpGet(UrlUtils.toJsonApiUri(uri, context, path));
        HttpResponse response = client.execute(getMethod, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        LOGGER.debug("get({}), version={}, responseCode={}", path, this.jenkinsVersion,
                response.getStatusLine().getStatusCode());
        try {
            httpResponseValidator.validateResponse(response);
            return IOUtils.toString(response.getEntity().getContent());
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(getMethod);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  T getQuietly(String path, Class cls) {
        T value;
        try {
            value = get(path, cls);
            return value;
        } catch (IOException e) {
            LOGGER.debug("getQuietly({}, {})", path, cls.getName(), e);
            // TODO: Is returing null a good idea?
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public InputStream getFile(URI path) throws IOException {
        HttpGet getMethod = new HttpGet(path);
        HttpResponse response = client.execute(getMethod, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        httpResponseValidator.validateResponse(response);
        return new RequestReleasingInputStream(response.getEntity().getContent(), getMethod);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  R post(String path, D data, Class cls) throws IOException {
        return post(path, data, cls, null, true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  R post(String path, D data, Class cls, boolean crumbFlag) throws IOException {
        return post(path, data, cls, null, crumbFlag);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  R post(String path, D data, Class cls, Map fileParams, boolean crumbFlag) throws IOException {
        HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path));
        if (crumbFlag == true) {
            Crumb crumb = getQuietly("/crumbIssuer", Crumb.class);
            if (crumb != null) {
                request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
            }
        }

        if (data != null) {
            String value = mapper.writeValueAsString(data);
            StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_JSON);
            request.setEntity(stringEntity);
        }

        // Prepare file parameters
        if(fileParams != null && !(fileParams.isEmpty())) {
            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);

            for (Map.Entry entry : fileParams.entrySet()) {
                FileBody fileBody = new FileBody(entry.getValue());
                builder.addPart(entry.getKey(), fileBody);
            }

            HttpEntity entity = builder.build();
            request.setEntity(entity);
        }

        HttpResponse response = client.execute(request, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);

        try {
            httpResponseValidator.validateResponse(response);

            if (cls != null) {
                R responseObject;
                if (cls.equals(ExtractHeader.class)) {
                    ExtractHeader location = new ExtractHeader();
                    location.setLocation(response.getFirstHeader("Location").getValue());
                    responseObject = (R) location;
                } else {
                    responseObject = objectFromResponse(cls, response);
                }
                return responseObject;
            } else {
                return null;
            }
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(request);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void post_form(String path, Map data, boolean crumbFlag) throws IOException {
        HttpPost request;
        if (data != null) {
            // https://gist.github.com/stuart-warren/7786892 was slightly
            // helpful here
            List queryParams = Lists.newArrayList();
            for (String param : data.keySet()) {
                queryParams.add(param + "=" + EncodingUtils.encodeParam(data.get(param)));
            }

            queryParams.add("json=" + EncodingUtils.encodeParam(JSONObject.fromObject(data).toString()));
            String value = mapper.writeValueAsString(data);
            StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED);
            request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path) + StringUtils.join(queryParams, "&"));
            request.setEntity(stringEntity);
        } else {
            request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path));
        }

        if (crumbFlag == true) {
            Crumb crumb = get("/crumbIssuer", Crumb.class);
            if (crumb != null) {
                request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
            }
        }

        HttpResponse response = client.execute(request, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);

        try {
            httpResponseValidator.validateResponse(response);
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(request);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public HttpResponse post_form_with_result(String path, List data, boolean crumbFlag) throws IOException {
        HttpPost request;
        if (data != null) {
            UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(data);
            request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path));
            request.setEntity(urlEncodedFormEntity);
        } else {
            request = new HttpPost(UrlUtils.toNoApiUri(uri, context, path));
        }

        if (crumbFlag == true) {
            Crumb crumb = get("/crumbIssuer", Crumb.class);
            if (crumb != null) {
                request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
            }
        }
        HttpResponse response = client.execute(request, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        return response;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String post_xml(String path, String xml_data) throws IOException {
        return post_xml(path, xml_data, true);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String post_xml(String path, String xml_data, boolean crumbFlag) throws IOException {
        HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path));
        if (crumbFlag == true) {
            Crumb crumb = getQuietly("/crumbIssuer", Crumb.class);
            if (crumb != null) {
                request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
            }
        }

        if (xml_data != null) {
            request.setEntity(new StringEntity(xml_data, ContentType.create("text/xml", "utf-8")));
        }
        HttpResponse response = client.execute(request, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        try {
            httpResponseValidator.validateResponse(response);
            return IOUtils.toString(response.getEntity().getContent());
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(request);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String post_text(String path, String textData, boolean crumbFlag) throws IOException {
        return post_text(path, textData, ContentType.DEFAULT_TEXT, crumbFlag);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String post_text(String path, String textData, ContentType contentType, boolean crumbFlag)
            throws IOException {
        HttpPost request = new HttpPost(UrlUtils.toJsonApiUri(uri, context, path));
        if (crumbFlag == true) {
            Crumb crumb = get("/crumbIssuer", Crumb.class);
            if (crumb != null) {
                request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb()));
            }
        }

        if (textData != null) {
            request.setEntity(new StringEntity(textData, contentType));
        }
        HttpResponse response = client.execute(request, localContext);
        jenkinsVersion = ResponseUtils.getJenkinsVersion(response);
        try {
            httpResponseValidator.validateResponse(response);
            return IOUtils.toString(response.getEntity().getContent());
        } finally {
            EntityUtils.consume(response.getEntity());
            releaseConnection(request);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void post(String path) throws IOException {
        post(path, null, null, null, false);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void post(String path, boolean crumbFlag) throws IOException {
        post(path, null, null,null, crumbFlag);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getJenkinsVersion() {
        return this.jenkinsVersion;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isJenkinsVersionSet() {
        return !EMPTY_VERSION.equals(this.jenkinsVersion);
    }

    /**
     * Closes underlying resources. Any I/O errors whilst closing are logged
     * with debug log level Closed instances should no longer be used Closing an
     * already closed instance has no side effects
     */
    @Override
    public void close() {
        try {
            client.close();
        } catch (final IOException ex) {
            LOGGER.debug("I/O exception closing client", ex);
        }
    }

    
    /**
     * Add authentication to supplied builder.
     * @param builder the builder to configure
     * @param uri the server URI
     * @param username the username
     * @param password the password
     * @return the passed in builder
     */
    protected static HttpClientBuilder addAuthentication(final HttpClientBuilder builder, 
            final URI uri, final String username,
            String password) {
        if (isNotBlank(username)) {
            CredentialsProvider provider = new BasicCredentialsProvider();
            AuthScope scope = new AuthScope(uri.getHost(), uri.getPort(), "realm");
            UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);
            provider.setCredentials(scope, credentials);
            builder.setDefaultCredentialsProvider(provider);

            builder.addInterceptorFirst(new PreemptiveAuth());
        }
        return builder;
    }

    
    /**
     * Get the local context.
     * @return context
     */
    protected HttpContext getLocalContext() {
        return localContext;
    }

    
    /**
     * Set the local context.
     * @param localContext the context
     */
    protected void setLocalContext(final HttpContext localContext) {
        this.localContext = localContext;
    }

    
    
    
    
    private  T objectFromResponse(Class cls, HttpResponse response) throws IOException {
        InputStream content = response.getEntity().getContent();
        byte[] bytes = ByteStreams.toByteArray(content);
        T result = mapper.readValue(bytes, cls);
        // TODO: original:
        // T result = mapper.readValue(content, cls);
        result.setClient(this);
        return result;
    }

    private ObjectMapper getDefaultMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(FAIL_ON_UNKNOWN_PROPERTIES);
        return mapper;
    }

    private void releaseConnection(HttpRequestBase httpRequestBase) {
        httpRequestBase.releaseConnection();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy