View Javadoc
1   package fr.ifremer.quadrige2.core.dao.technical.http;
2   
3   /*-
4    * #%L
5    * Quadrige2 Core :: Quadrige2 Core Shared
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.base.Joiner;
27  import com.google.common.base.Preconditions;
28  import com.google.gson.Gson;
29  import com.google.gson.GsonBuilder;
30  import fr.ifremer.quadrige2.core.dao.technical.gson.Gsons;
31  import fr.ifremer.quadrige2.core.exception.BadUpdateDtException;
32  import fr.ifremer.quadrige2.core.exception.DataLockedException;
33  import fr.ifremer.quadrige2.core.exception.DeleteForbiddenException;
34  import fr.ifremer.quadrige2.core.exception.Quadrige2TechnicalException;
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.lang3.builder.ToStringBuilder;
38  import org.apache.commons.lang3.builder.ToStringStyle;
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.http.Header;
42  import org.apache.http.HttpHeaders;
43  import org.apache.http.HttpResponse;
44  import org.apache.http.client.HttpClient;
45  import org.apache.http.client.methods.HttpUriRequest;
46  import org.apache.http.client.utils.URIBuilder;
47  import org.apache.http.util.EntityUtils;
48  import org.nuiton.i18n.I18n;
49  import org.nuiton.jaxx.application.type.ApplicationProgressionModel;
50  
51  import java.io.*;
52  import java.lang.reflect.Type;
53  import java.net.URI;
54  import java.net.URISyntaxException;
55  import java.nio.charset.StandardCharsets;
56  import java.util.List;
57  import java.util.regex.Matcher;
58  import java.util.regex.Pattern;
59  
60  /**
61   * <p>HttpHelper class.</p>
62   */
63  public class HttpHelper {
64  
65      private static final Log log = LogFactory.getLog(HttpHelper.class);
66  
67      private final static Pattern PATTERN_INTERNAL_SERVER_ERROR_MESSAGE = Pattern.compile("\\\"\\[([0-9]+)\\] .*");
68      
69         /**
70       * <p>executeRequest.</p>
71       *
72       * @param httpClient a {@link org.apache.http.client.HttpClient} object.
73       * @param request a {@link org.apache.http.client.methods.HttpUriRequest} object.
74       */
75      @SuppressWarnings("unchecked")
76      public static void executeRequest(HttpClient httpClient, HttpUriRequest request)  {
77          executeRequest(httpClient, request, null, null);
78      }
79  
80      /**
81       * <p>executeRequest.</p>
82       *
83       * @param httpClient a {@link org.apache.http.client.HttpClient} object.
84       * @param request a {@link org.apache.http.client.methods.HttpUriRequest} object.
85       * @param gson a {@link com.google.gson.Gson} object.
86       * @param resultClass a {@link java.lang.Class} object.
87       * @param <T> a T object.
88       * @return a T object.
89       */
90      @SuppressWarnings("unchecked")
91      public static <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Gson gson, Class<? extends T> resultClass)  {
92          T result = null;
93  
94          if (log.isDebugEnabled()) {
95              log.debug("Executing request : " + request.getRequestLine());
96          }
97  
98          // Setting user language, from I18n locale
99          if (I18n.getDefaultLocale() != null) {
100             request.setHeader(HttpHeaders.ACCEPT_LANGUAGE, I18n.getDefaultLocale().getLanguage());
101         }
102         else if (log.isWarnEnabled()) {
103             log.warn(String.format("I18n not initialized properly: no default locale found ! Could not set HTTP header [%s] with user language", HttpHeaders.ACCEPT_LANGUAGE));
104         }
105 
106         try {
107             HttpResponse response = httpClient.execute(request);
108 
109             if (log.isDebugEnabled()) {
110                 log.debug("Received response : " + response.getStatusLine());
111             }
112 
113             switch (response.getStatusLine().getStatusCode()) {
114             case HttpStatus.SC_OK: {
115                 if (gson != null && resultClass != null) {
116                     result = (T) parseResponse(response, gson, resultClass);
117                 }
118                 EntityUtils.consume(response.getEntity());
119                 break;
120             }
121             case HttpStatus.SC_UNAUTHORIZED:
122             case HttpStatus.SC_FORBIDDEN:
123                 throw new HttpAuthenticationException(I18n.t("quadrige2.error.authenticate.unauthorized"));
124             case HttpStatus.SC_NOT_FOUND:
125                 throw new HttpNotFoundException(I18n.t("quadrige2.error.notFound"));
126             default:
127                 throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.authenticate.failed", response.getStatusLine().toString()));
128             }
129 
130         }
131         catch (InterruptedIOException e) {
132             throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.timeout"), e);
133         }
134         catch (IOException e) {
135             throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.connect"), e);
136         }
137 
138         return result;
139     }
140 
141 
142     /**
143      * <p>executeRequest.</p>
144      *
145      * @param httpClient a {@link org.apache.http.client.HttpClient} object.
146      * @param request a {@link org.apache.http.client.methods.HttpUriRequest} object.
147      * @param gson a {@link com.google.gson.Gson} object.
148      * @param type a {@link java.lang.reflect.Type} object.
149      * @param <T> a T object.
150      * @return a T object.
151      */
152     @SuppressWarnings("unchecked")
153     public static <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Gson gson, Type type)  {
154         T result = null;
155 
156         if (log.isDebugEnabled()) {
157             log.debug("Executing request : " + request.getRequestLine());
158         }
159 
160         try {
161             HttpResponse response = httpClient.execute(request);
162 
163             if (log.isDebugEnabled()) {
164                 log.debug("Received response : " + response.getStatusLine());
165             }
166 
167             switch (response.getStatusLine().getStatusCode()) {
168                 case HttpStatus.SC_OK: {
169                     if (gson != null && type != null) {
170                         result = parseResponse(response, gson, type);
171                     }
172                     EntityUtils.consume(response.getEntity());
173                     break;
174                 }
175 
176                 case HttpStatus.SC_INTERNAL_SERVER_ERROR: {
177                     String errorMessage = (String)parseResponse(response, gson, String.class);
178                     switch (getInternalServerErrorCode(errorMessage)) {
179                         case HttpStatus.SC_BAD_UPDATE_DT:
180                             throw new BadUpdateDtException(errorMessage);
181                         case HttpStatus.SC_DATA_LOCKED:
182                             throw new DataLockedException(errorMessage);
183                         case HttpStatus.SC_DELETE_FORBIDDEN:
184                             List<String> objectIds = null;
185                             // Get specific header for this exception
186                             Header header = response.getFirstHeader(fr.ifremer.quadrige2.core.dao.technical.http.HttpHeaders.HH_DELETE_FORBIDDEN_OBJECT_IDS);
187                             if (gson != null && header != null) {
188                                 objectIds = gson.fromJson(header.getValue(), List.class);
189                             }
190                             throw new DeleteForbiddenException(errorMessage, objectIds);
191                         default:
192                             throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.server.internal", errorMessage));
193                     }
194                 }
195 
196                 case HttpStatus.SC_UNAUTHORIZED:
197                 case HttpStatus.SC_FORBIDDEN:
198                     throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.authenticate.unauthorized"));
199                 default:
200                     throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.authenticate.failed", response.getStatusLine().toString()));
201             }
202 
203         }
204         catch (InterruptedIOException e) {
205             throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.timeout"), e);
206         }
207         catch (IOException e) {
208             throw new Quadrige2TechnicalException(I18n.t("quadrige2.error.connect"), e);
209         }
210 
211         return result;
212     }
213 
214     /**
215      * <p>getAppendedPath.</p>
216      *
217      * @param baseURI a {@link java.net.URI} object.
218      * @param path a {@link java.lang.String} object.
219      * @return a {@link java.net.URI} object.
220      * @throws java.net.URISyntaxException if any.
221      */
222     public static URI getAppendedPath(URI baseURI, String... path) throws URISyntaxException {
223 
224         String pathToAppend = Joiner.on('/').skipNulls().join(path);
225 
226         URIBuilder builder = new URIBuilder(baseURI);
227         builder.setPath(baseURI.getPath() + pathToAppend);
228         return builder.build();
229 
230     }
231 
232     /**
233      * <p>executeDownloadFileRequest.</p>
234      *
235      * @param httpClient a {@link org.apache.http.client.HttpClient} object.
236      * @param request a {@link org.apache.http.client.methods.HttpUriRequest} object.
237      * @param progressionModel a {@link org.nuiton.jaxx.application.type.ApplicationProgressionModel} object.
238      * @param outputFile a {@link java.io.File} object.
239      * @throws java.io.IOException if any.
240      */
241     public static void executeDownloadFileRequest(HttpClient httpClient, HttpUriRequest request, ApplicationProgressionModel progressionModel, File outputFile) throws IOException {
242 
243         Preconditions.checkNotNull(httpClient);
244         Preconditions.checkNotNull(request);
245         Preconditions.checkNotNull(progressionModel);
246         Preconditions.checkNotNull(outputFile);
247 
248         if (!outputFile.createNewFile()) {
249             throw new Quadrige2TechnicalException(String.format("File %s already exists", outputFile.getAbsolutePath()));
250         }
251 
252 
253         OutputStream os = null;
254         InputStream is = null;
255 
256         try {
257 
258             HttpResponse response = httpClient.execute(request);
259 
260             if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
261                 // remove the newly created file
262                 FileUtils.deleteQuietly(outputFile);
263                 // Map error 404
264                 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
265                     throw new HttpNotFoundException(
266                             String.format("error while downloading file from [%s]; server responds: %s", request.getURI(), response.getStatusLine().getReasonPhrase()));
267                 }
268                 throw new Quadrige2TechnicalException(
269                         String.format("error while downloading file from [%s]; server responds: %s", request.getURI(), response.getStatusLine().getReasonPhrase()));
270             }
271 
272             os = new BufferedOutputStream(new FileOutputStream(outputFile));
273             is = new BufferedInputStream(response.getEntity().getContent());
274 
275             // initialize progression
276             long contentLength = response.getEntity().getContentLength();
277             progressionModel.setTotal((int)contentLength);
278 
279             long count = 0L;
280             int bytesReaded;
281             byte[] bytes = new byte[4096];
282 
283             while ((bytesReaded = is.read(bytes)) != -1) {
284 
285                 // copy
286                 os.write(bytes, 0, bytesReaded);
287 
288                 // progresion increment
289                 count += bytesReaded;
290                 progressionModel.setCurrent((int)count);
291 
292             }
293 
294         }
295         finally {
296             IOUtils.closeQuietly(is);
297             IOUtils.closeQuietly(os);
298         }
299     }
300 
301     /**
302      * <p>getInternalServerErrorCode.</p>
303      *
304      * @param message a {@link java.lang.String} object.
305      * @return a int.
306      */
307     public static int getInternalServerErrorCode(String message) {
308         if (message != null) {
309             Matcher matcher = PATTERN_INTERNAL_SERVER_ERROR_MESSAGE.matcher(message);
310             if (matcher.matches()) {
311                 String errorCodeAsStr = matcher.group(1);
312                 return Integer.parseInt(errorCodeAsStr);
313             }
314         }
315         // By default, return error 500
316         return HttpStatus.SC_INTERNAL_SERVER_ERROR;
317     }
318 
319 
320     /**
321      * <p>getInternalServerErrorMessage.</p>
322      *
323      * @param errorCode a int.
324      * @param message a {@link java.lang.String} object.
325      * @return a {@link java.lang.String} object.
326      */
327     public static String getInternalServerErrorMessage(int errorCode, String message) {
328         return String.format("[%s] %s", errorCode, message);
329     }
330 
331     /* -- Internal methods -- */
332 
333     /**
334      * <p>parseResponse.</p>
335      *
336      * @param response a {@link org.apache.http.HttpResponse} object.
337      * @param gson a {@link com.google.gson.Gson} object.
338      * @param ResultClass a {@link java.lang.Class} object.
339      * @return a {@link java.lang.Object} object.
340      * @throws java.io.IOException if any.
341      */
342     protected static Object parseResponse(HttpResponse response, Gson gson, Class<?> ResultClass) throws IOException {
343         Object result;
344         
345         boolean stringOutput = ResultClass.equals(String.class);
346         
347         // If trace enable, log the response before parsing
348         if (stringOutput) {
349             InputStream content = null;
350             try {
351                 content = response.getEntity().getContent();
352                 String stringContent = getContentAsString(content);
353                 if (log.isDebugEnabled()) {
354                     log.debug("Parsing response:\n" + stringContent);
355                 }
356 
357                 return stringContent;
358             }
359             finally {
360                 if (content!= null) {
361                     content.close();
362                 }
363             }
364         }
365         
366         // trace not enable
367         else {
368             InputStream content = null;
369             try {
370                 content = response.getEntity().getContent();
371                 Reader reader = new InputStreamReader(content, StandardCharsets.UTF_8);
372                 result = gson.fromJson(reader, ResultClass);                
373             }
374             finally {
375                 if (content!= null) {
376                     content.close();
377                 }
378             }
379         }
380         
381 
382         if (result == null) {
383             throw new Quadrige2TechnicalException("emptyResponse");
384         }
385 
386         if (log.isTraceEnabled()) {
387             log.trace("response: " + ToStringBuilder.reflectionToString(result, ToStringStyle.SHORT_PREFIX_STYLE));
388         }
389 
390         return result;
391     }
392 
393     /**
394      * <p>parseResponse.</p>
395      *
396      * @param response a {@link org.apache.http.HttpResponse} object.
397      * @param gson a {@link com.google.gson.Gson} object.
398      * @param typeOfT a {@link java.lang.reflect.Type} object.
399      * @param <T> a T object.
400      * @return a T object.
401      * @throws java.io.IOException if any.
402      */
403     protected static <T> T parseResponse(HttpResponse response, Gson gson, Type typeOfT) throws IOException {
404         T result;
405 
406         InputStream content = null;
407         try {
408             content = response.getEntity().getContent();
409             Reader reader = new InputStreamReader(content, StandardCharsets.UTF_8);
410             result = gson.fromJson(reader, typeOfT);
411         }
412         finally {
413             if (content!= null) {
414                 content.close();
415             }
416         }
417 
418         if (result == null) {
419             throw new Quadrige2TechnicalException("emptyResponse");
420         }
421 
422         if (log.isTraceEnabled()) {
423             log.trace("response: " + ToStringBuilder.reflectionToString(result, ToStringStyle.SHORT_PREFIX_STYLE));
424         }
425 
426         return result;
427     }
428 
429     /**
430      * <p>getContentAsString.</p>
431      *
432      * @param content a {@link java.io.InputStream} object.
433      * @return a {@link java.lang.String} object.
434      * @throws java.io.IOException if any.
435      */
436     protected static String getContentAsString(InputStream content) throws IOException {
437         Reader reader = new InputStreamReader(content, StandardCharsets.UTF_8);
438         StringBuilder result = new StringBuilder();
439         char[] buf = new char[64];
440         int len = 0;
441         while((len = reader.read(buf)) != -1) {
442             result.append(buf, 0, len);
443         }
444         return result.toString();
445     }
446     
447 }