View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013 - 2023, Tapio Rautonen
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  package org.eluder.coveralls.maven.plugin.httpclient;
25  
26  import com.fasterxml.jackson.core.JsonProcessingException;
27  import com.fasterxml.jackson.databind.ObjectMapper;
28  
29  import java.io.BufferedReader;
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStreamReader;
33  import java.security.Provider;
34  import java.security.Security;
35  
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.http.HttpEntity;
38  import org.apache.http.HttpResponse;
39  import org.apache.http.HttpStatus;
40  import org.apache.http.client.HttpClient;
41  import org.apache.http.client.methods.HttpPost;
42  import org.apache.http.entity.ContentType;
43  import org.apache.http.entity.mime.HttpMultipartMode;
44  import org.apache.http.entity.mime.MultipartEntityBuilder;
45  import org.eluder.coveralls.maven.plugin.ProcessingException;
46  import org.eluder.coveralls.maven.plugin.domain.CoverallsResponse;
47  
48  public class CoverallsClient {
49  
50      static {
51          for (Provider provider : Security.getProviders()) {
52              if (provider.getName().startsWith("SunPKCS11")) {
53                  Security.removeProvider(provider.getName());
54              }
55          }
56      }
57  
58      private static final String FILE_NAME = "coveralls.json";
59      private static final ContentType MIME_TYPE = ContentType.create("application/octet-stream", "utf-8");
60  
61      private final String coverallsUrl;
62      private final HttpClient httpClient;
63      private final ObjectMapper objectMapper;
64  
65      public CoverallsClient(final String coverallsUrl) {
66          this(coverallsUrl, new HttpClientFactory(coverallsUrl).create(), new ObjectMapper());
67      }
68  
69      public CoverallsClient(final String coverallsUrl, final HttpClient httpClient, final ObjectMapper objectMapper) {
70          this.coverallsUrl = coverallsUrl;
71          this.httpClient = httpClient;
72          this.objectMapper = objectMapper;
73      }
74  
75      public CoverallsResponse submit(final File file) throws ProcessingException, IOException {
76          HttpEntity entity = MultipartEntityBuilder.create()
77                  .setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
78                  .addBinaryBody("json_file", file, MIME_TYPE, FILE_NAME)
79                  .build();
80          HttpPost post = new HttpPost(coverallsUrl);
81          post.setEntity(entity);
82          HttpResponse response = httpClient.execute(post);
83          return parseResponse(response);
84      }
85  
86      private CoverallsResponse parseResponse(final HttpResponse response) throws ProcessingException, IOException {
87          HttpEntity entity = response.getEntity();
88          ContentType contentType = ContentType.getOrDefault(entity);
89          if (contentType.getCharset() == null) {
90              throw new ProcessingException(getResponseErrorMessage(response, "Response doesn't contain Content-Type header"));
91          }
92  
93          if (response.getStatusLine().getStatusCode() >= HttpStatus.SC_INTERNAL_SERVER_ERROR) {
94              throw new IOException(getResponseErrorMessage(response, "Coveralls API internal error"));
95          }
96  
97          try (BufferedReader reader = new BufferedReader(new InputStreamReader(entity.getContent(), contentType.getCharset()))) {
98              CoverallsResponse cr = objectMapper.readValue(reader, CoverallsResponse.class);
99              if (cr.isError()) {
100                 throw new ProcessingException(getResponseErrorMessage(response, cr.getMessage()));
101             }
102             return cr;
103         } catch (JsonProcessingException ex) {
104             throw new ProcessingException(getResponseErrorMessage(response, ex.getMessage()), ex);
105         }
106     }
107 
108     private String getResponseErrorMessage(final HttpResponse response, final String message) {
109         int status = response.getStatusLine().getStatusCode();
110         String reason = response.getStatusLine().getReasonPhrase();
111         String errorMessage = "Report submission to Coveralls API failed with HTTP status " + status + ":";
112         if (StringUtils.isNotBlank(reason)) {
113             errorMessage += " " + reason;
114         }
115         if (StringUtils.isNotBlank(message)) {
116             errorMessage += " (" + message + ")";
117         }
118         return errorMessage;
119     }
120 }