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

com.teamscale.commons.service.client.ServiceClientUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * 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.
 */

package com.teamscale.commons.service.client;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.auth.AuthProtocolState;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.RedirectStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.FormBodyPartBuilder;
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.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.function.FunctionWithException;
import org.conqat.lib.commons.net.TrustAllCertificatesManager;
import org.conqat.lib.commons.string.StringUtils;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.teamscale.commons.service.EMimeType;

/**
 * Provides utility methods for ServiceClient and IdeServiceClient.
 */
public class ServiceClientUtils {

	/**
	 * Sets up the HTTP client and returns it. The caller of this method is responsible for closing the
	 * client when it is no longer needed.
	 */
	public static CloseableHttpClient getHttpClient(ServerDetails serverDetails, boolean disableRedirects) {
		try {
			SSLContext sslContext = SSLContexts.createDefault();
			sslContext.init(null, new TrustManager[] { new TrustAllCertificatesManager() }, new SecureRandom());
			return getHttpClient(serverDetails, sslContext, null, disableRedirects);
		} catch (KeyManagementException e) {
			throw new IllegalStateException("Error creating HTTP client: " + e.getMessage(), e);
		}
	}

	/**
	 * See {@link #getHttpClient(ServerDetails, boolean)}
	 */
	public static CloseableHttpClient getHttpClient(ServerDetails serverDetails) {
		return getHttpClient(serverDetails, false);
	}

	/**
	 * Sets up the HTTP client and returns it. The caller of this method is responsible for closing the
	 * client when it is no longer needed.
	 */
	public static CloseableHttpClient getHttpClient(ServerDetails serverDetails, SSLContext sslContext,
			String userAgent, boolean disableRedirects) {
		SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);

		CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
		credentialsProvider.setCredentials(AuthScope.ANY,
				new UsernamePasswordCredentials(serverDetails.getUsername(), serverDetails.getPassword()));

		RequestConfig.Builder requestBuilder = RequestConfig.custom();
		int timeoutMilliseconds = (int) TimeUnit.SECONDS.toMillis(serverDetails.getTimeoutSeconds());
		requestBuilder = requestBuilder.setConnectTimeout(timeoutMilliseconds);
		requestBuilder = requestBuilder.setSocketTimeout(timeoutMilliseconds);
		requestBuilder = requestBuilder.setConnectionRequestTimeout(timeoutMilliseconds);
		RequestConfig requestConfig = requestBuilder.build();

		HttpClientBuilder clientBuilder = HttpClients.custom() //
				.setSSLSocketFactory(socketFactory) //
				.setDefaultCredentialsProvider(credentialsProvider) //
				.addInterceptorFirst(new PreemptiveAuthenticationInterceptor()) //
				.setDefaultRequestConfig(requestConfig);

		if (disableRedirects) {
			clientBuilder.disableRedirectHandling();
		} else {
			clientBuilder.setRedirectStrategy(getRedirectStrategy());
		}

		if (!StringUtils.isEmpty(userAgent)) {
			clientBuilder.setUserAgent(userAgent);
		}

		return clientBuilder.build();
	}

	private static RedirectStrategy getRedirectStrategy() {
		// We use the LaxRedirectStrategy to also redirect on POST (and DELETE) requests
		// (default is to only redirect GET and HEAD)
		return new LaxRedirectStrategy() {

			// We need to overwrite this method, as the DefaultRedirectStrategy would
			// redirect other request types to a GET request (in the case of a 301, 302 and
			// 303 redirect)
			@Override
			public HttpUriRequest getRedirect(HttpRequest request, HttpResponse response, HttpContext context)
					throws ProtocolException {
				URI uri = getLocationURI(request, response, context);
				String method = request.getRequestLine().getMethod();
				if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
					return new HttpHead(uri);
				} else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
					return new HttpGet(uri);
				} else {
					return checkStatusRedirect(request, response, uri);
				}
			}

			// Here we specify the status codes, where we use the same request method for
			// the redirect
			private HttpUriRequest checkStatusRedirect(HttpRequest request, HttpResponse response, URI uri) {
				int status = response.getStatusLine().getStatusCode();
				if (status == HttpStatus.SC_MOVED_PERMANENTLY //
						|| status == HttpStatus.SC_MOVED_TEMPORARILY //
						|| status == HttpStatus.SC_SEE_OTHER //
						|| status == HttpStatus.SC_TEMPORARY_REDIRECT //
						|| status == SC_PERMANENT_REDIRECT) {
					return RequestBuilder.copy(request).setUri(uri).build();
				} else {
					return new HttpGet(uri);
				}
			}
		};
	}

	/**
	 * Implements preemptive authentication, i.e., will send {@code Authorization} even if unchallenged
	 * by the server.
	 *
	 * @see Stack
	 *      Overflow: Preemptive Basic authentication with Apache HttpClient 4
	 */
	private static class PreemptiveAuthenticationInterceptor implements HttpRequestInterceptor {

		@Override
		public void process(HttpRequest request, HttpContext context) {
			AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
			// We are doing preemptive authentication, i.e., if unchallenged we set Basic
			// authentication credentials
			if (authState.getState() == AuthProtocolState.UNCHALLENGED) {
				CredentialsProvider credentialsProvider = (CredentialsProvider) context
						.getAttribute(HttpClientContext.CREDS_PROVIDER);
				HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
				AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
				Credentials credentials = credentialsProvider.getCredentials(authScope);
				if (credentials != null) {
					authState.update(new BasicScheme(), credentials);
				}
			}
		}
	}

	/**
	 * Creates a multi-part entity that uploads the given files under the given request parameter.
	 */
	public static HttpEntity createMultiPartEntityForFiles(String parameterName, List files) {
		MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
		for (File file : files) {
			FormBodyPart bodyPart = FormBodyPartBuilder
					.create(parameterName, new FileBody(file, ContentType.APPLICATION_OCTET_STREAM)).build();
			multipartEntityBuilder.addPart(bodyPart);
		}
		return multipartEntityBuilder.build();
	}

	/**
	 * Executes a request and handles the response. The request's header is not set to accept any
	 * specific type. Returns null in case of a "not found" HTTP return code.
	 */
	public static  T executeRequest(HttpClient client, HttpRequestBase request, EMimeType contentType,
			FunctionWithException deserializeFunction) throws ServiceCallException {
		if (contentType != null) {
			request.setHeader(HttpHeaders.ACCEPT, contentType.getType());
		}
		try {
			HttpResponse response = client.execute(request);
			int statusCode = response.getStatusLine().getStatusCode();

			switch (statusCode) {
			case HttpStatus.SC_OK:
			case HttpStatus.SC_ACCEPTED:
				return deserializeEntity(response.getEntity(), deserializeFunction);
			case HttpStatus.SC_NO_CONTENT:
			case HttpStatus.SC_NOT_FOUND:
				return null;
			default:
				String responseBody = EntityUtils.toString(response.getEntity());
				throw new ServiceCallException(request.getURI(), response.getStatusLine().getReasonPhrase(), statusCode,
						responseBody);
			}
		} catch (IOException | IllegalArgumentException e) {
			// IllegalArgumentException raised when the server port is illegal. Note that
			// this will also catch IllegalArgumentExceptions thrown for other reasons.
			throw new ServiceCallException("Service call " + request.getURI() + " failed:" + e.getMessage(), e);
		}
	}

	private static  T deserializeEntity(HttpEntity entity,
			FunctionWithException deserializeFunction) throws IOException {
		if (entity == null) {
			return null;
		}
		InputStream content = entity.getContent();
		if (content == null) {
			return null;
		}
		try (Reader reader = new InputStreamReader(content, getCharset(entity))) {
			T result = deserializeFunction.apply(reader);
			// As the reader is getting closed after this method,
			// there is no point in returning it
			CCSMAssert.isFalse(result == reader, "result must not be the same as input reader");
			return result;
		}
	}

	/**
	 * Copies the given array elements into a list, with {@code null} arrays being represented as the
	 * empty list.
	 */
	public static  List arrayToList(T[] array) {
		if (array == null) {
			return Collections.emptyList();
		}
		return ImmutableList.copyOf(array);
	}

	/**
	 * Copies the given array elements into a set, with {@code null} arrays being represented as the
	 * empty set.
	 */
	public static  Set arrayToSet(T[] array) {
		if (array == null) {
			return Collections.emptySet();
		}
		return ImmutableSet.copyOf(array);
	}

	private static Charset getCharset(HttpEntity entity) {
		ContentType contentType = ContentType.get(entity);
		Charset charset = null;
		if (contentType != null) {
			charset = contentType.getCharset();
			if (charset == null) {
				ContentType defaultContentType = ContentType.getByMimeType(contentType.getMimeType());
				if (defaultContentType != null) {
					charset = defaultContentType.getCharset();
				}
			}
		}
		if (charset == null) {
			charset = HTTP.DEF_CONTENT_CHARSET;
		}
		return charset;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy