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