001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.engine.service.shared.client;
018
019import java.io.File;
020import java.io.IOException;
021import java.net.URI;
022import java.security.KeyManagementException;
023import java.security.SecureRandom;
024import java.util.List;
025
026import javax.net.ssl.SSLContext;
027import javax.net.ssl.TrustManager;
028
029import org.apache.http.HttpEntity;
030import org.apache.http.HttpHeaders;
031import org.apache.http.HttpHost;
032import org.apache.http.HttpResponse;
033import org.apache.http.HttpStatus;
034import org.apache.http.auth.AuthScope;
035import org.apache.http.auth.UsernamePasswordCredentials;
036import org.apache.http.client.AuthCache;
037import org.apache.http.client.CredentialsProvider;
038import org.apache.http.client.HttpClient;
039import org.apache.http.client.config.RequestConfig;
040import org.apache.http.client.methods.HttpRequestBase;
041import org.apache.http.client.protocol.HttpClientContext;
042import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
043import org.apache.http.entity.ContentType;
044import org.apache.http.entity.mime.FormBodyPart;
045import org.apache.http.entity.mime.FormBodyPartBuilder;
046import org.apache.http.entity.mime.MultipartEntityBuilder;
047import org.apache.http.entity.mime.content.ByteArrayBody;
048import org.apache.http.entity.mime.content.FileBody;
049import org.apache.http.impl.auth.BasicScheme;
050import org.apache.http.impl.client.BasicAuthCache;
051import org.apache.http.impl.client.BasicCredentialsProvider;
052import org.apache.http.impl.client.CloseableHttpClient;
053import org.apache.http.impl.client.HttpClients;
054import org.apache.http.ssl.SSLContexts;
055import org.apache.http.util.EntityUtils;
056import org.conqat.engine.service.shared.EMimeType;
057import org.conqat.lib.commons.collections.CollectionUtils;
058import org.conqat.lib.commons.net.TrustAllCertificatesManager;
059import org.conqat.lib.commons.resources.Resource;
060
061/**
062 * Provides utility methods for ServiceClient and IdeServiceClient.
063 */
064public class ServiceClientUtils {
065
066        /**
067         * Sets up the HTTP client and returns it. The caller of this method is
068         * responsible for closing the client when it is no longer needed.
069         */
070        public static CloseableHttpClient getHttpClient(ServerDetails serverDetails) throws ServiceCallException {
071                try {
072                        SSLContext sslContext = SSLContexts.createDefault();
073                        sslContext.init(null, new TrustManager[] { new TrustAllCertificatesManager() }, new SecureRandom());
074                        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
075                        CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
076                        credentialsProvider.setCredentials(AuthScope.ANY,
077                                        new UsernamePasswordCredentials(serverDetails.getUsername(), serverDetails.getPassword()));
078                        RequestConfig.Builder requestBuilder = RequestConfig.custom();
079                        int timeoutMilliseconds = serverDetails.getTimeoutSeconds() * 1000;
080                        requestBuilder = requestBuilder.setConnectTimeout(timeoutMilliseconds);
081                        requestBuilder = requestBuilder.setSocketTimeout(timeoutMilliseconds);
082                        requestBuilder = requestBuilder.setConnectionRequestTimeout(timeoutMilliseconds);
083                        return HttpClients.custom().setSSLSocketFactory(socketFactory)
084                                        .setDefaultRequestConfig(requestBuilder.build()).setDefaultCredentialsProvider(credentialsProvider)
085                                        .build();
086                } catch (KeyManagementException e) {
087                        throw new ServiceCallException("Error creating HTTP client: " + e.getMessage(), e);
088                }
089        }
090
091        /**
092         * Creates a HTTP context for preemptive basic authentication, i.e. without
093         * waiting for a server authentication request.</a>.
094         */
095        public static HttpClientContext createPreemptiveAuthContext(HttpRequestBase request) {
096                URI uri = request.getURI();
097                AuthCache authCache = new BasicAuthCache();
098                authCache.put(new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme()), new BasicScheme());
099                HttpClientContext context = HttpClientContext.create();
100                context.setAuthCache(authCache);
101                return context;
102        }
103
104        /**
105         * Creates a multi-part entity that uploads the given files under the given
106         * request parameter.
107         */
108        public static HttpEntity createMultiPartEntity(String parameterName, List<Resource> files) {
109                MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
110                for (Resource file : files) {
111                        FormBodyPart bodyPart = FormBodyPartBuilder.create(parameterName,
112                                        new ByteArrayBody(file.getAsByteArray(), ContentType.APPLICATION_OCTET_STREAM, file.getName()))
113                                        .build();
114                        multipartEntityBuilder.addPart(bodyPart);
115                }
116                return multipartEntityBuilder.build();
117        }
118
119        /**
120         * Creates a multi-part entity that uploads the given files under the given
121         * request parameter.
122         */
123        public static HttpEntity createMultiPartEntityForFiles(String parameterName, List<File> files) {
124                MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create();
125                for (File file : files) {
126                        FormBodyPart bodyPart = FormBodyPartBuilder
127                                        .create(parameterName, new FileBody(file, ContentType.APPLICATION_OCTET_STREAM)).build();
128                        multipartEntityBuilder.addPart(bodyPart);
129                }
130                return multipartEntityBuilder.build();
131        }
132
133        /**
134         * Executes a request and handles the response. The request's header is not set
135         * to accept any specific type. Returns null in case of a "not found" HTTP
136         * return code.
137         */
138        public static <T> T executeRequest(HttpClient client, HttpRequestBase request, EMimeType contentType,
139                        CollectionUtils.FunctionWithException<String, T, IOException> deserializeFunction)
140                        throws ServiceCallException {
141                if (contentType != null) {
142                        request.setHeader(HttpHeaders.ACCEPT, contentType.getType());
143                }
144                try {
145                        HttpResponse response = client.execute(request, ServiceClientUtils.createPreemptiveAuthContext(request));
146                        int statusCode = response.getStatusLine().getStatusCode();
147
148                        switch (statusCode) {
149                        case HttpStatus.SC_OK:
150                                return deserializeFunction.apply(EntityUtils.toString(response.getEntity()));
151                        case HttpStatus.SC_NO_CONTENT:
152                        case HttpStatus.SC_NOT_FOUND:
153                                return null;
154                        default:
155                                String responseBody = EntityUtils.toString(response.getEntity());
156                                throw new ServiceCallException(
157                                                "Service returned with status code " + statusCode + ". "
158                                                                + response.getStatusLine().getReasonPhrase() + "\n" + responseBody,
159                                                statusCode, responseBody);
160                        }
161                } catch (IOException | IllegalArgumentException e) {
162                        // IllegalArgumentException raised when the server port is illegal. Note that
163                        // this will also catch IllegalArgumentExceptions thrown for other reasons.
164                        throw new ServiceCallException("Service call " + request.getURI() + " failed:" + e.getMessage(), e);
165                }
166        }
167}