/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Thibaud Arguillere <targuillere@nuxeo.com> * Vladimir Pasquier <vpasquier@nuxeo.com> * Ricardo Dias <rdias@nuxeo.com> */ package org.nuxeo.ecm.automation.features; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import com.sun.jersey.core.util.Base64; import com.sun.jersey.core.util.MultivaluedMapImpl; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.map.ObjectMapper; import org.nuxeo.ecm.automation.context.ContextHelper; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.Blobs; import org.nuxeo.ecm.core.api.impl.blob.StringBlob; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; import com.sun.jersey.multipart.MultiPart; import com.sun.jersey.multipart.impl.MultiPartWriter; /** * @since 7.3 */ public class HTTPHelper implements ContextHelper { protected static volatile ObjectMapper mapper = new ObjectMapper(); private static final Integer TIMEOUT = 1000 * 60 * 5; // 5min private static final String HTTP_CONTENT_DISPOSITION = "Content-Disposition"; public Blob call(String username, String password, String requestType, String path) throws IOException { return call(username, password, requestType, path, null, null, null, null); } public Blob call(String username, String password, String requestType, String path, Map<String, String> headers) throws IOException { return call(username, password, requestType, path, null, null, null, headers); } public Blob call(String username, String password, String requestType, String path, MultiPart mp) throws IOException { return call(username, password, requestType, path, null, null, mp, null); } public Blob call(String username, String password, String requestType, String path, MultiPart mp, Map<String, String> headers) throws IOException { return call(username, password, requestType, path, null, null, mp, headers); } public Blob call(String username, String password, String requestType, String path, MultivaluedMap<String, String> queryParams) throws IOException { return call(username, password, requestType, path, null, queryParams, null, null); } public Blob call(String username, String password, String requestType, String path, Object data) throws IOException { return call(username, password, requestType, path, data, null, null, null); } public Blob call(String username, String password, String requestType, String path, Object data, Map<String, String> headers) throws IOException { return call(username, password, requestType, path, data, null, null, headers); } public Blob call(String username, String password, String requestType, String url, Object data, MultivaluedMap<String, String> queryParams, MultiPart mp, Map<String, String> headers) throws IOException { ClientConfig config = new DefaultClientConfig(); config.getClasses().add(MultiPartWriter.class); Client client = Client.create(config); client.setConnectTimeout(TIMEOUT); client.setReadTimeout(TIMEOUT); if (username != null && password != null) { client.addFilter(new HTTPBasicAuthFilter(username, password)); } WebResource wr = client.resource(url); if (queryParams != null && !queryParams.isEmpty()) { wr = wr.queryParams(queryParams); } WebResource.Builder builder; builder = wr.accept(MediaType.APPLICATION_JSON); if (mp != null) { builder = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE); } // Adding some headers if needed if (headers != null && !headers.isEmpty()) { for (String headerKey : headers.keySet()) { builder.header(headerKey, headers.get(headerKey)); } } ClientResponse response = null; try { switch (requestType) { case "HEAD": case "GET": response = builder.get(ClientResponse.class); break; case "POST": if (mp != null) { response = builder.post(ClientResponse.class, mp); } else { response = builder.post(ClientResponse.class, data); } break; case "PUT": if (mp != null) { response = builder.put(ClientResponse.class, mp); } else { response = builder.put(ClientResponse.class, data); } break; case "DELETE": response = builder.delete(ClientResponse.class, data); break; default: break; } } catch (Exception e) { throw new RuntimeException(e); } if (response != null && response.getStatus() >= 200 && response.getStatus() < 300) { return Blobs.createBlob(response.getEntityInputStream()); } else { return new StringBlob(response.getStatusInfo() != null ? response.getStatusInfo().toString() : "error"); } } /** * @since 8.4 */ public Blob get(String url, Map<String, Object> options) throws IOException { return invoke("GET", url, null, null, options); } /** * @since 8.4 */ public Blob post(String url, Object data, Map<String, Object> options) throws IOException { return invoke("POST", url, data, null, options); } /** * @since 8.4 */ public Blob post(String url, MultiPart multiPart, Map<String, Object> options) throws IOException { return invoke("POST", url, null, multiPart, options); } /** * @since 8.4 */ public Blob put(String url, Object data, Map<String, Object> options) throws IOException { return invoke("PUT", url, data, null, options); } /** * @since 8.4 */ public Blob put(String url, MultiPart multiPart, Map<String, Object> options) throws IOException { return invoke("PUT", url, null, multiPart, options); } /** * @since 8.4 */ public Blob delete(String url, Object data, Map<String, Object> options) throws IOException { return invoke("DELETE", url, data, null, options); } private Blob invoke(String requestType, String url, Object data, MultiPart multipart, Map<String, Object> options) throws IOException { MultivaluedMap<String, String> queryParams = getQueryParameters(options); Map<String, String> headers = getHeaderParameters(options); ClientConfig config = new DefaultClientConfig(); config.getClasses().add(MultiPartWriter.class); Client client = Client.create(config); client.setConnectTimeout(TIMEOUT); WebResource wr = client.resource(url); if (queryParams != null && !queryParams.isEmpty()) { wr = wr.queryParams(queryParams); } WebResource.Builder builder; builder = wr.accept(MediaType.APPLICATION_JSON); if (multipart != null) { builder = wr.type(MediaType.MULTIPART_FORM_DATA_TYPE); } // Adding some headers if needed if (headers != null && !headers.isEmpty()) { for (String headerKey : headers.keySet()) { builder.header(headerKey, headers.get(headerKey)); } } ClientResponse response = null; try { switch (requestType) { case "HEAD": case "GET": response = builder.get(ClientResponse.class); break; case "POST": if (multipart != null) { response = builder.post(ClientResponse.class, multipart); } else { response = builder.post(ClientResponse.class, data); } break; case "PUT": if (multipart != null) { response = builder.put(ClientResponse.class, multipart); } else { response = builder.put(ClientResponse.class, data); } break; case "DELETE": response = builder.delete(ClientResponse.class, data); break; default: break; } } catch (Exception e) { throw new RuntimeException(e); } if (response != null && response.getStatus() >= 200 && response.getStatus() < 300) { return setUpBlob(response, url); } else { return new StringBlob(response.getStatusInfo() != null ? response.getStatusInfo().toString() : "error"); } } private Map<String, String> getHeaderParameters(Map<String, Object> options) { if (options != null) { Map<String, String> headers = new HashMap<>(); Map<String, String> authorization = (Map<String, String>) options.get("auth"); if (authorization != null) { String method = authorization.get("method"); switch (method) { case "basic": Map<String, String> header = basicAuthentication(authorization.get("username"), authorization.get("password")); headers.putAll(header); break; default: break; } } Map<String, String> headersOptions = (Map<String, String>) options.get("headers"); if (headersOptions != null) { headers.putAll(headersOptions); } return headers; } return null; } private MultivaluedMap<String, String> getQueryParameters(Map<String, Object> options) { if (options != null) { Map<String, List<String>> params = (Map<String, List<String>>) options.get("params"); if (params != null) { MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); for (String key : params.keySet()) { queryParams.put(key, params.get(key)); } return queryParams; } } return null; } private Blob setUpBlob(ClientResponse response, String url) throws IOException { MultivaluedMap<String, String> headers = response.getHeaders(); String disposition = headers.getFirst(HTTP_CONTENT_DISPOSITION); String filename = ""; if (disposition != null) { // extracts file name from header field int index = disposition.indexOf("filename="); if (index > -1) { filename = disposition.substring(index + 9); } } else { // extracts file name from URL filename = url.substring(url.lastIndexOf("/") + 1, url.length()); } Blob resultBlob = Blobs.createBlob(response.getEntityInputStream()); if (!StringUtils.isEmpty(filename)) { resultBlob.setFilename(filename); } String encoding = headers.getFirst(HttpHeaders.CONTENT_ENCODING); if (encoding != null) { resultBlob.setEncoding(encoding); } MediaType contentType = response.getType(); if (contentType != null) { resultBlob.setMimeType(contentType.getType()); } return resultBlob; } private Map<String, String> basicAuthentication(String username, String password) { if (username == null || password == null) { return null; } Map<String, String> authenticationHeader; try { final byte[] prefix = (username + ":").getBytes(Charset.forName("iso-8859-1")); final byte[] usernamePassword = new byte[prefix.length + password.getBytes().length]; System.arraycopy(prefix, 0, usernamePassword, 0, prefix.length); System.arraycopy(password.getBytes(), 0, usernamePassword, prefix.length, password.getBytes().length); String authentication = "Basic " + new String(Base64.encode(usernamePassword), "ASCII"); authenticationHeader = new HashMap<>(); authenticationHeader.put(HttpHeaders.AUTHORIZATION, authentication); } catch (UnsupportedEncodingException ex) { // This should never occur throw new RuntimeException(ex); } return authenticationHeader; } }