001/* 002 * (C) Copyright 2015-2018 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Thibaud Arguillere <[email protected]> 018 * Vladimir Pasquier <[email protected]> 019 * Ricardo Dias <[email protected]> 020 */ 021package org.nuxeo.ecm.automation.features; 022 023import java.io.IOException; 024import java.io.UnsupportedEncodingException; 025import java.nio.charset.Charset; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029 030import javax.ws.rs.core.HttpHeaders; 031import javax.ws.rs.core.MediaType; 032import javax.ws.rs.core.MultivaluedMap; 033 034import org.apache.commons.lang3.StringUtils; 035import org.nuxeo.ecm.automation.context.ContextHelper; 036import org.nuxeo.ecm.core.api.Blob; 037import org.nuxeo.ecm.core.api.Blobs; 038import org.nuxeo.ecm.core.api.NuxeoException; 039import org.nuxeo.ecm.core.api.impl.blob.StringBlob; 040 041import com.fasterxml.jackson.databind.ObjectMapper; 042import com.sun.jersey.api.client.Client; 043import com.sun.jersey.api.client.ClientResponse; 044import com.sun.jersey.api.client.WebResource; 045import com.sun.jersey.api.client.config.ClientConfig; 046import com.sun.jersey.api.client.config.DefaultClientConfig; 047import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; 048import com.sun.jersey.core.util.Base64; 049import com.sun.jersey.core.util.MultivaluedMapImpl; 050import com.sun.jersey.multipart.MultiPart; 051import com.sun.jersey.multipart.impl.MultiPartWriter; 052 053/** 054 * @since 7.3 055 */ 056public class HTTPHelper implements ContextHelper { 057 058 protected static volatile ObjectMapper mapper = new ObjectMapper(); 059 060 private static final Integer TIMEOUT = 1000 * 60 * 5; // 5min 061 062 private static final String HTTP_CONTENT_DISPOSITION = "Content-Disposition"; 063 064 public Blob call(String username, String password, String requestType, String path) throws IOException { 065 return call(username, password, requestType, path, null, null, null, null); 066 } 067 068 public Blob call(String username, String password, String requestType, String path, Map<String, String> headers) 069 throws IOException { 070 return call(username, password, requestType, path, null, null, null, headers); 071 } 072 073 public Blob call(String username, String password, String requestType, String path, MultiPart mp) 074 throws IOException { 075 return call(username, password, requestType, path, null, null, mp, null); 076 } 077 078 public Blob call(String username, String password, String requestType, String path, MultiPart mp, 079 Map<String, String> headers) throws IOException { 080 return call(username, password, requestType, path, null, null, mp, headers); 081 } 082 083 public Blob call(String username, String password, String requestType, String path, 084 MultivaluedMap<String, String> queryParams) throws IOException { 085 return call(username, password, requestType, path, null, queryParams, null, null); 086 } 087 088 public Blob call(String username, String password, String requestType, String path, Object data) 089 throws IOException { 090 return call(username, password, requestType, path, data, null, null, null); 091 } 092 093 public Blob call(String username, String password, String requestType, String path, Object data, 094 Map<String, String> headers) throws IOException { 095 return call(username, password, requestType, path, data, null, null, headers); 096 } 097 098 public Blob call(String username, String password, String requestType, String url, Object data, 099 MultivaluedMap<String, String> queryParams, MultiPart mp, Map<String, String> headers) throws IOException { 100 ClientConfig config = new DefaultClientConfig(); 101 config.getClasses().add(MultiPartWriter.class); 102 Client client = Client.create(config); 103 client.setConnectTimeout(TIMEOUT); 104 client.setReadTimeout(TIMEOUT); 105 if (username != null && password != null) { 106 client.addFilter(new HTTPBasicAuthFilter(username, password)); 107 } 108 109 WebResource wr = client.resource(url); 110 111 if (queryParams != null && !queryParams.isEmpty()) { 112 wr = wr.queryParams(queryParams); 113 } 114 WebResource.Builder builder; 115 builder = wr.accept(MediaType.APPLICATION_JSON); 116 if (mp != null) { 117 builder = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE); 118 } 119 120 // Adding some headers if needed 121 if (headers != null && !headers.isEmpty()) { 122 for (String headerKey : headers.keySet()) { 123 builder.header(headerKey, headers.get(headerKey)); 124 } 125 } 126 ClientResponse response; 127 try { 128 switch (requestType) { 129 case "HEAD": 130 case "GET": 131 response = builder.get(ClientResponse.class); 132 break; 133 case "POST": 134 if (mp != null) { 135 response = builder.post(ClientResponse.class, mp); 136 } else { 137 response = builder.post(ClientResponse.class, data); 138 } 139 break; 140 case "PUT": 141 if (mp != null) { 142 response = builder.put(ClientResponse.class, mp); 143 } else { 144 response = builder.put(ClientResponse.class, data); 145 } 146 break; 147 case "DELETE": 148 response = builder.delete(ClientResponse.class, data); 149 break; 150 default: 151 throw new NuxeoException("Unknown request type: " + requestType); 152 } 153 } catch (Exception e) { 154 throw new RuntimeException(e); 155 } 156 if (response.getStatus() >= 200 && response.getStatus() < 300) { 157 return Blobs.createBlob(response.getEntityInputStream()); 158 } else { 159 return new StringBlob(response.getStatusInfo() != null ? response.getStatusInfo().toString() : "error"); 160 } 161 } 162 163 /** 164 * @since 8.4 165 */ 166 public Blob get(String url, Map<String, Object> options) throws IOException { 167 return invoke("GET", url, null, null, options); 168 } 169 170 /** 171 * @since 8.4 172 */ 173 public Blob post(String url, Object data, Map<String, Object> options) throws IOException { 174 return invoke("POST", url, data, null, options); 175 } 176 177 /** 178 * @since 8.4 179 */ 180 public Blob post(String url, MultiPart multiPart, Map<String, Object> options) throws IOException { 181 return invoke("POST", url, null, multiPart, options); 182 } 183 184 /** 185 * @since 8.4 186 */ 187 public Blob put(String url, Object data, Map<String, Object> options) throws IOException { 188 return invoke("PUT", url, data, null, options); 189 } 190 191 /** 192 * @since 8.4 193 */ 194 public Blob put(String url, MultiPart multiPart, Map<String, Object> options) throws IOException { 195 return invoke("PUT", url, null, multiPart, options); 196 } 197 198 /** 199 * @since 8.4 200 */ 201 public Blob delete(String url, Object data, Map<String, Object> options) throws IOException { 202 return invoke("DELETE", url, data, null, options); 203 } 204 205 private Blob invoke(String requestType, String url, Object data, MultiPart multipart, Map<String, Object> options) 206 throws IOException { 207 MultivaluedMap<String, String> queryParams = getQueryParameters(options); 208 Map<String, String> headers = getHeaderParameters(options); 209 210 ClientConfig config = new DefaultClientConfig(); 211 config.getClasses().add(MultiPartWriter.class); 212 Client client = Client.create(config); 213 client.setConnectTimeout(TIMEOUT); 214 215 WebResource wr = client.resource(url); 216 217 if (queryParams != null && !queryParams.isEmpty()) { 218 wr = wr.queryParams(queryParams); 219 } 220 WebResource.Builder builder; 221 builder = wr.accept(MediaType.APPLICATION_JSON); 222 if (multipart != null) { 223 builder = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE); 224 } 225 226 // Adding some headers if needed 227 if (headers != null && !headers.isEmpty()) { 228 for (String headerKey : headers.keySet()) { 229 builder.header(headerKey, headers.get(headerKey)); 230 } 231 } 232 ClientResponse response; 233 try { 234 switch (requestType) { 235 case "HEAD": 236 case "GET": 237 response = builder.get(ClientResponse.class); 238 break; 239 case "POST": 240 if (multipart != null) { 241 response = builder.post(ClientResponse.class, multipart); 242 } else { 243 response = builder.post(ClientResponse.class, data); 244 } 245 break; 246 case "PUT": 247 if (multipart != null) { 248 response = builder.put(ClientResponse.class, multipart); 249 } else { 250 response = builder.put(ClientResponse.class, data); 251 } 252 break; 253 case "DELETE": 254 response = builder.delete(ClientResponse.class, data); 255 break; 256 default: 257 throw new NuxeoException("Unknown request type: " + requestType); 258 } 259 } catch (Exception e) { 260 throw new RuntimeException(e); 261 } 262 if (response.getStatus() >= 200 && response.getStatus() < 300) { 263 return setUpBlob(response, url); 264 } else { 265 return new StringBlob(response.getStatusInfo() != null ? response.getStatusInfo().toString() : "error"); 266 } 267 } 268 269 private Map<String, String> getHeaderParameters(Map<String, Object> options) { 270 if (options != null) { 271 Map<String, String> headers = new HashMap<>(); 272 273 Map<String, String> authorization = (Map<String, String>) options.get("auth"); 274 if (authorization != null) { 275 String method = authorization.get("method"); 276 switch (method) { 277 case "basic": 278 Map<String, String> header = basicAuthentication(authorization.get("username"), 279 authorization.get("password")); 280 headers.putAll(header); 281 break; 282 default: 283 break; 284 } 285 } 286 287 Map<String, String> headersOptions = (Map<String, String>) options.get("headers"); 288 if (headersOptions != null) { 289 headers.putAll(headersOptions); 290 } 291 292 return headers; 293 } 294 return null; 295 } 296 297 private MultivaluedMap<String, String> getQueryParameters(Map<String, Object> options) { 298 if (options != null) { 299 Map<String, List<String>> params = (Map<String, List<String>>) options.get("params"); 300 if (params != null) { 301 MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); 302 for (String key : params.keySet()) { 303 queryParams.put(key, params.get(key)); 304 } 305 return queryParams; 306 } 307 } 308 return null; 309 } 310 311 private Blob setUpBlob(ClientResponse response, String url) throws IOException { 312 MultivaluedMap<String, String> headers = response.getHeaders(); 313 String disposition = headers.getFirst(HTTP_CONTENT_DISPOSITION); 314 315 String filename = ""; 316 if (disposition != null) { 317 // extracts file name from header field 318 int index = disposition.indexOf("filename="); 319 if (index > -1) { 320 filename = disposition.substring(index + 9); 321 } 322 } else { 323 // extracts file name from URL 324 filename = url.substring(url.lastIndexOf("/") + 1, url.length()); 325 } 326 327 Blob resultBlob = Blobs.createBlob(response.getEntityInputStream()); 328 if (!StringUtils.isEmpty(filename)) { 329 resultBlob.setFilename(filename); 330 } 331 332 String encoding = headers.getFirst(HttpHeaders.CONTENT_ENCODING); 333 if (encoding != null) { 334 resultBlob.setEncoding(encoding); 335 } 336 337 MediaType contentType = response.getType(); 338 if (contentType != null) { 339 resultBlob.setMimeType(contentType.getType()); 340 } 341 342 return resultBlob; 343 } 344 345 private Map<String, String> basicAuthentication(String username, String password) { 346 347 if (username == null || password == null) { 348 return null; 349 } 350 351 Map<String, String> authenticationHeader; 352 try { 353 final byte[] prefix = (username + ":").getBytes(Charset.forName("iso-8859-1")); 354 final byte[] usernamePassword = new byte[prefix.length + password.getBytes().length]; 355 356 System.arraycopy(prefix, 0, usernamePassword, 0, prefix.length); 357 System.arraycopy(password.getBytes(), 0, usernamePassword, prefix.length, password.getBytes().length); 358 359 String authentication = "Basic " + new String(Base64.encode(usernamePassword), "ASCII"); 360 361 authenticationHeader = new HashMap<>(); 362 authenticationHeader.put(HttpHeaders.AUTHORIZATION, authentication); 363 364 } catch (UnsupportedEncodingException ex) { 365 // This should never occur 366 throw new RuntimeException(ex); 367 } 368 return authenticationHeader; 369 } 370}